summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/tools')
-rw-r--r--src/VBox/Runtime/tools/Makefile.kmk287
-rw-r--r--src/VBox/Runtime/tools/RTCat.cpp330
-rw-r--r--src/VBox/Runtime/tools/RTChMod.cpp435
-rw-r--r--src/VBox/Runtime/tools/RTCp.cpp361
-rw-r--r--src/VBox/Runtime/tools/RTDbgSymCache.cpp1858
-rw-r--r--src/VBox/Runtime/tools/RTDbgSymSrv.cpp600
-rw-r--r--src/VBox/Runtime/tools/RTEfiFatExtract.cpp263
-rw-r--r--src/VBox/Runtime/tools/RTEfiSigDb.cpp833
-rw-r--r--src/VBox/Runtime/tools/RTFtpServer.cpp666
-rw-r--r--src/VBox/Runtime/tools/RTFuzzClient.cpp54
-rw-r--r--src/VBox/Runtime/tools/RTFuzzMaster.cpp54
-rw-r--r--src/VBox/Runtime/tools/RTGzip.cpp55
-rw-r--r--src/VBox/Runtime/tools/RTHttp.cpp188
-rw-r--r--src/VBox/Runtime/tools/RTHttpServer.cpp889
-rw-r--r--src/VBox/Runtime/tools/RTIsoMaker.cpp54
-rw-r--r--src/VBox/Runtime/tools/RTKrnlModInfo.cpp187
-rw-r--r--src/VBox/Runtime/tools/RTLdrCheckImports.cpp708
-rw-r--r--src/VBox/Runtime/tools/RTLdrFlt.cpp598
-rw-r--r--src/VBox/Runtime/tools/RTLs.cpp54
-rw-r--r--src/VBox/Runtime/tools/RTManifest.cpp404
-rw-r--r--src/VBox/Runtime/tools/RTMkDir.cpp379
-rw-r--r--src/VBox/Runtime/tools/RTNtDbgHelp.cpp393
-rw-r--r--src/VBox/Runtime/tools/RTRm.cpp54
-rw-r--r--src/VBox/Runtime/tools/RTRmDir.cpp369
-rw-r--r--src/VBox/Runtime/tools/RTShutdown.cpp114
-rw-r--r--src/VBox/Runtime/tools/RTSignTool.cpp6330
-rw-r--r--src/VBox/Runtime/tools/RTTar.cpp54
-rw-r--r--src/VBox/Runtime/tools/RTTraceLogTool.cpp343
-rw-r--r--src/VBox/Runtime/tools/RTUnzip.cpp54
29 files changed, 16968 insertions, 0 deletions
diff --git a/src/VBox/Runtime/tools/Makefile.kmk b/src/VBox/Runtime/tools/Makefile.kmk
new file mode 100644
index 00000000..929bd728
--- /dev/null
+++ b/src/VBox/Runtime/tools/Makefile.kmk
@@ -0,0 +1,287 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the IPRT tools.
+#
+
+#
+# Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>.
+#
+# 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
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+
+if !defined(VBOX_ONLY_EXTPACKS) && !defined(VBOX_ONLY_DOCS)
+ # RTIsoMaker - ISO image maker - build version.
+ ifeq ($(KBUILD_TARGET),win) # Needed for repacking guest additions.
+ PROGRAMS += bldRTIsoMaker
+ bldRTIsoMaker_INSTTYPE = stage
+ else
+ BLDPROGS += bldRTIsoMaker
+ endif
+ bldRTIsoMaker_TEMPLATE = VBoxAdvBldProg
+ bldRTIsoMaker_SOURCES = \
+ RTIsoMaker.cpp \
+ ../common/misc/buildconfig.cpp
+ bldRTIsoMaker_DEFS = \
+ IPRT_BLDCFG_SCM_REV=$(if $(VBOX_SVN_REV_FALLBACK),$(VBOX_SVN_REV_FALLBACK),$(VBOX_SVN_REV)) \
+ IPRT_BLDCFG_VERSION_MAJOR=$(VBOX_VERSION_MAJOR) \
+ IPRT_BLDCFG_VERSION_MINOR=$(VBOX_VERSION_MINOR) \
+ IPRT_BLDCFG_VERSION_BUILD=$(VBOX_VERSION_BUILD)
+ ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ bldRTIsoMaker_DEFS += \
+ IPRT_BLDCFG_VERSION_STRING="$(VBOX_VERSION_STRING)" \
+ IPRT_BLDCFG_TARGET="$(KBUILD_TARGET)" \
+ IPRT_BLDCFG_TARGET_ARCH="$(KBUILD_TARGET_ARCH)" \
+ IPRT_BLDCFG_TYPE="$(KBUILD_TYPE)"
+ else
+ bldRTIsoMaker_DEFS += \
+ IPRT_BLDCFG_VERSION_STRING=\"$(VBOX_VERSION_STRING)\" \
+ IPRT_BLDCFG_TARGET=\"$(KBUILD_TARGET)\" \
+ IPRT_BLDCFG_TARGET_ARCH=\"$(KBUILD_TARGET_ARCH)\" \
+ IPRT_BLDCFG_TYPE=\"$(KBUILD_TYPE)\"
+ endif
+ bldRTIsoMaker_INCS = ../include
+endif
+
+
+if !defined(VBOX_ONLY_DOCS)
+
+ # RTManifest is a tool for creating and verifying manifest files - build version.
+ BLDPROGS += bldRTManifest
+ bldRTManifest_TEMPLATE = VBoxAdvBldProg
+ bldRTManifest_SOURCES = RTManifest.cpp
+
+
+ if !defined(VBOX_ONLY_EXTPACKS) || "$(KBUILD_TARGET)" == "win" || "$(KBUILD_TARGET)" == "darwin"
+ # RTSignTool - Signing utility - build version. Signed on windows so we can get the certificate from it.
+ BLDPROGS += bldRTSignTool
+ bldRTSignTool_TEMPLATE := VBoxAdvBldProg
+ bldRTSignTool_INCS := ../include
+ bldRTSignTool_SOURCES := RTSignTool.cpp
+ bldRTSignTool_DEFS := IPRT_IN_BUILD_TOOL
+ bldRTSignTool_LDFLAGS.darwin := \
+ -framework CoreFoundation \
+ -framework Security
+ bldRTSignTool_LIBS.win := Crypt32.lib NCrypt.lib
+ ifndef VBOX_WITH_BLD_RTSIGNTOOL_SIGNING
+ bldRTSignTool_DEFS += IPRT_SIGNTOOL_NO_SIGNING
+ else # RuntimeBldProg is missing a lot and is built w/o IPRT_WITH_OPENSSL. So, include missing and rebuilt openssl deps.
+ bldRTSignTool_SDKS += VBoxOpenSslBldProg
+ bldRTSignTool_DEFS += IPRT_WITH_OPENSSL
+ bldRTSignTool_SOURCES += \
+ ../common/crypto/pkix-signature-builtin.cpp \
+ ../common/crypto/pkix-signature-ossl.cpp \
+ ../common/crypto/store.cpp \
+ ../common/crypto/digest-builtin.cpp \
+ ../common/crypto/iprt-openssl.cpp \
+ ../common/crypto/key.cpp \
+ ../common/crypto/key-file.cpp \
+ ../common/crypto/key-openssl.cpp \
+ ../common/crypto/pkcs7-sign.cpp \
+ ../common/crypto/RTCrRandBytes-openssl.cpp
+ if1of ($(KBUILD_HOST), darwin win)
+ bldRTSignTool_SOURCES += ../r3/$(KBUILD_HOST)/RTCrStoreCreateSnapshotById-$(KBUILD_HOST).cpp
+ else
+ bldRTSignTool_SOURCES += ../generic/RTCrStoreCreateSnapshotById-generic.cpp
+ endif
+ endif
+ endif
+
+ if !defined(VBOX_ONLY_EXTPACKS)
+ # RTLdrCheckImports - import checker.
+ PROGRAMS += bldRTLdrCheckImports
+ bldRTLdrCheckImports_TEMPLATE = VBoxAdvBldProg
+ bldRTLdrCheckImports_DEFS = IPRT_IN_BUILD_TOOL
+ bldRTLdrCheckImports_SOURCES = RTLdrCheckImports.cpp
+ endif
+endif
+
+if !defined(VBOX_ONLY_BUILD)
+
+ # RTCat is a tool for displaying files.
+ PROGRAMS += RTCat
+ RTCat_TEMPLATE = VBoxR3Tool
+ RTCat_SOURCES = RTCat.cpp
+ RTCat_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTChMod - our chmod clone.
+ PROGRAMS += RTChMod
+ RTChMod_TEMPLATE = VBoxR3Tool
+ RTChMod_SOURCES = RTChMod.cpp
+
+ # RTCp - our cp clone.
+ PROGRAMS += RTCp
+ RTCp_TEMPLATE = VBoxR3Tool
+ RTCp_SOURCES = RTCp.cpp
+ RTCp_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTIsoMaker - ISO image maker - build version.
+ PROGRAMS += RTIsoMaker
+ RTIsoMaker_TEMPLATE = VBoxR3Tool
+ RTIsoMaker_SOURCES = RTIsoMaker.cpp
+ RTIsoMaker_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTLs is a tool for listing file information.
+ PROGRAMS += RTLs
+ RTLs_TEMPLATE = VBoxR3Tool
+ RTLs_SOURCES = RTLs.cpp
+ RTLs_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTRm is a tool for removing files and directories.
+ PROGRAMS += RTRm
+ RTRm_TEMPLATE = VBoxR3Tool
+ RTRm_SOURCES = RTRm.cpp
+ RTRm_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTManifest is a tool for creating and verifying manifest files.
+ PROGRAMS += RTManifest
+ RTManifest_TEMPLATE = VBoxR3Tool
+ RTManifest_SOURCES = RTManifest.cpp
+ RTManifest_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTLdrFlt is similar to c++filt, except that it's for VMMR0.r0 stacks.
+ PROGRAMS += RTLdrFlt
+ RTLdrFlt_TEMPLATE = VBoxR3Tool
+ RTLdrFlt_SOURCES = RTLdrFlt.cpp
+
+ # RTFtpServer implements a simple FTP server.
+ PROGRAMS += RTFtpServer
+ RTFtpServer_TEMPLATE = VBoxR3Tool
+ RTFtpServer_SOURCES = RTFtpServer.cpp
+
+ # RTGzip - our gzip clone (for testing the gzip/gunzip streaming code)
+ PROGRAMS += RTGzip
+ RTGzip_TEMPLATE = VBoxR3Tool
+ RTGzip_SOURCES = RTGzip.cpp
+ RTGzip_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ ifdef VBOX_WITH_LIBCURL
+ # RTHttp - our http/https fetcher (for testing the http client API).
+ PROGRAMS += RTHttp
+ RTHttp_TEMPLATE = VBoxR3Tool
+ RTHttp_SOURCES = RTHttp.cpp
+ endif
+
+ # RTHttpServer implements a simple HTTP server.
+ PROGRAMS += RTHttpServer
+ RTHttpServer_TEMPLATE = VBoxR3Tool
+ RTHttpServer_SOURCES = RTHttpServer.cpp
+ ifdef IPRT_HTTP_WITH_WEBDAV
+ RTHttpServer_DEFS += IPRT_HTTP_WITH_WEBDAV
+ endif
+
+ # RTLdrCheckImports - import checker.
+ PROGRAMS += RTLdrCheckImports
+ RTLdrCheckImports_TEMPLATE = VBoxR3Tool
+ RTLdrCheckImports_SOURCES = RTLdrCheckImports.cpp
+ RTLdrCheckImports_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTMkDir - our mkdir clone.
+ PROGRAMS += RTMkDir
+ RTMkDir_TEMPLATE = VBoxR3Tool
+ RTMkDir_SOURCES = RTMkDir.cpp
+
+ # RTRmDir - our mkdir clone.
+ PROGRAMS += RTRmDir
+ RTRmDir_TEMPLATE = VBoxR3Tool
+ RTRmDir_SOURCES = RTRmDir.cpp
+
+ # RTShutdown - similar (but not identical) to a typical unix shutdown command.
+ PROGRAMS += RTShutdown
+ RTShutdown_TEMPLATE = VBoxR3Tool
+ RTShutdown_SOURCES = RTShutdown.cpp
+
+ # RTTar - our tar clone (for testing the tar/gzip/gunzip streaming code)
+ PROGRAMS += RTTar
+ RTTar_TEMPLATE = VBoxR3Tool
+ RTTar_SOURCES = RTTar.cpp
+ RTTar_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTUnzip - our unzip clone (for testing the unzip streaming code)
+ PROGRAMS += RTUnzip
+ RTUnzip_TEMPLATE = VBoxR3Tool
+ RTUnzip_SOURCES = RTUnzip.cpp
+ RTUnzip_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTNtDbgHelp - our tar clone (for testing the tar/gzip/gunzip streaming code)
+ PROGRAMS.win += RTNtDbgHelp
+ RTNtDbgHelp_TEMPLATE = VBoxR3Tool
+ RTNtDbgHelp_SOURCES = RTNtDbgHelp.cpp
+
+ # RTDbgSymCache - Symbol cache manager.
+ PROGRAMS += RTDbgSymCache
+ RTDbgSymCache_TEMPLATE = VBoxR3Tool
+ RTDbgSymCache_SOURCES = RTDbgSymCache.cpp
+
+ # RTSignTool - Signing utility.
+ PROGRAMS += RTSignTool
+ RTSignTool_TEMPLATE := VBoxR3Tool
+ RTSignTool_INCS := ../include
+ RTSignTool_SOURCES := RTSignTool.cpp
+ RTSignTool_LIBS = $(PATH_STAGE_LIB)/SUPR3$(VBOX_SUFF_LIB)
+ RTSignTool_LIBS.win = Crypt32.lib NCrypt.lib
+
+ # RTTraceLogTool - Trace log collection and dissection tool.
+ PROGRAMS += RTTraceLogTool
+ RTTraceLogTool_TEMPLATE = VBoxR3Tool
+ RTTraceLogTool_SOURCES = RTTraceLogTool.cpp
+
+ # RTFuzzMaster - Fuzzing master tool.
+ PROGRAMS += RTFuzzMaster
+ RTFuzzMaster_TEMPLATE = VBoxR3Tool
+ RTFuzzMaster_SOURCES = RTFuzzMaster.cpp
+
+ # RTFuzzClient - Fuzzing client tool.
+ PROGRAMS += RTFuzzClient
+ RTFuzzClient_TEMPLATE = VBoxR3Tool
+ RTFuzzClient_SOURCES = RTFuzzClient.cpp
+
+ # RTEfiFatExtract - Extracting single files from a fat EFI binary.
+ PROGRAMS += RTEfiFatExtract
+ RTEfiFatExtract_TEMPLATE = VBoxR3Tool
+ RTEfiFatExtract_SOURCES = RTEfiFatExtract.cpp
+
+ # RTEfiSigDb - EFI signature database management tool.
+ PROGRAMS += RTEfiSigDb
+ RTEfiSigDb_TEMPLATE = VBoxR3Tool
+ RTEfiSigDb_SOURCES = RTEfiSigDb.cpp
+
+ if1of ($(KBUILD_TARGET), darwin linux solaris win)
+ # RTKrnlModInfo - our lsmod/kextstat clone (for testing the RTKrnlMod code).
+ PROGRAMS += RTKrnlModInfo
+ RTKrnlModInfo_TEMPLATE = VBoxR3Tool
+ RTKrnlModInfo_SOURCES = RTKrnlModInfo.cpp
+ endif
+
+endif # !VBOX_ONLY_BUILD
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Runtime/tools/RTCat.cpp b/src/VBox/Runtime/tools/RTCat.cpp
new file mode 100644
index 00000000..b34204be
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTCat.cpp
@@ -0,0 +1,330 @@
+/* $Id: RTCat.cpp $ */
+/** @file
+ * IPRT - cat like utility.
+ */
+
+/*
+ * Copyright (C) 2017-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/vfs.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * CAT command options.
+ */
+typedef struct RTCMDCATOPTS
+{
+ bool fShowEnds; /**< -E */
+ bool fShowNonPrinting; /**< -v */
+ bool fShowTabs; /**< -T */
+ bool fSqueezeBlankLines; /**< -s */
+ bool fNumberLines; /**< -n */
+ bool fNumberNonBlankLines; /**< -b */
+ bool fAdvisoryOutputLock; /**< -l */
+ bool fUnbufferedOutput; /**< -u */
+} RTCMDCATOPTS;
+/** Pointer to const CAT options. */
+typedef RTCMDCATOPTS const *PCRTCMDCATOPTS;
+
+
+
+/**
+ * Outputs the source raw.
+ *
+ * @returns Command exit, error messages written using RTMsg*.
+ * @param hVfsOutput The output I/O stream.
+ * @param hVfsSrc The input I/O stream.
+ * @param pszSrc The input name.
+ */
+static RTEXITCODE rtCmdCatShowRaw(RTVFSIOSTREAM hVfsOutput, RTVFSIOSTREAM hVfsSrc, const char *pszSrc)
+{
+ int rc = RTVfsUtilPumpIoStreams(hVfsSrc, hVfsOutput, 0 /*cbBufHint*/);
+ if (RT_SUCCESS(rc))
+ return RTEXITCODE_SUCCESS;
+ return RTMsgErrorExitFailure("Error catting '%s': %Rrc", pszSrc, rc);
+}
+
+
+/**
+ * Outputs the source with complicated formatting.
+ *
+ * @returns Command exit, error messages written using RTMsg*.
+ * @param hVfsOutput The output I/O stream.
+ * @param hVfsSrc The input I/O stream.
+ * @param pszSrc The input name.
+ */
+static RTEXITCODE rtCmdCatShowComplicated(RTVFSIOSTREAM hVfsOutput, RTVFSIOSTREAM hVfsSrc, const char *pszSrc,
+ PCRTCMDCATOPTS pOpts)
+{
+ if (pOpts->fShowEnds)
+ RTMsgWarning("--show-ends is not implemented\n");
+ if (pOpts->fShowTabs)
+ RTMsgWarning("--show-tabs is not implemented\n");
+ if (pOpts->fShowNonPrinting)
+ RTMsgWarning("--show-nonprinting is not implemented\n");
+ if (pOpts->fSqueezeBlankLines)
+ RTMsgWarning("--squeeze-blank is not implemented\n");
+ if (pOpts->fNumberLines)
+ RTMsgWarning("--number is not implemented\n");
+ if (pOpts->fNumberNonBlankLines)
+ RTMsgWarning("--number-nonblank is not implemented\n");
+ return rtCmdCatShowRaw(hVfsOutput, hVfsSrc, pszSrc);
+}
+
+
+/**
+ * Opens the input file.
+ *
+ * @returns Command exit, error messages written using RTMsg*.
+ *
+ * @param pszFile The input filename.
+ * @param phVfsIos Where to return the input stream handle.
+ */
+static RTEXITCODE rtCmdCatOpenInput(const char *pszFile, PRTVFSIOSTREAM phVfsIos)
+{
+ int rc;
+
+ if (!strcmp(pszFile, "-"))
+ {
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ true /*fLeaveOpen*/,
+ phVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Error opening standard input: %Rrc", rc);
+ }
+ else
+ {
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenIoStream(pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ phVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pszFile, rc, offError, &ErrInfo.Core);
+ }
+
+ return RTEXITCODE_SUCCESS;
+
+}
+
+
+/**
+ * A /bin/cat clone.
+ *
+ * @returns Program exit code.
+ *
+ * @param cArgs The number of arguments.
+ * @param papszArgs The argument vector. (Note that this may be
+ * reordered, so the memory must be writable.)
+ */
+RTEXITCODE RTCmdCat(unsigned cArgs, char **papszArgs)
+{
+
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--show-all", 'A', RTGETOPT_REQ_NOTHING },
+ { "--number-nonblanks", 'b', RTGETOPT_REQ_NOTHING },
+ { "--show-ends-and-nonprinting", 'e', RTGETOPT_REQ_NOTHING },
+ { "--show-ends", 'E', RTGETOPT_REQ_NOTHING },
+ { "--advisory-output-lock", 'l', RTGETOPT_REQ_NOTHING },
+ { "--number", 'n', RTGETOPT_REQ_NOTHING },
+ { "--squeeze-blank", 's', RTGETOPT_REQ_NOTHING },
+ { "--show-tabs-and-nonprinting", 't', RTGETOPT_REQ_NOTHING },
+ { "--show-tabs", 'T', RTGETOPT_REQ_NOTHING },
+ { "--unbuffered-output", 'u', RTGETOPT_REQ_NOTHING },
+ { "--show-nonprinting", 'v', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTCMDCATOPTS Opts;
+ Opts.fShowEnds = false;
+ Opts.fShowNonPrinting = false;
+ Opts.fShowTabs = false;
+ Opts.fSqueezeBlankLines = false;
+ Opts.fNumberLines = false;
+ Opts.fNumberNonBlankLines = false;
+ Opts.fAdvisoryOutputLock = false;
+ Opts.fUnbufferedOutput = false;
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ unsigned cProcessed = 0;
+ RTVFSIOSTREAM hVfsOutput = NIL_RTVFSIOSTREAM;
+ int rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ true /*fLeaveOpen*/, &hVfsOutput);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTVfsIoStrmFromStdHandle: %Rrc", rc);
+
+ RTGETOPTSTATE GetState;
+ rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_SUCCESS(rc))
+ {
+ bool fContinue = true;
+ do
+ {
+ RTGETOPTUNION ValueUnion;
+ int chOpt = RTGetOpt(&GetState, &ValueUnion);
+ switch (chOpt)
+ {
+ case 0:
+ /*
+ * If we've processed any files we're done. Otherwise take
+ * input from stdin and write the output to stdout.
+ */
+ if (cProcessed > 0)
+ {
+ fContinue = false;
+ break;
+ }
+ ValueUnion.psz = "-";
+ RT_FALL_THRU();
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ RTVFSIOSTREAM hVfsSrc;
+ RTEXITCODE rcExit2 = rtCmdCatOpenInput(ValueUnion.psz, &hVfsSrc);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ {
+ if ( Opts.fShowEnds
+ || Opts.fShowTabs
+ || Opts.fShowNonPrinting
+ || Opts.fSqueezeBlankLines
+ || Opts.fNumberLines
+ || Opts.fNumberNonBlankLines)
+ rcExit2 = rtCmdCatShowComplicated(hVfsOutput, hVfsSrc, ValueUnion.psz, &Opts);
+ else
+ rcExit2 = rtCmdCatShowRaw(hVfsOutput, hVfsSrc, ValueUnion.psz);
+ RTVfsIoStrmRelease(hVfsSrc);
+ }
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ cProcessed++;
+ break;
+ }
+
+ case 'A':
+ Opts.fShowNonPrinting = true;
+ Opts.fShowEnds = true;
+ Opts.fShowTabs = true;
+ break;
+
+ case 'b':
+ Opts.fNumberNonBlankLines = true;
+ break;
+
+ case 'e':
+ Opts.fShowNonPrinting = true;
+ RT_FALL_THRU();
+ case 'E':
+ Opts.fShowEnds = true;
+ break;
+
+ case 'l':
+ Opts.fAdvisoryOutputLock = true;
+ break;
+
+ case 'n':
+ Opts.fNumberLines = true;
+ Opts.fNumberNonBlankLines = false;
+ break;
+
+ case 's':
+ Opts.fSqueezeBlankLines = true;
+ break;
+
+ case 't':
+ Opts.fShowNonPrinting = true;
+ RT_FALL_THRU();
+ case 'T':
+ Opts.fShowTabs = true;
+ break;
+
+ case 'u': /* currently ignored */
+ Opts.fUnbufferedOutput = true;
+ break;
+
+ case 'v':
+ Opts.fShowNonPrinting = true;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: to be written\nOption dump:\n");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
+ RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
+ fContinue = false;
+ break;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ fContinue = false;
+ break;
+
+ default:
+ rcExit = RTGetOptPrintError(chOpt, &ValueUnion);
+ fContinue = false;
+ break;
+ }
+ } while (fContinue);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc);
+ RTVfsIoStrmRelease(hVfsOutput);
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTCmdCat(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTChMod.cpp b/src/VBox/Runtime/tools/RTChMod.cpp
new file mode 100644
index 00000000..46ddd6c2
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTChMod.cpp
@@ -0,0 +1,435 @@
+/* $Id: RTChMod.cpp $ */
+/** @file
+ * IPRT - Changes the mode/attributes of a file system object.
+ */
+
+/*
+ * Copyright (C) 2013-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** What to clear we all bits are being set. */
+#define RTCHMOD_SET_ALL_MASK (~( RTFS_TYPE_MASK \
+ | RTFS_DOS_NT_ENCRYPTED \
+ | RTFS_DOS_NT_COMPRESSED \
+ | RTFS_DOS_NT_REPARSE_POINT \
+ | RTFS_DOS_NT_SPARSE_FILE \
+ | RTFS_DOS_NT_DEVICE \
+ | RTFS_DOS_DIRECTORY))
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef enum RTCMDCHMODNOISE
+{
+ kRTCmdChModNoise_Quiet,
+ kRTCmdChModNoise_Default,
+ kRTCmdChModNoise_Changes,
+ kRTCmdChModNoise_Verbose
+} RTCMDCHMODNOISE;
+
+
+typedef struct RTCMDCHMODOPTS
+{
+ /** The noise level. */
+ RTCMDCHMODNOISE enmNoiseLevel;
+ /** -R, --recursive */
+ bool fRecursive;
+ /** --preserve-root / --no-preserve-root (don't allow recursion from root). */
+ bool fPreserveRoot;
+ /** Whether to always use the VFS chain API (for testing). */
+ bool fAlwaysUseChainApi;
+ /** Which mode bits to set. */
+ RTFMODE fModeSet;
+ /** Which mode bits to clear. */
+ RTFMODE fModeClear;
+} RTCMDCHMODOPTS;
+
+
+
+/**
+ * Calculates the new file mode.
+ *
+ * @returns New mode mask.
+ * @param pOpts The chmod options.
+ * @param fMode The current file mode.
+ */
+static RTFMODE rtCmdMkModCalcNewMode(RTCMDCHMODOPTS const *pOpts, RTFMODE fMode)
+{
+ fMode &= ~pOpts->fModeClear;
+ fMode |= pOpts->fModeSet;
+ /** @todo do 'X' */
+ return fMode;
+}
+
+
+/**
+ * Changes the file mode of one file system object.
+ *
+ * @returns exit code
+ * @param pOpts The chmod options.
+ * @param pszPath The path to the file system object to change the
+ * file mode of.
+ */
+static RTEXITCODE rtCmdChModOne(RTCMDCHMODOPTS const *pOpts, const char *pszPath)
+{
+ int rc;
+ RTFSOBJINFO ObjInfo;
+ bool fChanges = false;
+ if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszPath) )
+ {
+ rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ RTFMODE fNewMode = rtCmdMkModCalcNewMode(pOpts, ObjInfo.Attr.fMode);
+ fChanges = fNewMode != ObjInfo.Attr.fMode;
+ if (fChanges)
+ {
+ rc = RTPathSetMode(pszPath, fNewMode);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTPathSetMode failed on '%s' with fNewMode=%#x: %Rrc", pszPath, fNewMode, rc);
+ }
+ }
+ else
+ RTMsgError("RTPathQueryInfoEx failed on '%s': %Rrc", pszPath, rc);
+ }
+ else
+ {
+ RTVFSOBJ hVfsObj;
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenObj(pszPath, RTFILE_O_ACCESS_ATTR_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_CREATE_NOTHING | RTPATH_F_FOLLOW_LINK,
+ &hVfsObj, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ RTFMODE fNewMode = rtCmdMkModCalcNewMode(pOpts, ObjInfo.Attr.fMode);
+ fChanges = fNewMode != ObjInfo.Attr.fMode;
+ if (fChanges)
+ {
+ rc = RTVfsObjSetMode(hVfsObj, fNewMode, RTCHMOD_SET_ALL_MASK);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTVfsObjSetMode failed on '%s' with fNewMode=%#x: %Rrc", pszPath, fNewMode, rc);
+ }
+ }
+ else
+ RTVfsChainMsgError("RTVfsObjQueryInfo", pszPath, rc, offError, &ErrInfo.Core);
+ RTVfsObjRelease(hVfsObj);
+ }
+ else
+ RTVfsChainMsgError("RTVfsChainOpenObject", pszPath, rc, offError, &ErrInfo.Core);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->enmNoiseLevel >= (fChanges ? kRTCmdChModNoise_Changes : kRTCmdChModNoise_Verbose))
+ RTPrintf("%s\n", pszPath);
+ return RTEXITCODE_SUCCESS;
+ }
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Recursively changes the file mode.
+ *
+ * @returns exit code
+ * @param pOpts The mkdir option.
+ * @param pszPath The path to start changing the mode of.
+ */
+static int rtCmdChModRecursive(RTCMDCHMODOPTS const *pOpts, const char *pszPath)
+{
+ /*
+ * Check if it's a directory first. If not, join the non-recursive code.
+ */
+ int rc;
+ uint32_t offError;
+ RTFSOBJINFO ObjInfo;
+ RTERRINFOSTATIC ErrInfo;
+ bool const fUseChainApi = pOpts->fAlwaysUseChainApi || RTVfsChainIsSpec(pszPath);
+ if (!fUseChainApi)
+ {
+ rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTPathQueryInfoEx failed on '%s': %Rrc", pszPath, rc);
+ }
+ else
+ {
+ rc = RTVfsChainQueryInfo(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pszPath, rc, offError, &ErrInfo.Core);
+ }
+
+ if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ {
+ /*
+ * Don't bother redoing the above work if its not necessary.
+ */
+ RTFMODE fNewMode = rtCmdMkModCalcNewMode(pOpts, ObjInfo.Attr.fMode);
+ if (fNewMode != ObjInfo.Attr.fMode)
+ return rtCmdChModOne(pOpts, pszPath);
+ if (pOpts->enmNoiseLevel >= kRTCmdChModNoise_Verbose)
+ RTPrintf("%s\n", pszPath);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ /*
+ * For recursion we always use the VFS layer.
+ */
+ RTVFSDIR hVfsDir;
+ if (!fUseChainApi)
+ {
+ rc = RTVfsDirOpenNormal(pszPath, 0 /** @todo write attrib flag*/, &hVfsDir);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTVfsDirOpenNormal failed on '%s': %Rrc", pszPath, rc);
+ }
+ else
+ {
+ rc = RTVfsChainOpenDir(pszPath, 0 /** @todo write attrib flag*/, &hVfsDir, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pszPath, rc, offError, &ErrInfo.Core);
+ }
+
+ RTMsgError("Recursion is not yet implemented\n");
+ RTVfsDirRelease(hVfsDir);
+ rc = VERR_NOT_IMPLEMENTED;
+
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+static RTEXITCODE RTCmdChMod(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* operations */
+ { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
+ { "--preserve-root", 'x', RTGETOPT_REQ_NOTHING },
+ { "--no-preserve-root", 'X', RTGETOPT_REQ_NOTHING },
+ { "--changes", 'c', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'f', RTGETOPT_REQ_NOTHING },
+ { "--silent", 'f', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--reference", 'Z', RTGETOPT_REQ_NOTHING },
+ { "--always-use-vfs-chain-api", 'A', RTGETOPT_REQ_NOTHING },
+
+ };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc);
+
+ RTCMDCHMODOPTS Opts;
+ Opts.enmNoiseLevel = kRTCmdChModNoise_Default;
+ Opts.fPreserveRoot = false;
+ Opts.fRecursive = false;
+ Opts.fAlwaysUseChainApi = false;
+ Opts.fModeClear = 0;
+ Opts.fModeSet = 0;
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ case 'R':
+ Opts.fRecursive = true;
+ break;
+
+ case 'x':
+ Opts.fPreserveRoot = true;
+ break;
+ case 'X':
+ Opts.fPreserveRoot = false;
+ break;
+
+ case 'f':
+ Opts.enmNoiseLevel = kRTCmdChModNoise_Quiet;
+ break;
+ case 'c':
+ Opts.enmNoiseLevel = kRTCmdChModNoise_Changes;
+ break;
+ case 'v':
+ Opts.enmNoiseLevel = kRTCmdChModNoise_Verbose;
+ break;
+
+ case 'Z':
+ {
+ RTFSOBJINFO ObjInfo;
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ rc = RTVfsChainQueryInfo(ValueUnion.psz, &ObjInfo,RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", ValueUnion.psz, rc, offError, &ErrInfo.Core);
+ Opts.fModeClear = RTCHMOD_SET_ALL_MASK;
+ Opts.fModeSet = ObjInfo.Attr.fMode & RTCHMOD_SET_ALL_MASK;
+ break;
+ }
+
+ case 'A':
+ Opts.fAlwaysUseChainApi = true;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [options] <mode> <file> [..]\n"
+ "\n"
+ "Options:\n"
+ " -f, --silent, --quiet\n"
+ " -c, --changes\n"
+ " -v, --verbose\n"
+ " Noise level selection.\n"
+ " -R, --recursive\n"
+ " Recurse into directories.\n"
+ " --preserve-root, --no-preserve-root\n"
+ " Whether to allow recursion from the root (default: yes).\n"
+ " --reference <file>\n"
+ " Take mode mask to use from <file> instead of <mode>.\n"
+ "\n"
+ "The <mode> part isn't fully implemented, so only numerical octal notation\n"
+ "works. Prefix the number(s) with 0x to use hexadecimal. There are two forms\n"
+ "of the numerical notation: <SET> and <SET>:<CLEAR>\n"
+ , papszArgs[0]);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * The MODE.
+ */
+ if ( Opts.fModeClear == 0
+ && Opts.fModeSet == 0)
+ {
+ if (rc != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No mode change specified.\n");
+
+ char *pszNext;
+ if ( ValueUnion.psz[0] == '0'
+ && (ValueUnion.psz[1] == 'x' || ValueUnion.psz[1] == 'X'))
+ rc = RTStrToUInt32Ex(ValueUnion.psz, &pszNext, 16, &Opts.fModeSet);
+ else
+ rc = RTStrToUInt32Ex(ValueUnion.psz, &pszNext, 8, &Opts.fModeSet);
+ if ( rc != VINF_SUCCESS
+ && (rc != VWRN_TRAILING_CHARS || *pszNext != ':'))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unable to parse mode mask: %s\n", ValueUnion.psz);
+ Opts.fModeSet &= RTCHMOD_SET_ALL_MASK;
+
+ if (rc == VINF_SUCCESS)
+ Opts.fModeClear = RTCHMOD_SET_ALL_MASK;
+ else
+ {
+ pszNext++;
+ if ( pszNext[0] == '0'
+ && (pszNext[1] == 'x' || pszNext[1] == 'X'))
+ rc = RTStrToUInt32Ex(pszNext, &pszNext, 16, &Opts.fModeClear);
+ else
+ rc = RTStrToUInt32Ex(pszNext, &pszNext, 8, &Opts.fModeClear);
+ if (rc != VINF_SUCCESS)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unable to parse mode mask: %s\n", ValueUnion.psz);
+ Opts.fModeClear &= RTCHMOD_SET_ALL_MASK;
+ }
+
+ rc = RTGetOpt(&GetState, &ValueUnion);
+ }
+
+ /*
+ * No files means error.
+ */
+ if (rc != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No directories specified.\n");
+
+ /*
+ * Work thru the specified dirs.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ while (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ if (Opts.fRecursive)
+ rc = rtCmdChModRecursive(&Opts, ValueUnion.psz);
+ else
+ rc = rtCmdChModOne(&Opts, ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+
+ /* next */
+ rc = RTGetOpt(&GetState, &ValueUnion);
+ }
+ if (rc != 0)
+ rcExit = RTGetOptPrintError(rc, &ValueUnion);
+
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTCmdChMod(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTCp.cpp b/src/VBox/Runtime/tools/RTCp.cpp
new file mode 100644
index 00000000..0a96d6d0
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTCp.cpp
@@ -0,0 +1,361 @@
+/* $Id: RTCp.cpp $ */
+/** @file
+ * IPRT - cp like utility.
+ */
+
+/*
+ * Copyright (C) 2017-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/vfs.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/file.h>
+#include <iprt/fs.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * CAT command options.
+ */
+typedef struct RTCMDCPOPTS
+{
+ /** -v, --verbose. */
+ bool fVerbose;
+
+ /** -H */
+ bool fFollowCommandLineSymlinks;
+
+ /** Set if recursive copy. */
+ bool fRecursive;
+ /** -x, --one-filesystem. */
+ bool fOneFileSystem;
+
+ /** Special --no-replace-nor-trucate hack for basic NTFS write support. */
+ bool fNoReplaceNorTruncate;
+
+ /** Number of sources. */
+ size_t cSources;
+ /** Source files/dirs. */
+ const char **papszSources;
+ /** Destination dir/file. */
+ const char *pszDestination;
+} RTCMDCPOPTS;
+/** Pointer to const CAT options. */
+typedef RTCMDCPOPTS const *PCRTCMDCPOPTS;
+
+
+
+/**
+ * Does the copying, source by source.
+ *
+ * @returns exit code.
+ * @param pOpts Options.
+ */
+static RTEXITCODE rtCmdCpDoIt(PCRTCMDCPOPTS pOpts)
+{
+ /*
+ * Check out what the destination is.
+ */
+/** @todo need to cache + share VFS chain elements here! */
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ RTFSOBJINFO DstObjInfo;
+ int rc = RTVfsChainQueryInfo(pOpts->pszDestination, &DstObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->cSources > 1 && !RTFS_IS_DIRECTORY(DstObjInfo.Attr.fMode))
+ return RTMsgErrorExitFailure("Mutiple files to copy and destination is not a directory!");
+ }
+ else if (rc != VERR_FILE_NOT_FOUND)
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pOpts->pszDestination, rc, offError, &ErrInfo.Core);
+ else
+ RT_ZERO(DstObjInfo);
+#if !RT_GNUC_PREREQ(8,2) || RT_GNUC_PREREQ(8,3) /* GCC 8.2 produces a tautological compare warning/error here. */
+ AssertCompile(!RTFS_IS_DIRECTORY(0));
+#endif
+
+ /*
+ * Process the sources.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ for (size_t iSrc = 0; iSrc < pOpts->cSources; iSrc++)
+ {
+ const char *pszSrc = pOpts->papszSources[iSrc];
+ RTFSOBJINFO SrcObjInfo;
+ RT_ZERO(SrcObjInfo);
+ rc = RTVfsChainQueryInfo(pszSrc, &SrcObjInfo, RTFSOBJATTRADD_UNIX,
+ pOpts->fFollowCommandLineSymlinks ? RTPATH_F_FOLLOW_LINK : RTPATH_F_ON_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ {
+ rcExit = RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pszSrc, rc, offError, &ErrInfo.Core);
+ continue;
+ }
+
+ /*
+ * Regular file.
+ */
+ if (RTFS_IS_FILE(SrcObjInfo.Attr.fMode))
+ {
+ /* Open source. */
+ RTVFSFILE hVfsSrc;
+ rc = RTVfsChainOpenFile(pszSrc, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ &hVfsSrc, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ /* Make destination name if necessary and open destination.
+ Note! RTFILE_O_READ needed for VFS chains. */
+ char szDstPath[RTPATH_MAX];
+ const char *pszDst = pOpts->pszDestination;
+ if (RTFS_IS_DIRECTORY(DstObjInfo.Attr.fMode))
+ {
+ rc = RTPathJoin(szDstPath, sizeof(szDstPath), pszDst, RTPathFilename(pszSrc));
+ pszDst = szDstPath;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsDst;
+ uint64_t fDstFlags = (pOpts->fNoReplaceNorTruncate ? RTFILE_O_OPEN_CREATE : RTFILE_O_CREATE_REPLACE)
+ | RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | (0666 << RTFILE_O_CREATE_MODE_SHIFT);
+ rc = RTVfsChainOpenFile(pszDst, fDstFlags, &hVfsDst, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ /* Copy the bytes. */
+ RTVFSIOSTREAM hVfsIosSrc = RTVfsFileToIoStream(hVfsSrc);
+ RTVFSIOSTREAM hVfsIosDst = RTVfsFileToIoStream(hVfsDst);
+
+ rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, 0);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->fVerbose)
+ RTPrintf("'%s' -> '%s'\n", pszSrc, pszDst);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("RTVfsUtilPumpIoStreams failed for '%s' -> '%s': %Rrc",
+ pszSrc, pszDst, rc);
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ RTVfsFileRelease(hVfsDst);
+ }
+ else
+ rcExit = RTVfsChainMsgErrorExitFailure("RTVfsChainOpenFile", pszDst, rc, offError, &ErrInfo.Core);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("Destination path too long for source #%u (%Rrc): %s", iSrc, pszSrc, rc);
+ RTVfsFileRelease(hVfsSrc);
+ }
+ else
+ rcExit = RTVfsChainMsgErrorExitFailure("RTVfsChainOpenFile", pszSrc, rc, offError, &ErrInfo.Core);
+ }
+ /*
+ * Copying a directory requires the -R option to be active.
+ */
+ else if (RTFS_IS_DIRECTORY(SrcObjInfo.Attr.fMode))
+ {
+ if (pOpts->fRecursive)
+ {
+ /** @todo recursive copy */
+ rcExit = RTMsgErrorExitFailure("Recursion not implemented yet!");
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("Source #%u is a directory: %s", iSrc + 1, pszSrc);
+ }
+ /*
+ * We currently don't support copying any other file types.
+ */
+ else
+ rcExit = RTMsgErrorExitFailure("Source #%u neither a file nor a directory: %s", iSrc + 1, pszSrc);
+ }
+ return rcExit;
+}
+
+
+/**
+ * A /bin/cp clone.
+ *
+ * @returns Program exit code.
+ *
+ * @param cArgs The number of arguments.
+ * @param papszArgs The argument vector. (Note that this may be
+ * reordered, so the memory must be writable.)
+ */
+RTEXITCODE RTCmdCp(unsigned cArgs, char **papszArgs)
+{
+
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--archive", 'a', RTGETOPT_REQ_NOTHING },
+ { "--backup", 'B', RTGETOPT_REQ_STRING },
+ { "", 'b', RTGETOPT_REQ_NOTHING },
+ { "--copy-contents", 1024, RTGETOPT_REQ_NOTHING },
+ { "", 'd', RTGETOPT_REQ_NOTHING },
+ { "--no-dereference", 'P', RTGETOPT_REQ_NOTHING },
+ { "--force", 'f', RTGETOPT_REQ_NOTHING },
+ { "", 'H', RTGETOPT_REQ_NOTHING },
+ { "--link", 'l', RTGETOPT_REQ_NOTHING },
+ { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
+ { "", 'p', RTGETOPT_REQ_NOTHING },
+ { "--preserve", 1026, RTGETOPT_REQ_STRING },
+ { "--no-preserve", 1027, RTGETOPT_REQ_STRING },
+ { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
+ { "--remove-destination", 1028, RTGETOPT_REQ_NOTHING },
+ { "--reply", 1029, RTGETOPT_REQ_STRING },
+ { "--sparse", 1030, RTGETOPT_REQ_STRING },
+ { "--strip-trailing-slashes", 1031, RTGETOPT_REQ_NOTHING },
+ { "--symbolic-links", 's', RTGETOPT_REQ_NOTHING },
+ { "--suffix", 'S', RTGETOPT_REQ_STRING },
+ { "--target-directory", 't', RTGETOPT_REQ_STRING },
+ { "--no-target-directory", 'T', RTGETOPT_REQ_NOTHING },
+ { "--update", 'u', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--one-file-system", 'x', RTGETOPT_REQ_NOTHING },
+ { "--no-replace-nor-trucate", 1032, RTGETOPT_REQ_NOTHING },
+ };
+
+ RTCMDCPOPTS Opts;
+ Opts.fVerbose = false;
+ Opts.fFollowCommandLineSymlinks = false;
+ Opts.fRecursive = false;
+ Opts.fOneFileSystem = false;
+ Opts.fNoReplaceNorTruncate = false;
+ Opts.pszDestination = NULL;
+ Opts.cSources = 0;
+ Opts.papszSources = (const char **)RTMemAllocZ(sizeof(const char *) * (cArgs + 2));
+ AssertReturn(Opts.papszSources, RTEXITCODE_FAILURE);
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_SUCCESS(rc))
+ {
+ bool fContinue = true;
+ do
+ {
+ RTGETOPTUNION ValueUnion;
+ int chOpt = RTGetOpt(&GetState, &ValueUnion);
+ switch (chOpt)
+ {
+ case 0:
+ if (Opts.pszDestination == NULL && Opts.cSources == 0)
+ rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing source and destination");
+ else if (Opts.pszDestination == NULL && Opts.cSources == 1)
+ rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing destination");
+ else if (Opts.pszDestination != NULL && Opts.cSources == 0)
+ rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing source");
+ else
+ {
+ if (Opts.pszDestination == NULL && Opts.cSources > 0)
+ Opts.pszDestination = Opts.papszSources[--Opts.cSources];
+ Assert(Opts.cSources > 0);
+ rcExit = rtCmdCpDoIt(&Opts);
+ }
+ fContinue = false;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ Assert(Opts.cSources < cArgs);
+ Opts.papszSources[Opts.cSources++] = ValueUnion.psz;
+ break;
+
+ case 'H':
+ Opts.fFollowCommandLineSymlinks = true;
+ break;
+
+ case 'R':
+ Opts.fRecursive = true;
+ break;
+ case 'x':
+ Opts.fOneFileSystem = true;
+ break;
+
+ case 'v':
+ Opts.fVerbose = true;
+ break;
+
+ case 1032:
+ Opts.fNoReplaceNorTruncate = true;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: to be written\nOption dump:\n");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
+ RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
+ fContinue = false;
+ break;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ fContinue = false;
+ break;
+
+ default:
+ rcExit = RTGetOptPrintError(chOpt, &ValueUnion);
+ fContinue = false;
+ break;
+ }
+ } while (fContinue);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc);
+ RTMemFree(Opts.papszSources);
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTCmdCp(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTDbgSymCache.cpp b/src/VBox/Runtime/tools/RTDbgSymCache.cpp
new file mode 100644
index 00000000..961cfcac
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTDbgSymCache.cpp
@@ -0,0 +1,1858 @@
+/* $Id: RTDbgSymCache.cpp $ */
+/** @file
+ * IPRT - Debug Symbol Cache Utility.
+ */
+
+/*
+ * Copyright (C) 2013-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/zip.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/dbg.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/formats/mach-o.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/ldr.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Cache file type.
+ */
+typedef enum RTDBGSYMCACHEFILETYPE
+{
+ RTDBGSYMCACHEFILETYPE_INVALID,
+ RTDBGSYMCACHEFILETYPE_DIR,
+ RTDBGSYMCACHEFILETYPE_DIR_FILTER,
+ RTDBGSYMCACHEFILETYPE_DEBUG_FILE,
+ RTDBGSYMCACHEFILETYPE_IMAGE_FILE,
+ RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE,
+ RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE,
+ RTDBGSYMCACHEFILETYPE_IGNORE
+} RTDBGSYMCACHEFILETYPE;
+
+
+/**
+ * Configuration for the 'add' command.
+ */
+typedef struct RTDBGSYMCACHEADDCFG
+{
+ bool fRecursive;
+ bool fOverwriteOnConflict;
+ const char *pszFilter;
+ const char *pszCache;
+} RTDBGSYMCACHEADDCFG;
+/** Pointer to a read only 'add' config. */
+typedef RTDBGSYMCACHEADDCFG const *PCRTDBGSYMCACHEADDCFG;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Bundle suffixes. */
+static const char * const g_apszBundleSuffixes[] =
+{
+ ".kext",
+ ".app",
+ ".framework", /** @todo framework is different. */
+ ".component",
+ ".action",
+ ".caction",
+ ".bundle",
+ ".sourcebundle",
+ ".plugin",
+ ".ppp",
+ ".menu",
+ ".monitorpanel",
+ ".scripting",
+ ".prefPane",
+ ".qlgenerator",
+ ".brailledriver",
+ ".saver",
+ ".SpeechVoice",
+ ".SpeechRecognizer",
+ ".SpeechSynthesizer",
+ ".mdimporter",
+ ".spreporter",
+ ".xpc",
+ NULL
+};
+
+/** Debug bundle suffixes. (Same as above + .dSYM) */
+static const char * const g_apszDSymBundleSuffixes[] =
+{
+ ".kext.dSYM",
+ ".app.dSYM",
+ ".framework.dSYM",
+ ".component.dSYM",
+ ".action.dSYM",
+ ".caction.dSYM",
+ ".bundle.dSYM",
+ ".sourcebundle.dSYM",
+ ".menu.dSYM",
+ ".plugin.dSYM",
+ ".ppp.dSYM",
+ ".monitorpanel.dSYM",
+ ".scripting.dSYM",
+ ".prefPane.dSYM",
+ ".qlgenerator.dSYM",
+ ".brailledriver.dSYM",
+ ".saver.dSYM",
+ ".SpeechVoice.dSYM",
+ ".SpeechRecognizer.dSYM",
+ ".SpeechSynthesizer.dSYM",
+ ".mdimporter.dSYM",
+ ".spreporter.dSYM",
+ ".xpc.dSYM",
+ ".dSYM",
+ NULL
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtDbgSymCacheAddDirWorker(char *pszPath, size_t cchPath, PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Verbositity level. */
+static uint32_t g_iLogLevel = 99;
+
+
+/**
+ * Display the version of the cache program.
+ *
+ * @returns exit code.
+ */
+static RTEXITCODE rtDbgSymCacheVersion(void)
+{
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Shows the usage of the cache program.
+ *
+ * @returns Exit code.
+ * @param pszArg0 Program name.
+ * @param pszCommand Command selector, NULL if all.
+ */
+static RTEXITCODE rtDbgSymCacheUsage(const char *pszArg0, const char *pszCommand)
+{
+ if (!pszCommand || !strcmp(pszCommand, "add"))
+ RTPrintf("Usage: %s add [-Rno] <cache-root-dir> <file1[=cache-name]> [fileN..]\n"
+ "\n"
+ "Options:\n"
+ " -R, --recursive\n"
+ " Process directory arguments recursively.\n"
+ " -n, --no-recursive\n"
+ " No recursion. (default)\n"
+ " -o, --overwrite-on-conflict\n"
+ " Overwrite existing cache entry.\n"
+ , RTPathFilename(pszArg0));
+
+
+ if (!pszCommand || !strcmp(pszCommand, "get"))
+ RTPrintf("Usage: %s get <query-options> <cache-options> [--output|-o <path>]\n"
+ "\n"
+ "Query Options:\n"
+ " --for-exe[cutable] <path>\n"
+ " Get debug file for the given executable.\n"
+ " --dwo, --dwarf, --dwarf-external\n"
+ " Get external DWARF debug file. Needs --name and --dwo-crc32.\n"
+ " --dsym\n"
+ " Get DWARF debug file from .dSYM bundle. Needs --uuid or --name.\n"
+ " --dbg\n"
+ " Get NT DBG debug file. Needs --name, --timestamp and --size.\n"
+ " --pdb20\n"
+ " Get PDB 2.0 debug file. Needs --name, --timestamp, --size\n"
+ " and --pdb-age (if non-zero).\n"
+ " --pdb70\n"
+ " Get PDB 7.0 debug file. Needs --name, --uuid, and --pdb-age\n"
+ " (if non-zero).\n"
+ " --macho\n"
+ " Get Mach-O image file. Needs --uuid or --name.\n"
+ " --pe\n"
+ " Get PE image file. Needs --name, --timestamp and --size.\n"
+ " --timestamp, --ts, -t <timestamp>\n"
+ " The timestamp (32-bit) for the file to get. Used with --dbg, --pdb20\n"
+ " and --pe.\n"
+ " --uuid, -u, <uuid>\n"
+ " The UUID for the file to get. Used with --dsym, --pdb70 and --macho\n"
+ " --image-size, --size, -z <size>\n"
+ " The image size (32-bit) for the file to get. Used with --dbg,\n"
+ " --pdb20, --pdb70 and --pe.\n"
+ " --pdb-age, -a <age>\n"
+ " The PDB age (32-bit) for the file to get. Used with --pdb20 and --pdb70.\n"
+ " --dwo-crc32, -c <crc32>\n"
+ " The CRC32 for the file to get. Used with --dwo.\n"
+ " --name, -n <name>\n"
+ " The name (in the cache) of the file to get.\n"
+ "\n"
+ "Debug Cache Options:\n"
+ " --sym-path, -s <path>\n"
+ " Adds the path to the debug configuration, NT style with 'srv*' and\n"
+ " 'cache*' prefixes as well as our own 'rec*' and 'norec*' recursion\n"
+ " prefixes.\n"
+ " --env-prefix, -p <prefix>\n"
+ " The enviornment variable prefix, default is 'IPRT_' making the\n"
+ " symbol path variable 'IPRT_PATH'.\n"
+ " --use-native-paths (default), --no-native-paths\n"
+ " Pick up native symbol paths from the environment.\n"
+ "\n"
+ "Output Options:\n"
+ " --output, -o <path>\n"
+ " The output filename or directory. Directories must end with a\n"
+ " path separator. The default filename that in the cache.\n"
+ "\n"
+ "This is handy for triggering downloading of symbol files from a server. Say\n"
+ "you have the executable but want the corrsponding PDB or .dSYM file:\n"
+ " %s get --for-executable VBoxRT.dll\n"
+ " %s get --for-executable VBoxRT.dylib\n"
+ " "
+ , RTPathFilename(pszArg0), RTPathFilename(pszArg0), RTPathFilename(pszArg0));
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNRTDBGCFGLOG}
+ */
+static DECLCALLBACK(void) rtDbgSymCacheLogCallback(RTDBGCFG hDbgCfg, uint32_t iLevel, const char *pszMsg, void *pvUser)
+{
+ RT_NOREF(hDbgCfg, pvUser);
+ if (iLevel <= g_iLogLevel)
+ {
+ size_t cchMsg = strlen(pszMsg);
+ if (cchMsg > 0 && pszMsg[cchMsg - 1] == '\n')
+ RTMsgInfo("[%u] %s", iLevel, pszMsg);
+ else if (cchMsg > 0)
+ RTMsgInfo("[%u] %s\n", iLevel, pszMsg);
+ }
+}
+
+
+/**
+ * Creates a UUID mapping for the file.
+ *
+ * @returns IPRT status code.
+ * @param pszCacheFile The path to the file in the cache.
+ * @param pFileUuid The UUID of the file.
+ * @param pszUuidMapDir The UUID map subdirectory in the cache, if this is
+ * wanted, otherwise NULL.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddCreateUuidMapping(const char *pszCacheFile, PRTUUID pFileUuid,
+ const char *pszUuidMapDir, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * Create the UUID map entry first, deep.
+ */
+ char szMapPath[RTPATH_MAX];
+ int rc = RTPathJoin(szMapPath, sizeof(szMapPath) - sizeof("/xxxx/yyyy/xxxx/yyyy/xxxx/zzzzzzzzzzzz") + 1,
+ pCfg->pszCache, pszUuidMapDir);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error constructing UUID map path (RTPathJoin): %Rrc", rc);
+
+ size_t cch = strlen(szMapPath);
+ szMapPath[cch] = '-';
+
+ rc = RTUuidToStr(pFileUuid, &szMapPath[cch + 2], sizeof(szMapPath) - cch);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error constructing UUID map path (RTUuidToStr): %Rrc", rc);
+
+ /* Uppercase the whole lot. */
+ RTStrToUpper(&szMapPath[cch + 2]);
+
+ /* Split the first dword in two. */
+ szMapPath[cch + 1] = szMapPath[cch + 2];
+ szMapPath[cch + 2] = szMapPath[cch + 3];
+ szMapPath[cch + 3] = szMapPath[cch + 4];
+ szMapPath[cch + 4] = szMapPath[cch + 5];
+ szMapPath[cch + 5] = '-';
+
+ /*
+ * Create the directories in the path.
+ */
+ for (unsigned i = 0; i < 6; i++, cch += 5)
+ {
+ Assert(szMapPath[cch] == '-');
+ szMapPath[cch] = '\0';
+ if (!RTDirExists(szMapPath))
+ {
+ rc = RTDirCreate(szMapPath, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "RTDirCreate failed on '%s' (UUID map path): %Rrc", szMapPath, rc);
+ }
+ szMapPath[cch] = RTPATH_SLASH;
+ }
+ cch -= 5;
+
+ /*
+ * Calculate a relative path from there to the actual file.
+ */
+ char szLinkTarget[RTPATH_MAX];
+ szMapPath[cch] = '\0';
+ rc = RTPathCalcRelative(szLinkTarget, sizeof(szLinkTarget), szMapPath, false /*fFromFile*/, pszCacheFile);
+ szMapPath[cch] = RTPATH_SLASH;
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Failed to calculate relative path from '%s' to '%s': %Rrc", szMapPath, pszCacheFile, rc);
+
+ /*
+ * If there is already a link there, check if it matches or whether
+ * perhaps it's target doesn't exist.
+ */
+ RTFSOBJINFO ObjInfo;
+ rc = RTPathQueryInfoEx(szMapPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
+ {
+ rc = RTPathQueryInfoEx(szMapPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszCurTarget = NULL;
+ rc = RTSymlinkReadA(szMapPath, &pszCurTarget);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "UUID map: failed to read existing symlink '%s': %Rrc", szMapPath, rc);
+ if (RTPathCompare(pszCurTarget, szLinkTarget) == 0)
+ RTMsgInfo("UUID map: existing link '%s' has the same target ('%s').", szMapPath, pszCurTarget);
+ else
+ {
+ RTMsgError("UUID map: Existing mapping '%s' pointing to '%s' insted of '%s'",
+ szMapPath, pszCurTarget, szLinkTarget);
+ rc = VERR_ALREADY_EXISTS;
+ }
+ RTStrFree(pszCurTarget);
+ return rc;
+ }
+ else
+ RTMsgInfo("UUID map: replacing dangling link '%s'", szMapPath);
+ RTSymlinkDelete(szMapPath, 0 /*fFlags*/);
+ }
+ else if (RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ return RTMsgErrorRc(VERR_IS_A_FILE,
+ "UUID map: found file at '%s', expect symbolic link or nothing.", szMapPath);
+ else if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ return RTMsgErrorRc(VERR_IS_A_DIRECTORY,
+ "UUID map: found directory at '%s', expect symbolic link or nothing.", szMapPath);
+ else
+ return RTMsgErrorRc(VERR_NOT_SYMLINK,
+ "UUID map: Expected symbolic link or nothing at '%s', found: fMode=%#x",
+ szMapPath, ObjInfo.Attr.fMode);
+ }
+
+ /*
+ * Create the symbolic link.
+ */
+ rc = RTSymlinkCreate(szMapPath, szLinkTarget, RTSYMLINKTYPE_FILE, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Failed to create UUID map symlink '%s' to '%s': %Rrc", szMapPath, szLinkTarget, rc);
+ RTMsgInfo("UUID map: %s => %s", szMapPath, szLinkTarget);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Adds a file to the cache.
+ *
+ * @returns IPRT status code.
+ * @param pszSrcPath Path to the source file.
+ * @param pszDstName The name of the destionation file (no path stuff).
+ * @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack.
+ * @param pszDstSubDir The subdirectory to file it under. This is the
+ * stringification of a relatively unique identifier of
+ * the file in question.
+ * @param pAddToUuidMap Optional file UUID that is used to create a UUID map
+ * entry.
+ * @param pszUuidMapDir The UUID map subdirectory in the cache, if this is
+ * wanted, otherwise NULL.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddOneFile(const char *pszSrcPath, const char *pszDstName, const char *pszExtraStuff,
+ const char *pszDstSubDir, PRTUUID pAddToUuidMap, const char *pszUuidMapDir,
+ PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * Build and create the destination path, step by step.
+ */
+ char szDstPath[RTPATH_MAX];
+ int rc = RTPathJoin(szDstPath, sizeof(szDstPath), pCfg->pszCache, pszDstName);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc);
+
+ if (!RTDirExists(szDstPath))
+ {
+ rc = RTDirCreate(szDstPath, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error creating '%s': %Rrc", szDstPath, rc);
+ }
+
+ rc = RTPathAppend(szDstPath, sizeof(szDstPath), pszDstSubDir);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc);
+
+ if (!RTDirExists(szDstPath))
+ {
+ rc = RTDirCreate(szDstPath, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error creating '%s': %Rrc", szDstPath, rc);
+ }
+
+ rc = RTPathAppend(szDstPath, sizeof(szDstPath), pszDstName);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc);
+ if (pszExtraStuff)
+ {
+ rc = RTStrCat(szDstPath, sizeof(szDstPath), pszExtraStuff);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc);
+ }
+
+ /*
+ * If the file exists, we compare the two and throws an error if the doesn't match.
+ */
+ if (RTPathExists(szDstPath))
+ {
+ rc = RTFileCompare(pszSrcPath, szDstPath);
+ if (RT_SUCCESS(rc))
+ {
+ RTMsgInfo("%s is already in the cache.", pszSrcPath);
+ if (pAddToUuidMap && pszUuidMapDir)
+ return rtDbgSymCacheAddCreateUuidMapping(szDstPath, pAddToUuidMap, pszUuidMapDir, pCfg);
+ return VINF_SUCCESS;
+ }
+ if (rc == VERR_NOT_EQUAL)
+ RTMsgInfo("Cache conflict with existing entry '%s' when inserting '%s'.", szDstPath, pszSrcPath);
+ else
+ RTMsgInfo("Error comparing '%s' with '%s': %Rrc", pszSrcPath, szDstPath, rc);
+ if (!pCfg->fOverwriteOnConflict)
+ return rc;
+ }
+
+ /*
+ * The file doesn't exist or we should overwrite it,
+ */
+ RTMsgInfo("Copying '%s' to '%s'...", pszSrcPath, szDstPath);
+ rc = RTFileCopy(pszSrcPath, szDstPath);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error copying '%s' to '%s': %Rrc", pszSrcPath, szDstPath, rc);
+ if (pAddToUuidMap && pszUuidMapDir)
+ return rtDbgSymCacheAddCreateUuidMapping(szDstPath, pAddToUuidMap, pszUuidMapDir, pCfg);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker that add the image file to the right place.
+ *
+ * @returns IPRT status code.
+ * @param pszPath Path to the image file.
+ * @param pszDstName Add to the cache under this name. Typically the
+ * filename part of @a pszPath.
+ * @param pCfg Configuration data.
+ * @param hLdrMod Image handle.
+ * @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack.
+ * @param pszUuidMapDir Optional UUID map cache directory if the image
+ * should be mapped by UUID.
+ * The map is a Mac OS X debug feature supported by
+ * the two native debuggers gdb and lldb. Look for
+ * descriptions of DBGFileMappedPaths in the
+ * com.apple.DebugSymbols in the user defaults.
+ */
+static int rtDbgSymCacheAddImageFileWorker(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg,
+ RTLDRMOD hLdrMod, const char *pszExtrSuff, const char *pszUuidMapDir)
+{
+ /*
+ * Determine which subdirectory to put the files in.
+ */
+ RTUUID Uuid;
+ PRTUUID pUuid = NULL;
+ int rc;
+ char szSubDir[48];
+ RTLDRFMT enmFmt = RTLdrGetFormat(hLdrMod);
+ switch (enmFmt)
+ {
+ case RTLDRFMT_MACHO:
+ {
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_UUID, &Uuid, sizeof(Uuid));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error quering image UUID from image '%s': %Rrc", pszPath, rc);
+
+ rc = RTUuidToStr(&Uuid, szSubDir, sizeof(szSubDir));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error convering UUID for image '%s' to string: %Rrc", pszPath, rc);
+ pUuid = &Uuid;
+ break;
+ }
+
+ case RTLDRFMT_PE:
+ {
+ uint32_t uTimestamp;
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp, sizeof(uTimestamp));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error quering timestamp from image '%s': %Rrc", pszPath, rc);
+
+ size_t cbImage = RTLdrSize(hLdrMod);
+ if (cbImage == ~(size_t)0)
+ return RTMsgErrorRc(rc, "Error quering size of image '%s': %Rrc", pszPath, rc);
+
+ RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uTimestamp, cbImage);
+ break;
+ }
+
+ case RTLDRFMT_AOUT:
+ return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Caching of a.out image has not yet been implemented: %s", pszPath);
+ case RTLDRFMT_ELF:
+ return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Caching of ELF image has not yet been implemented: %s", pszPath);
+ case RTLDRFMT_LX:
+ return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Caching of LX image has not yet been implemented: %s", pszPath);
+ default:
+ return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Unknown loader format for '%s': %d", pszPath, enmFmt);
+ }
+
+ /*
+ * Now add it.
+ */
+ return rtDbgSymCacheAddOneFile(pszPath, pszDstName, pszExtrSuff, szSubDir, pUuid, pszUuidMapDir, pCfg);
+}
+
+
+/**
+ * Adds what we think is an image file to the cache.
+ *
+ * @returns IPRT status code.
+ * @param pszPath Path to the image file.
+ * @param pszDstName Add to the cache under this name. Typically the
+ * filename part of @a pszPath.
+ * @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack.
+ * @param pszUuidMapDir The UUID map subdirectory in the cache, if this is
+ * wanted, otherwise NULL.
+ * @param pCfg Configuration data.
+ */
+static int rtDbgSymCacheAddImageFile(const char *pszPath, const char *pszDstName, const char *pszExtraSuff,
+ const char *pszUuidMapDir, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ RTERRINFOSTATIC ErrInfo;
+
+ /*
+ * Use the loader to open the alleged image file. We need to open it with
+ * arch set to amd64 and x86_32 in order to handle FAT images from the mac
+ * guys (we should actually enumerate archs, but that's currently not
+ * implemented nor necessary for our current use).
+ */
+ /* Open it as AMD64. */
+ RTLDRMOD hLdrMod64;
+ int rc = RTLdrOpenEx(pszPath, RTLDR_O_FOR_DEBUG, RTLDRARCH_AMD64, &hLdrMod64, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_LDR_ARCH_MISMATCH)
+ {
+ if (rc != VERR_INVALID_EXE_SIGNATURE)
+ return RTMsgErrorRc(rc, "RTLdrOpen failed opening '%s' [arch=amd64]: %Rrc%s%s", pszPath, rc,
+ RTErrInfoIsSet(&ErrInfo.Core) ? " - " : "",
+ RTErrInfoIsSet(&ErrInfo.Core) ? ErrInfo.Core.pszMsg : "");
+
+ RTMsgInfo("Skipping '%s', no a recognizable image file...", pszPath);
+ return VINF_SUCCESS;
+ }
+ hLdrMod64 = NIL_RTLDRMOD;
+ }
+
+ /* Open it as X86. */
+ RTLDRMOD hLdrMod32;
+ rc = RTLdrOpenEx(pszPath, RTLDR_O_FOR_DEBUG, RTLDRARCH_X86_32, &hLdrMod32, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_LDR_ARCH_MISMATCH)
+ {
+ RTLdrClose(hLdrMod64);
+ return RTMsgErrorRc(rc, "RTLdrOpen failed opening '%s' [arch=x86]: %Rrc%s%s", pszPath, rc,
+ RTErrInfoIsSet(&ErrInfo.Core) ? " - " : "",
+ RTErrInfoIsSet(&ErrInfo.Core) ? ErrInfo.Core.pszMsg : "");
+ }
+ hLdrMod32 = NIL_RTLDRMOD;
+ }
+
+ /*
+ * Add the file.
+ */
+ if (hLdrMod32 == NIL_RTLDRMOD)
+ rc = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, pCfg, hLdrMod64, pszExtraSuff, pszUuidMapDir);
+ else if (hLdrMod64 == NIL_RTLDRMOD)
+ rc = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, pCfg, hLdrMod32, pszExtraSuff, pszUuidMapDir);
+ else
+ {
+ /*
+ * Do we need to add it once or twice?
+ */
+ RTLDRFMT enmFmt = RTLdrGetFormat(hLdrMod32);
+ bool fSame = enmFmt == RTLdrGetFormat(hLdrMod64);
+ if (fSame && enmFmt == RTLDRFMT_MACHO)
+ {
+ RTUUID Uuid32, Uuid64;
+ int rc32 = RTLdrQueryProp(hLdrMod32, RTLDRPROP_UUID, &Uuid32, sizeof(Uuid32));
+ int rc64 = RTLdrQueryProp(hLdrMod64, RTLDRPROP_UUID, &Uuid64, sizeof(Uuid64));
+ fSame = RT_SUCCESS(rc32) == RT_SUCCESS(rc64);
+ if (fSame && RT_SUCCESS(rc32))
+ fSame = RTUuidCompare(&Uuid32, &Uuid64) == 0;
+ }
+ else if (fSame && enmFmt == RTLDRFMT_PE)
+ {
+ fSame = RTLdrSize(hLdrMod32) == RTLdrSize(hLdrMod64);
+ if (fSame)
+ {
+ uint32_t uTimestamp32, uTimestamp64;
+ int rc32 = RTLdrQueryProp(hLdrMod32, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp32, sizeof(uTimestamp32));
+ int rc64 = RTLdrQueryProp(hLdrMod64, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp64, sizeof(uTimestamp64));
+ fSame = RT_SUCCESS(rc32) == RT_SUCCESS(rc64);
+ if (fSame && RT_SUCCESS(rc32))
+ fSame = uTimestamp32 == uTimestamp64;
+ }
+ }
+
+ rc = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, pCfg, hLdrMod64, pszExtraSuff, pszUuidMapDir);
+ if (!fSame)
+ {
+ /** @todo should symlink or hardlink this second copy. */
+ int rc2 = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, pCfg, hLdrMod32, pszExtraSuff, pszUuidMapDir);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+
+ RTLdrClose(hLdrMod32);
+ RTLdrClose(hLdrMod64);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtDbgSymCacheAddDebugFile that adds a Mach-O debug file to the
+ * cache.
+ *
+ * @returns IPRT status code
+ * @param pszPath The path to the PDB file.
+ * @param pszDstName Add to the cache under this name. Typically the
+ * filename part of @a pszPath.
+ * @param pCfg The configuration.
+ * @param hFile Handle to the file.
+ */
+static int rtDbgSymCacheAddDebugMachO(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /* This shouldn't happen, figure out what to do if it does. */
+ RT_NOREF(pCfg, pszDstName);
+ return RTMsgErrorRc(VERR_NOT_IMPLEMENTED,
+ "'%s' is an OS X image file, did you point me to a file inside a .dSYM or .sym file?",
+ pszPath);
+}
+
+
+/**
+ * Worker for rtDbgSymCacheAddDebugFile that adds PDBs to the cace.
+ *
+ * @returns IPRT status code
+ * @param pszPath The path to the PDB file.
+ * @param pszDstName Add to the cache under this name. Typically the
+ * filename part of @a pszPath.
+ * @param pCfg The configuration.
+ * @param hFile Handle to the file.
+ */
+static int rtDbgSymCacheAddDebugPdb(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg, RTFILE hFile)
+{
+ RT_NOREF(pCfg, hFile, pszDstName);
+ return RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "PDB support not implemented: '%s'", pszPath);
+}
+
+
+/**
+ * Adds a debug file to the cache.
+ *
+ * @returns IPRT status code
+ * @param pszPath The path to the debug file in question.
+ * @param pszDstName Add to the cache under this name. Typically the
+ * filename part of @a pszPath.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddDebugFile(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * Need to extract an identifier of sorts here in order to put them in
+ * the right place in the cache. Currently only implemnted for Mach-O
+ * files since these use executable containers.
+ *
+ * We take a look at the file header in hope to figure out what to do
+ * with the file.
+ */
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszPath, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error opening '%s': %Rrc", pszPath, rc);
+
+ union
+ {
+ uint64_t au64[16];
+ uint32_t au32[16];
+ uint16_t au16[32];
+ uint8_t ab[64];
+ } uBuf;
+ rc = RTFileRead(hFile, &uBuf, sizeof(uBuf), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Look for magics and call workers.
+ */
+ if (!memcmp(uBuf.ab, RT_STR_TUPLE("Microsoft C/C++ MSF 7.00")))
+ rc = rtDbgSymCacheAddDebugPdb(pszPath, pszDstName, pCfg, hFile);
+ else if ( uBuf.au32[0] == IMAGE_FAT_SIGNATURE
+ || uBuf.au32[0] == IMAGE_FAT_SIGNATURE_OE
+ || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE
+ || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE
+ || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE_OE
+ || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE_OE)
+ rc = rtDbgSymCacheAddDebugMachO(pszPath, pszDstName, pCfg);
+ else
+ rc = RTMsgErrorRc(VERR_INVALID_MAGIC, "Unsupported debug file '%s' magic: %#010x", pszPath, uBuf.au32[0]);
+ }
+ else
+ rc = RTMsgErrorRc(rc, "Error reading '%s': %Rrc", pszPath, rc);
+
+ /* close the file. */
+ int rc2 = RTFileClose(hFile);
+ if (RT_FAILURE(rc2))
+ {
+ RTMsgError("Error closing '%s': %Rrc", pszPath, rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ return rc;
+}
+
+
+/**
+ * Constructs the path to the file instide the bundle that we're keen on.
+ *
+ * @returns IPRT status code.
+ * @param pszPath Path to the bundle on input, on successful
+ * return it's the path to the desired file. This
+ * a RTPATH_MAX size buffer.
+ * @param cchPath The length of the path up to the bundle name.
+ * @param cchName The length of the bundle name.
+ * @param pszSubDir The bundle subdirectory the file lives in.
+ * @param papszSuffixes Pointer to an array of bundle suffixes.
+ */
+static int rtDbgSymCacheConstructBundlePath(char *pszPath, size_t cchPath, size_t cchName, const char *pszSubDir,
+ const char * const *papszSuffixes)
+{
+ /*
+ * Calc the name without the bundle extension.
+ */
+ size_t const cchOrgName = cchName;
+ const char *pszEnd = &pszPath[cchPath + cchName];
+ for (unsigned i = 0; papszSuffixes[i]; i++)
+ {
+ Assert(papszSuffixes[i][0] == '.');
+ size_t cchSuff = strlen(papszSuffixes[i]);
+ if ( cchSuff < cchName
+ && !memcmp(&pszEnd[-(ssize_t)cchSuff], papszSuffixes[i], cchSuff))
+ {
+ cchName -= cchSuff;
+ break;
+ }
+ }
+
+ /*
+ * Check the immediate directory first, in case it's layed out like
+ * IOPCIFamily.kext.
+ */
+ int rc = RTPathAppendEx(pszPath, RTPATH_MAX, &pszPath[cchPath], cchName, RTPATH_STR_F_STYLE_HOST);
+ if (RT_FAILURE(rc) || !RTFileExists(pszPath))
+ {
+ /*
+ * Not there, ok then try the given subdirectory + name.
+ */
+ pszPath[cchPath + cchOrgName] = '\0';
+ rc = RTPathAppend(pszPath, RTPATH_MAX, pszSubDir);
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppendEx(pszPath, RTPATH_MAX, &pszPath[cchPath], cchName, RTPATH_STR_F_STYLE_HOST);
+ if (RT_FAILURE(rc))
+ {
+ pszPath[cchPath + cchOrgName] = '\0';
+ return RTMsgErrorRc(rc, "Error constructing image bundle path for '%s': %Rrc", pszPath, rc);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Adds a image bundle of some sort.
+ *
+ * @returns IPRT status code.
+ * @param pszPath Path to the bundle. This a RTPATH_MAX size
+ * buffer that we can write to when creating the
+ * path to the file inside the bundle that we're
+ * interested in.
+ * @param cchPath The length of the path up to the bundle name.
+ * @param cchName The length of the bundle name.
+ * @param pszDstName Add to the cache under this name, NULL if not
+ * specified.
+ * @param pDirEntry The directory entry buffer, for handling bundle
+ * within bundle recursion.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddImageBundle(char *pszPath, size_t cchPath, size_t cchName, const char *pszDstName,
+ PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * Assuming these are kexts or simple applications, we only add the image
+ * file itself to the cache. No Info.plist or other files.
+ */
+ /** @todo consider looking for Frameworks and handling framework bundles. */
+ int rc = rtDbgSymCacheConstructBundlePath(pszPath, cchPath, cchName, "Contents/MacOS/", g_apszBundleSuffixes);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pszDstName)
+ pszDstName = RTPathFilename(pszPath);
+ rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, NULL, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, pCfg);
+ }
+
+ /*
+ * Look for plugins and other sub-bundles.
+ */
+ if (pCfg->fRecursive)
+ {
+ static char const * const s_apszSubBundleDirs[] =
+ {
+ "Contents/Plugins/",
+ /** @todo Frameworks ++ */
+ };
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_apszSubBundleDirs); i++)
+ {
+ pszPath[cchPath + cchName] = '\0';
+ int rc2 = RTPathAppend(pszPath, RTPATH_MAX - 1, s_apszSubBundleDirs[i]);
+ if (RT_SUCCESS(rc2))
+ {
+ if (RTDirExists(pszPath))
+ {
+ size_t cchPath2 = strlen(pszPath);
+ if (!RTPATH_IS_SLASH(pszPath[cchPath2 - 1]))
+ {
+ pszPath[cchPath2++] = RTPATH_SLASH;
+ pszPath[cchPath2] = '\0';
+ }
+ rc2 = rtDbgSymCacheAddDirWorker(pszPath, cchPath2, pDirEntry, pCfg);
+ }
+ }
+ else
+ {
+ pszPath[cchPath + cchName] = '\0';
+ RTMsgError("Error constructing bundle subdir path for '%s' + '%s': %Rrc", pszPath, s_apszSubBundleDirs[i], rc);
+ }
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Adds a debug bundle.
+ *
+ * @returns IPRT status code.
+ * @param pszPath Path to the bundle. This a RTPATH_MAX size
+ * buffer that we can write to when creating the
+ * path to the file inside the bundle that we're
+ * interested in.
+ * @param cchPath The length of the path up to the bundle name.
+ * @param cchName The length of the bundle name.
+ * @param pszDstName Add to the cache under this name, NULL if not
+ * specified.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddDebugBundle(char *pszPath, size_t cchPath, size_t cchName, const char *pszDstName,
+ PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * The current policy is not to add the whole .dSYM (or .sym) bundle, but
+ * rather just the dwarf image instide it. The <UUID>.plist and Info.plist
+ * files generally doesn't contain much extra information that's really
+ * necessary, I hope. At least this is what the uuidmap example in the
+ * lldb hints at (it links to the dwarf file, not the .dSYM dir).
+ *
+ * To avoid confusion with a .dSYM bundle, as well as collision with the
+ * image file, we use .dwarf suffix for the file.
+ *
+ * For details on the uuid map see rtDbgSymCacheAddImageFile as well as
+ * http://lldb.llvm.org/symbols.html .
+ *
+ * ASSUMES bundles contains Mach-O DWARF files.
+ */
+ int rc = rtDbgSymCacheConstructBundlePath(pszPath, cchPath, cchName, "Contents/Resources/DWARF/", g_apszDSymBundleSuffixes);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pszDstName)
+ pszDstName = RTPathFilename(pszPath);
+ rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, RTDBG_CACHE_DSYM_FILE_SUFFIX, RTDBG_CACHE_UUID_MAP_DIR_DSYMS, pCfg);
+ }
+ return rc;
+}
+
+
+/**
+ * Figure the type of a file/dir based on path and FS object info.
+ *
+ * @returns The type.
+ * @param pszPath The path to the file/dir.
+ * @param pObjInfo The object information, symlinks followed.
+ */
+static RTDBGSYMCACHEFILETYPE rtDbgSymCacheFigureType2(const char *pszPath, PCRTFSOBJINFO pObjInfo)
+{
+ const char *pszName = RTPathFilename(pszPath);
+ const char *pszExt = RTPathSuffix(pszName);
+ if (pszExt)
+ pszExt++;
+ else
+ pszExt = "";
+
+ if ( RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode)
+ || (pObjInfo->Attr.fMode & RTFS_DOS_DIRECTORY)) /** @todo OS X samba reports reparse points in /Volumes/ that we cannot resolve. */
+ {
+ /* Skip directories shouldn't bother with. */
+ if ( !RTStrICmp(pszName, ".Trashes")
+ || !RTStrICmp(pszName, ".$RESCYCLE.BIN")
+ || !RTStrICmp(pszName, "System.kext") /* Usually only plugins here, so skip it. */
+ )
+ return RTDBGSYMCACHEFILETYPE_IGNORE;
+
+ /* Directories can also be bundles on the mac. */
+ if (!RTStrICmp(pszExt, "dSYM"))
+ return RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_apszBundleSuffixes) - 1; i++)
+ if (!RTStrICmp(pszExt, &g_apszBundleSuffixes[i][1]))
+ return RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE;
+
+ return RTDBGSYMCACHEFILETYPE_DIR;
+ }
+
+ if (!RTFS_IS_FILE(pObjInfo->Attr.fMode))
+ return RTDBGSYMCACHEFILETYPE_INVALID;
+
+ /* Select image vs debug info based on extension. */
+ if ( !RTStrICmp(pszExt, "pdb")
+ || !RTStrICmp(pszExt, "dbg")
+ || !RTStrICmp(pszExt, "sym")
+ || !RTStrICmp(pszExt, "dwo")
+ || !RTStrICmp(pszExt, "dwp")
+ || !RTStrICmp(pszExt, "debug")
+ || !RTStrICmp(pszExt, "dsym")
+ || !RTStrICmp(pszExt, "dwarf")
+ || !RTStrICmp(pszExt, "map")
+ || !RTStrICmp(pszExt, "cv"))
+ return RTDBGSYMCACHEFILETYPE_DEBUG_FILE;
+
+ /* Filter out a bunch of files which obviously shouldn't be images. */
+ if ( !RTStrICmp(pszExt, "txt")
+ || !RTStrICmp(pszExt, "html")
+ || !RTStrICmp(pszExt, "htm")
+ || !RTStrICmp(pszExt, "rtf")
+ || !RTStrICmp(pszExt, "zip")
+ || !RTStrICmp(pszExt, "doc")
+ || !RTStrICmp(pszExt, "gz")
+ || !RTStrICmp(pszExt, "bz2")
+ || !RTStrICmp(pszExt, "xz")
+ || !RTStrICmp(pszExt, "kmk")
+ || !RTStrICmp(pszExt, "c")
+ || !RTStrICmp(pszExt, "cpp")
+ || !RTStrICmp(pszExt, "h")
+ || !RTStrICmp(pszExt, "m")
+ || !RTStrICmp(pszExt, "mm")
+ || !RTStrICmp(pszExt, "asm")
+ || !RTStrICmp(pszExt, "S")
+ || !RTStrICmp(pszExt, "inc")
+ || !RTStrICmp(pszExt, "sh")
+ )
+ return RTDBGSYMCACHEFILETYPE_IGNORE;
+ if ( !RTStrICmp(pszName, "Makefile")
+ || !RTStrICmp(pszName, "GNUmakefile")
+ || !RTStrICmp(pszName, "createsymbolfiles")
+ || !RTStrICmp(pszName, "kgmacros")
+ )
+ return RTDBGSYMCACHEFILETYPE_IGNORE;
+
+ return RTDBGSYMCACHEFILETYPE_IMAGE_FILE;
+}
+
+
+/**
+ * Figure file type based on name, will stat the file/dir.
+ *
+ * @returns File type.
+ * @param pszPath The path to the file/dir to figure.
+ */
+static RTDBGSYMCACHEFILETYPE rtDbgSymCacheFigureType(const char *pszPath)
+{
+ const char *pszName = RTPathFilename(pszPath);
+
+ /* Trailing slash. */
+ if (!pszName)
+ return RTDBGSYMCACHEFILETYPE_DIR;
+
+ /* Wildcard means listing directory and filtering. */
+ if (strpbrk(pszName, "?*"))
+ return RTDBGSYMCACHEFILETYPE_DIR_FILTER;
+
+ /* Get object info, following links. */
+ RTFSOBJINFO ObjInfo;
+ int rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_FAILURE(rc))
+ return RTDBGSYMCACHEFILETYPE_INVALID;
+ return rtDbgSymCacheFigureType2(pszPath, &ObjInfo);
+}
+
+
+/**
+ * Recursive worker for rtDbgSymCacheAddDir, for minimal stack wasting.
+ *
+ * @returns IPRT status code (fully bitched).
+ * @param pszPath Pointer to a RTPATH_MAX size buffer containing
+ * the path to the current directory ending with a
+ * slash.
+ * @param cchPath The size of the current directory path.
+ * @param pDirEntry Pointer to the RTDIRENTRYEX structure to use.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddDirWorker(char *pszPath, size_t cchPath, PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * Open the directory.
+ */
+ RTDIR hDir;
+ int rc, rc2;
+ if (pCfg->pszFilter)
+ {
+ rc = RTStrCopy(&pszPath[cchPath], RTPATH_MAX - cchPath, pCfg->pszFilter);
+ if (RT_FAILURE(rc))
+ {
+ pszPath[cchPath] = '\0';
+ return RTMsgErrorRc(rc, "Filename too long (%Rrc): '%s" RTPATH_SLASH_STR "%s'", rc, pszPath, pCfg->pszFilter);
+ }
+ rc = RTDirOpenFiltered(&hDir, pszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/);
+ }
+ else
+ rc = RTDirOpen(&hDir, pszPath);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "RTDirOpen%s failed on '%s': %Rrc", pCfg->pszFilter ? "Filtered" : "", pszPath, rc);
+
+ /*
+ * Enumerate the files.
+ */
+ for (;;)
+ {
+ rc2 = RTDirReadEx(hDir, pDirEntry, NULL, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_FAILURE(rc2))
+ {
+ pszPath[cchPath] = '\0';
+ if (rc2 != VERR_NO_MORE_FILES)
+ {
+ RTMsgError("RTDirReadEx failed in '%s': %Rrc\n", pszPath, rc2);
+ rc = rc2;
+ }
+ break;
+ }
+
+ /* Skip dot and dot-dot. */
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ continue;
+
+ /* Construct a full path. */
+ rc = RTStrCopy(&pszPath[cchPath], RTPATH_MAX, pDirEntry->szName);
+ if (RT_FAILURE(rc))
+ {
+ pszPath[cchPath] = '\0';
+ RTMsgError("File name too long in '%s': '%s' (%Rrc)", pszPath, pDirEntry->szName, rc);
+ break;
+ }
+
+ switch (rtDbgSymCacheFigureType2(pszPath, &pDirEntry->Info))
+ {
+ case RTDBGSYMCACHEFILETYPE_DIR:
+ if (!pCfg->fRecursive)
+ RTMsgInfo("Skipping directory '%s'...", pszPath);
+ else
+ {
+ if (cchPath + pDirEntry->cbName + 3 <= RTPATH_MAX)
+ {
+ pszPath[cchPath + pDirEntry->cbName] = RTPATH_SLASH;
+ pszPath[cchPath + pDirEntry->cbName + 1] = '\0';
+ rc2 = rtDbgSymCacheAddDirWorker(pszPath, cchPath + pDirEntry->cbName + 1, pDirEntry, pCfg);
+ }
+ else
+ {
+ RTMsgError("File name too long in '%s': '%s' (%Rrc)", pszPath, pDirEntry->szName, rc);
+ rc2 = VERR_FILENAME_TOO_LONG;
+ }
+ }
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_DEBUG_FILE:
+ rc2 = rtDbgSymCacheAddDebugFile(pszPath, pDirEntry->szName, pCfg);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_IMAGE_FILE:
+ rc2 = rtDbgSymCacheAddImageFile(pszPath, pDirEntry->szName, NULL /*pszExtraSuff*/, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, pCfg);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE:
+ rc2 = rtDbgSymCacheAddDebugBundle(pszPath, cchPath, pDirEntry->cbName, NULL /*pszDstName*/, pCfg);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE:
+ rc2 = rtDbgSymCacheAddImageBundle(pszPath, cchPath, pDirEntry->cbName, NULL /*pszDstName*/, pDirEntry, pCfg);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_DIR_FILTER:
+ case RTDBGSYMCACHEFILETYPE_INVALID:
+ rc2 = RTMsgErrorRc(VERR_INTERNAL_ERROR_2, "Invalid: '%s'", pszPath);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_IGNORE:
+ rc2 = VINF_SUCCESS;
+ break;
+ }
+
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ /*
+ * Clean up.
+ */
+ rc2 = RTDirClose(hDir);
+ if (RT_FAILURE(rc2))
+ {
+ RTMsgError("RTDirClose failed in '%s': %Rrc", pszPath, rc);
+ rc = rc2;
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a directory.
+ *
+ * @returns IPRT status code (fully bitched).
+ * @param pszPath The directory path.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddDir(const char *pszPath, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * Set up the path buffer, stripping any filter.
+ */
+ char szPath[RTPATH_MAX];
+ int rc = RTStrCopy(szPath, sizeof(szPath) - 2, pszPath);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Path too long: '%s'", pszPath);
+
+ size_t cchPath = strlen(pszPath);
+ if (!cchPath)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Path empty: '%s'", pszPath);
+
+ if (pCfg->pszFilter)
+ szPath[cchPath - strlen(pCfg->pszFilter)] = '\0';
+ cchPath = RTPathStripTrailingSlash(szPath);
+ if (!RTPATH_IS_SEP(pszPath[cchPath - 1]))
+ {
+ szPath[cchPath++] = RTPATH_SLASH;
+ szPath[cchPath] = '\0';
+ }
+
+ /*
+ * Let the worker do the rest.
+ */
+ RTDIRENTRYEX DirEntry;
+ return rtDbgSymCacheAddDirWorker(szPath, cchPath, &DirEntry, pCfg);
+}
+
+
+/**
+ * Adds a file or directory.
+ *
+ * @returns Program exit code.
+ * @param pszPath The user supplied path to the file or directory.
+ * @param pszCache The path to the cache.
+ * @param fRecursive Whether to process directories recursively.
+ * @param fOverwriteOnConflict Whether to overwrite existing cache entry on
+ * conflict, or just leave it.
+ */
+static RTEXITCODE rtDbgSymCacheAddFileOrDir(const char *pszPath, const char *pszCache, bool fRecursive,
+ bool fOverwriteOnConflict)
+{
+ RT_NOREF1(fOverwriteOnConflict);
+ RTDBGSYMCACHEADDCFG Cfg;
+ Cfg.fRecursive = fRecursive;
+ Cfg.pszCache = pszCache;
+ Cfg.pszFilter = NULL;
+
+ /* If the filename contains an equal ('=') char, treat the left as the file
+ to add tne right part as the name to add it under (handy for kernels). */
+ char *pszFree = NULL;
+ const char *pszDstName = RTPathFilename(pszPath);
+ const char *pszEqual = pszDstName ? strchr(pszDstName, '=') : NULL;
+ if (pszEqual)
+ {
+ pszPath = pszFree = RTStrDupN(pszPath, pszEqual - pszPath);
+ if (!pszFree)
+ return RTMsgErrorExitFailure("out of memory!\n");
+ pszDstName = pszEqual + 1;
+ if (!*pszDstName)
+ return RTMsgErrorExitFailure("add-as filename is empty!\n");
+ }
+
+ int rc;
+ RTDBGSYMCACHEFILETYPE enmType = rtDbgSymCacheFigureType(pszPath);
+ switch (enmType)
+ {
+ default:
+ case RTDBGSYMCACHEFILETYPE_INVALID:
+ rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Invalid: '%s'", pszPath);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_DIR_FILTER:
+ Cfg.pszFilter = RTPathFilename(pszPath);
+ RT_FALL_THRU();
+ case RTDBGSYMCACHEFILETYPE_DIR:
+ if (!pszEqual)
+ rc = rtDbgSymCacheAddDir(pszPath, &Cfg);
+ else
+ rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Add-as filename is not applicable to directories!");
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_DEBUG_FILE:
+ rc = rtDbgSymCacheAddDebugFile(pszPath, pszDstName, &Cfg);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_IMAGE_FILE:
+ rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, NULL /*pszExtraSuff*/, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, &Cfg);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE:
+ case RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE:
+ {
+ size_t cchPath = strlen(pszPath);
+ size_t cchFilename = strlen(RTPathFilename(pszPath));
+ char szPathBuf[RTPATH_MAX];
+ if (cchPath < sizeof(szPathBuf))
+ {
+ memcpy(szPathBuf, pszPath, cchPath + 1);
+ if (enmType == RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE)
+ rc = rtDbgSymCacheAddDebugBundle(szPathBuf, cchPath - cchFilename, cchFilename,
+ pszEqual ? pszDstName : NULL, &Cfg);
+ else
+ {
+ RTDIRENTRYEX DirEntry;
+ rc = rtDbgSymCacheAddImageBundle(szPathBuf, cchPath - cchFilename, cchFilename,
+ pszEqual ? pszDstName : NULL, &DirEntry, &Cfg);
+ }
+ }
+ else
+ rc = RTMsgErrorRc(VERR_FILENAME_TOO_LONG, "Filename too long: '%s'", pszPath);
+ break;
+ }
+
+ case RTDBGSYMCACHEFILETYPE_IGNORE:
+ rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Invalid file: '%s'", pszPath);
+ break;
+ }
+
+ RTStrFree(pszFree);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Handles the 'add' command.
+ *
+ * @returns Program exit code.
+ * @param pszArg0 The program name.
+ * @param cArgs The number of arguments to the 'add' command.
+ * @param papszArgs The argument vector, starting after 'add'.
+ */
+static RTEXITCODE rtDbgSymCacheCmdAdd(const char *pszArg0, int cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+ static RTGETOPTDEF const s_aOptions[] =
+ {
+ { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
+ { "--no-recursive", 'n', RTGETOPT_REQ_NOTHING },
+ { "--overwrite-on-conflict", 'o', RTGETOPT_REQ_NOTHING },
+ };
+
+ const char *pszCache = NULL;
+ bool fRecursive = false;
+ bool fOverwriteOnConflict = false;
+
+ RTGETOPTSTATE State;
+ int rc = RTGetOptInit(&State, cArgs, papszArgs, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc);
+
+ //uint32_t cAdded = 0;
+ RTGETOPTUNION ValueUnion;
+ int chOpt;
+ while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0)
+ {
+ switch (chOpt)
+ {
+ case 'R':
+ fRecursive = true;
+ break;
+
+ case 'n':
+ fRecursive = false;
+ break;
+
+ case 'o':
+ fOverwriteOnConflict = true;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ /* The first non-option is a cache directory. */
+ if (!pszCache)
+ {
+ pszCache = ValueUnion.psz;
+ if (!RTPathExists(pszCache))
+ {
+ rc = RTDirCreate(pszCache, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Error creating cache directory '%s': %Rrc", pszCache, rc);
+ }
+ else if (!RTDirExists(pszCache))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Specified cache directory is not a directory: '%s'", pszCache);
+ }
+ /* Subsequent non-options are files to be added to the cache. */
+ else
+ {
+ RTEXITCODE rcExit = rtDbgSymCacheAddFileOrDir(ValueUnion.psz, pszCache, fRecursive, fOverwriteOnConflict);
+ if (rcExit != RTEXITCODE_FAILURE)
+ return rcExit;
+ }
+ break;
+
+ case 'h':
+ return rtDbgSymCacheUsage(pszArg0, "add");
+ case 'V':
+ return rtDbgSymCacheVersion();
+ default:
+ return RTGetOptPrintError(chOpt, &ValueUnion);
+ }
+ }
+
+ if (!pszCache)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No cache directory or files to add were specified.");
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Debug info + external path for the 'get' command.
+ */
+typedef struct MYDBGINFO
+{
+ /** The kind of debug info. */
+ RTLDRDBGINFOTYPE enmType;
+ /** The CRC32 of the external file (RTLDRDBGINFOTYPE_DWARF_DWO). */
+ uint32_t uDwoCrc32;
+ /** The PE image size (RTLDRDBGINFOTYPE_CODEVIEW_DBG,
+ * RTLDRDBGINFOTYPE_CODEVIEW_PDB20, RTLDRDBGINFOTYPE_CODEVIEW_PDB70 (,
+ * RTLDRDBGINFOTYPE_CODEVIEW, RTLDRDBGINFOTYPE_COFF)). */
+ uint32_t cbImage;
+ /** Timestamp in seconds since unix epoch (RTLDRDBGINFOTYPE_CODEVIEW_DBG,
+ * RTLDRDBGINFOTYPE_CODEVIEW_PDB20 (, RTLDRDBGINFOTYPE_CODEVIEW,
+ * RTLDRDBGINFOTYPE_COFF)). */
+ uint32_t uTimestamp;
+ /** The PDB age (RTLDRDBGINFOTYPE_CODEVIEW_PDB20, RTLDRDBGINFOTYPE_CODEVIEW_PDB70). */
+ uint32_t uPdbAge;
+ /** The UUID of the PDB or mach-o image (RTLDRDBGINFOTYPE_CODEVIEW_PDB70, +). */
+ RTUUID Uuid;
+ /** External path (can be empty). */
+ char szExtFile[RTPATH_MAX];
+} MYDBGINFO;
+
+/**
+ * @callback_method_impl{FNRTLDRENUMDBG, For the 'get' command.}
+ */
+static DECLCALLBACK(int) rtDbgSymCacheCmdGetForExeDbgInfoCallback(RTLDRMOD hLdrMod, PCRTLDRDBGINFO pDbgInfo, void *pvUser)
+{
+ RT_NOREF(hLdrMod);
+ if (!pDbgInfo->pszExtFile)
+ switch (pDbgInfo->enmType)
+ {
+ case RTLDRDBGINFOTYPE_CODEVIEW_PDB20:
+ case RTLDRDBGINFOTYPE_CODEVIEW_PDB70:
+ case RTLDRDBGINFOTYPE_CODEVIEW_DBG:
+ break;
+ default:
+ return VINF_SUCCESS;
+ }
+
+ /* Copy the info: */
+ MYDBGINFO *pMyInfo = (MYDBGINFO *)pvUser;
+ RT_ZERO(*pMyInfo);
+ pMyInfo->enmType = pDbgInfo->enmType;
+ int rc = VINF_SUCCESS;
+ if (pDbgInfo->pszExtFile)
+ rc = RTStrCopy(pMyInfo->szExtFile, sizeof(pMyInfo->szExtFile), pDbgInfo->pszExtFile);
+
+ switch (pDbgInfo->enmType)
+ {
+ case RTLDRDBGINFOTYPE_DWARF_DWO:
+ pMyInfo->uDwoCrc32 = pDbgInfo->u.Dwo.uCrc32;
+ break;
+
+ case RTLDRDBGINFOTYPE_CODEVIEW:
+ case RTLDRDBGINFOTYPE_COFF:
+ pMyInfo->cbImage = pDbgInfo->u.Cv.cbImage;
+ pMyInfo->uTimestamp = pDbgInfo->u.Cv.uTimestamp;
+ break;
+
+ case RTLDRDBGINFOTYPE_CODEVIEW_DBG:
+ pMyInfo->cbImage = pDbgInfo->u.Dbg.cbImage;
+ pMyInfo->uTimestamp = pDbgInfo->u.Dbg.uTimestamp;
+ break;
+
+ case RTLDRDBGINFOTYPE_CODEVIEW_PDB20:
+ pMyInfo->cbImage = pDbgInfo->u.Pdb20.cbImage;
+ pMyInfo->uTimestamp = pDbgInfo->u.Pdb20.uTimestamp;
+ pMyInfo->uPdbAge = pDbgInfo->u.Pdb20.uAge;
+ break;
+
+ case RTLDRDBGINFOTYPE_CODEVIEW_PDB70:
+ pMyInfo->cbImage = pDbgInfo->u.Pdb70.cbImage;
+ pMyInfo->Uuid = pDbgInfo->u.Pdb70.Uuid;
+ pMyInfo->uPdbAge = pDbgInfo->u.Pdb70.uAge;
+ break;
+
+ default:
+ return VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTDBGCFGOPEN}
+ */
+static DECLCALLBACK(int) rtDbgSymCacheCmdGetCallback(RTDBGCFG hDbgCfg, const char *pszFilename, void *pvUser1, void *pvUser2)
+{
+ RT_NOREF(hDbgCfg, pvUser2);
+
+ char *pszJoined = NULL;
+ const char *pszOutput = (const char *)pvUser1;
+ if (!pszOutput || *pszOutput == '\0')
+ pszOutput = RTPathFilename(pszFilename);
+ else if (RTPathFilename(pszOutput) == NULL)
+ pszOutput = pszJoined = RTPathJoinA(pszOutput, RTPathFilename(pszFilename));
+
+ if (g_iLogLevel > 0) // --pe --name wintypes.dll --image-size 1388544 --timestamp 0x57F8D9F0
+ RTMsgInfo("Copying '%s' to '%s...", pszFilename, pszOutput);
+ int rc = RTFileCopy(pszFilename, pszOutput);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_ALREADY_EXISTS)
+ {
+ rc = RTFileCompare(pszFilename, pszOutput);
+ if (RT_SUCCESS(rc))
+ RTMsgInfo("Output '%s' exists and matches '%s'.", pszOutput, pszFilename);
+ else
+ RTMsgError("Output '%s' already exists (does not match '%s')", pszOutput, pszFilename);
+ }
+ else
+ RTMsgError("Copying '%s' to '%s failed: %Rrc", pszFilename, pszOutput, rc);
+ }
+ RTStrFree(pszJoined);
+ if (RT_SUCCESS(rc))
+ return VINF_CALLBACK_RETURN;
+ return rc;
+}
+
+
+/**
+ * Handles the 'get' command.
+ *
+ * @returns Program exit code.
+ * @param pszArg0 The program name.
+ * @param cArgs The number of arguments to the 'add' command.
+ * @param papszArgs The argument vector, starting after 'add'.
+ */
+static RTEXITCODE rtDbgSymCacheCmdGet(const char *pszArg0, int cArgs, char **papszArgs)
+{
+ RTERRINFOSTATIC ErrInfo;
+
+ /*
+ * Parse the command line.
+ */
+ static RTGETOPTDEF const s_aOptions[] =
+ {
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+
+ /* Query: */
+ { "--for-exe", 'e', RTGETOPT_REQ_STRING },
+ { "--for-executable", 'e', RTGETOPT_REQ_STRING },
+ { "--uuid", 'u', RTGETOPT_REQ_UUID },
+ { "--ts", 't', RTGETOPT_REQ_UINT32 },
+ { "--timestamp", 't', RTGETOPT_REQ_UINT32 },
+ { "--size", 'z', RTGETOPT_REQ_UINT32 },
+ { "--image-size", 'z', RTGETOPT_REQ_UINT32 },
+ { "--pdb-age", 'a', RTGETOPT_REQ_UINT32 },
+ { "--dwo-crc32", 'c', RTGETOPT_REQ_UINT32 },
+ { "--name", 'n', RTGETOPT_REQ_STRING },
+
+ { "--dwo", 'd', RTGETOPT_REQ_NOTHING },
+ { "--dwarf", 'd', RTGETOPT_REQ_NOTHING },
+ { "--dwarf-external", 'd', RTGETOPT_REQ_NOTHING },
+ { "--dsym", 'D', RTGETOPT_REQ_NOTHING },
+ { "--dbg", '0', RTGETOPT_REQ_NOTHING },
+ { "--pdb20", '2', RTGETOPT_REQ_NOTHING },
+ { "--pdb70", '7', RTGETOPT_REQ_NOTHING },
+
+ { "--pe", 'P', RTGETOPT_REQ_NOTHING },
+ { "--macho", 'M', RTGETOPT_REQ_NOTHING },
+ { "--elf", 'E', RTGETOPT_REQ_NOTHING },
+
+ /* RTDbgCfg: */
+ { "--env-prefix", 'p', RTGETOPT_REQ_STRING },
+ { "--sym-path", 's', RTGETOPT_REQ_STRING },
+ { "--use-native-paths", 1000, RTGETOPT_REQ_NOTHING },
+ { "--no-native-paths", 1001, RTGETOPT_REQ_NOTHING },
+ };
+
+ RTGETOPTSTATE State;
+ int rc = RTGetOptInit(&State, cArgs, papszArgs, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc);
+
+ const char *pszOutput = NULL;
+
+ bool fGetExeImage = true;
+ const char *pszForExec = NULL;
+ const char *pszName = NULL;
+ RTLDRARCH enmImageArch = RTLDRARCH_WHATEVER;
+ RTLDRFMT enmImageFmt = RTLDRFMT_INVALID;
+ MYDBGINFO DbgInfo;
+ RT_ZERO(DbgInfo);
+
+ const char *pszEnvPrefix = "IPRT_";
+ bool fNativePaths = true;
+ const char *apszSymPaths[12] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+ unsigned cSymPaths = 0;
+
+ RTGETOPTUNION ValueUnion;
+ int chOpt;
+ while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0)
+ {
+ switch (chOpt)
+ {
+ case 'o':
+ pszOutput = ValueUnion.psz;
+ break;
+
+ /*
+ * Query elements:
+ */
+ case 'z':
+ DbgInfo.cbImage = ValueUnion.u32;
+ break;
+
+ case 't':
+ DbgInfo.uTimestamp = ValueUnion.u32;
+ enmImageFmt = RTLDRFMT_PE;
+ break;
+
+ case 'u':
+ DbgInfo.Uuid = ValueUnion.Uuid;
+ enmImageFmt = RTLDRFMT_MACHO;
+ break;
+
+ case 'a':
+ DbgInfo.uPdbAge = ValueUnion.u32;
+ if (DbgInfo.enmType != RTLDRDBGINFOTYPE_CODEVIEW_PDB20)
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB70;
+ break;
+
+ case 'c':
+ DbgInfo.uDwoCrc32 = ValueUnion.u32;
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF_DWO;
+ break;
+
+ case 'n':
+ pszName = ValueUnion.psz;
+ DbgInfo.szExtFile[0] = '\0';
+ break;
+
+ case 'd':
+ fGetExeImage = false;
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF_DWO;
+ break;
+
+ case 'D':
+ fGetExeImage = false;
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF; /* == dSYM */
+ break;
+
+ case '0':
+ fGetExeImage = false;
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_DBG;
+ break;
+
+ case '2':
+ fGetExeImage = false;
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB20;
+ break;
+
+ case '7':
+ fGetExeImage = false;
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB70;
+ break;
+
+ case 'E':
+ fGetExeImage = true;
+ enmImageFmt = RTLDRFMT_ELF;
+ break;
+
+ case 'M':
+ fGetExeImage = true;
+ enmImageFmt = RTLDRFMT_MACHO;
+ break;
+
+ case 'P':
+ fGetExeImage = true;
+ enmImageFmt = RTLDRFMT_PE;
+ break;
+
+ case 'e':
+ {
+ /* Open the executable and retrieve the query parameters from it: */
+ fGetExeImage = false;
+ pszForExec = ValueUnion.psz;
+ if (!pszName)
+ pszName = RTPathFilename(pszForExec);
+
+ RTLDRMOD hLdrMod;
+ rc = RTLdrOpenEx(pszForExec, RTLDR_O_FOR_DEBUG, enmImageArch, &hLdrMod, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to open image '%s': %Rrc%#RTeim", pszForExec, rc, &ErrInfo);
+
+ DbgInfo.cbImage = (uint32_t)RTLdrSize(hLdrMod);
+ enmImageFmt = RTLdrGetFormat(hLdrMod);
+ if (enmImageFmt == RTLDRFMT_MACHO)
+ {
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF; /* .dSYM */
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_UUID, &DbgInfo.Uuid, sizeof(DbgInfo.Uuid));
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to query image UUID from '%s': %Rrc", pszForExec, rc);
+ }
+ else
+ {
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &DbgInfo.uTimestamp, sizeof(DbgInfo.uTimestamp));
+ if (RT_SUCCESS(rc) || (rc == VERR_NOT_FOUND && enmImageFmt != RTLDRFMT_PE))
+ {
+ RT_ZERO(DbgInfo);
+ rc = RTLdrEnumDbgInfo(hLdrMod, NULL, rtDbgSymCacheCmdGetForExeDbgInfoCallback, &DbgInfo);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTLdrEnumDbgInfo failed on '%s': %Rrc", pszForExec, rc);
+ }
+ else if (RT_FAILURE(rc))
+ RTMsgError("Failed to query image timestamp from '%s': %Rrc", pszForExec, rc);
+ }
+
+ RTLdrClose(hLdrMod);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+ break;
+ }
+
+ /*
+ * RTDbgCfg setup:
+ */
+ case 'p':
+ pszEnvPrefix = ValueUnion.psz;
+ break;
+
+ case 's':
+ if (cSymPaths < RT_ELEMENTS(apszSymPaths))
+ apszSymPaths[cSymPaths++] = ValueUnion.psz;
+ else
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --sym-paths arguments: max %u", RT_ELEMENTS(apszSymPaths));
+ break;
+
+ case 1000:
+ fNativePaths = true;
+ break;
+
+ case 1001:
+ fNativePaths = false;
+ break;
+
+ case 'h':
+ return rtDbgSymCacheUsage(pszArg0, "get");
+ case 'V':
+ return rtDbgSymCacheVersion();
+ default:
+ return RTGetOptPrintError(chOpt, &ValueUnion);
+ }
+ }
+
+ /*
+ * Instantiate the debug config we'll be querying.
+ */
+ RTDBGCFG hDbgCfg;
+ rc = RTDbgCfgCreate(&hDbgCfg, pszEnvPrefix, fNativePaths);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDbgCfgCreate failed: %Rrc", rc);
+
+ rc = RTDbgCfgSetLogCallback(hDbgCfg, rtDbgSymCacheLogCallback, NULL);
+ AssertRCStmt(rc, RTMsgError("RTDbgCfgSetLogCallback failed: %Rrc", rc));
+
+ for (unsigned i = 0; i < cSymPaths && RT_SUCCESS(rc); i++)
+ {
+ rc = RTDbgCfgChangeString(hDbgCfg, RTDBGCFGPROP_PATH, RTDBGCFGOP_APPEND, apszSymPaths[i]);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to append symbol path '%s': %Rrc", apszSymPaths[i], rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do the getting.
+ */
+ if (fGetExeImage)
+ {
+ if (enmImageFmt == RTLDRFMT_INVALID)
+ {
+ if (!RTUuidIsNull(&DbgInfo.Uuid))
+ enmImageFmt = RTLDRFMT_MACHO;
+ else if (DbgInfo.cbImage && DbgInfo.uTimestamp)
+ enmImageFmt = RTLDRFMT_PE;
+ else
+ rc = RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "Not enough to go on to find executable");
+ }
+ if (enmImageFmt == RTLDRFMT_PE)
+ rc = RTDbgCfgOpenPeImage(hDbgCfg, pszName, DbgInfo.cbImage, DbgInfo.uTimestamp,
+ rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
+ else if (enmImageFmt == RTLDRFMT_MACHO)
+ rc = RTDbgCfgOpenMachOImage(hDbgCfg, pszName, &DbgInfo.Uuid,
+ rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
+ else if (enmImageFmt != RTLDRFMT_INVALID)
+ rc = RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "Format not implemented: %s", RTLdrGetFormat);
+ }
+ else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_CODEVIEW_PDB70)
+ rc = RTDbgCfgOpenPdb70(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, &DbgInfo.Uuid, DbgInfo.uPdbAge,
+ rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
+ else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_CODEVIEW_PDB20)
+ rc = RTDbgCfgOpenPdb20(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, DbgInfo.cbImage,
+ DbgInfo.uTimestamp, DbgInfo.uPdbAge, rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
+ else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_CODEVIEW_DBG)
+ rc = RTDbgCfgOpenDbg(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, DbgInfo.cbImage,
+ DbgInfo.uTimestamp, rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
+ else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_DWARF_DWO)
+ rc = RTDbgCfgOpenDwo(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, DbgInfo.uDwoCrc32,
+ rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
+ else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_DWARF)
+ rc = RTDbgCfgOpenDsymBundle(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, &DbgInfo.Uuid,
+ rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
+ else
+ rc = RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "Format not implemented");
+ }
+
+ RTDbgCfgRelease(hDbgCfg);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Switch on the command.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SYNTAX;
+ if (argc < 2)
+ rtDbgSymCacheUsage(argv[0], NULL);
+ else if (!strcmp(argv[1], "add"))
+ rcExit = rtDbgSymCacheCmdAdd(argv[0], argc - 2, argv + 2);
+ else if (!strcmp(argv[1], "get"))
+ rcExit = rtDbgSymCacheCmdGet(argv[0], argc - 2, argv + 2);
+ else if ( !strcmp(argv[1], "-h")
+ || !strcmp(argv[1], "-?")
+ || !strcmp(argv[1], "--help"))
+ rcExit = rtDbgSymCacheUsage(argv[0], NULL);
+ else if ( !strcmp(argv[1], "-V")
+ || !strcmp(argv[1], "--version"))
+ rcExit = rtDbgSymCacheVersion();
+ else
+ RTMsgError("Unknown command: '%s'", argv[1]);
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTDbgSymSrv.cpp b/src/VBox/Runtime/tools/RTDbgSymSrv.cpp
new file mode 100644
index 00000000..4d47086b
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTDbgSymSrv.cpp
@@ -0,0 +1,600 @@
+/* $Id: RTDbgSymSrv.cpp $ */
+/** @file
+ * IPRT - Debug Symbol Server.
+ */
+
+/*
+ * Copyright (C) 2021-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/err.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/getopt.h>
+#include <iprt/http.h>
+#include <iprt/http-server.h>
+#include <iprt/initterm.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/thread.h>
+#include <iprt/pipe.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) dbgSymSrvOpen(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void **ppvHandle);
+static DECLCALLBACK(int) dbgSymSrvRead(PRTHTTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbBuf, size_t *pcbRead);
+static DECLCALLBACK(int) dbgSymSrvClose(PRTHTTPCALLBACKDATA pData, void *pvHandle);
+static DECLCALLBACK(int) dbgSymSrvQueryInfo(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char **ppszMIMEHint);
+static DECLCALLBACK(int) dbgSymSrvDestroy(PRTHTTPCALLBACKDATA pData);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Flag whether the server was interrupted. */
+static bool g_fCanceled = false;
+/** The symbol cache absolute root. */
+static const char *g_pszSymCacheRoot = NULL;
+/** The path to the pdb.exe. */
+static const char *g_pszPdbExe = NULL;
+/** Symbol server to forward requests to if not found locally. */
+static const char *g_pszSymSrvFwd = NULL;
+#ifndef RT_OS_WINDOWS
+/** The WINEPREFIX to use. */
+static const char *g_pszWinePrefix = NULL;
+/** The path to the wine binary to use for pdb.exe. */
+static const char *g_pszWinePath = NULL;
+#endif
+/** Verbositity level. */
+//static uint32_t g_iLogLevel = 99;
+/** Server callbacks. */
+static RTHTTPSERVERCALLBACKS g_SrvCallbacks =
+{
+ dbgSymSrvOpen,
+ dbgSymSrvRead,
+ dbgSymSrvClose,
+ dbgSymSrvQueryInfo,
+ NULL,
+ NULL,
+ dbgSymSrvDestroy
+};
+
+
+/**
+ * Resolves (and validates) a given URL to an absolute (local) path.
+ *
+ * @returns VBox status code.
+ * @param pszUrl URL to resolve.
+ * @param ppszPathAbs Where to store the resolved absolute path on success.
+ * Needs to be free'd with RTStrFree().
+ * @param ppszPathAbsXml Where to store the resolved absolute path for the converted XML
+ * file. Needs to be free'd with RTStrFree().
+ */
+static int rtDbgSymSrvPathResolve(const char *pszUrl, char **ppszPathAbs, char **ppszPathAbsXml)
+{
+ /* The URL needs to start with /download/symbols/. */
+ if (strncmp(pszUrl, "/download/symbols/", sizeof("/download/symbols/") - 1))
+ return VERR_NOT_FOUND;
+
+ pszUrl += sizeof("/download/symbols/") - 1;
+ /* Construct absolute path. */
+ char *pszPathAbs = NULL;
+ if (RTStrAPrintf(&pszPathAbs, "%s/%s", g_pszSymCacheRoot, pszUrl) <= 0)
+ return VERR_NO_MEMORY;
+
+ if (ppszPathAbsXml)
+ {
+ char *pszPathAbsXml = NULL;
+ if (RTStrAPrintf(&pszPathAbsXml, "%s/%s.xml", g_pszSymCacheRoot, pszUrl) <= 0)
+ return VERR_NO_MEMORY;
+
+ *ppszPathAbsXml = pszPathAbsXml;
+ }
+
+ *ppszPathAbs = pszPathAbs;
+
+ return VINF_SUCCESS;
+}
+
+
+static int rtDbgSymSrvFwdDownload(const char *pszUrl, char *pszPathAbs)
+{
+ RTPrintf("'%s' not in local cache, fetching from '%s'\n", pszPathAbs, g_pszSymSrvFwd);
+
+ char *pszFilename = RTPathFilename(pszPathAbs);
+ char chStart = *pszFilename;
+ *pszFilename = '\0';
+ int rc = RTDirCreateFullPath(pszPathAbs, 0766);
+ if (!RTDirExists(pszPathAbs))
+ {
+ Log(("Error creating cache dir '%s': %Rrc\n", pszPathAbs, rc));
+ return rc;
+ }
+ *pszFilename = chStart;
+
+ char szUrl[_2K];
+ RTHTTP hHttp;
+ rc = RTHttpCreate(&hHttp);
+ if (RT_SUCCESS(rc))
+ {
+ RTHttpUseSystemProxySettings(hHttp);
+ RTHttpSetFollowRedirects(hHttp, 8);
+
+ static const char * const s_apszHeaders[] =
+ {
+ "User-Agent: Microsoft-Symbol-Server/6.6.0999.9",
+ "Pragma: no-cache",
+ };
+
+ rc = RTHttpSetHeaders(hHttp, RT_ELEMENTS(s_apszHeaders), s_apszHeaders);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrPrintf(szUrl, sizeof(szUrl), "%s/%s", g_pszSymSrvFwd, pszUrl + sizeof("/download/symbols/") - 1);
+
+ /** @todo Use some temporary file name and rename it after the operation
+ * since not all systems support read-deny file sharing
+ * settings. */
+ RTPrintf("Downloading '%s' to '%s'...\n", szUrl, pszPathAbs);
+ rc = RTHttpGetFile(hHttp, szUrl, pszPathAbs);
+ if (RT_FAILURE(rc))
+ {
+ RTFileDelete(pszPathAbs);
+ RTPrintf("%Rrc on URL '%s'\n", rc, szUrl);
+ }
+ if (rc == VERR_HTTP_NOT_FOUND)
+ {
+ /* Try the compressed version of the file. */
+ pszPathAbs[strlen(pszPathAbs) - 1] = '_';
+ szUrl[strlen(szUrl) - 1] = '_';
+ RTPrintf("Downloading '%s' to '%s'...\n", szUrl, pszPathAbs);
+ rc = RTHttpGetFile(hHttp, szUrl, pszPathAbs);
+#if 0 /** @todo */
+ if (RT_SUCCESS(rc))
+ rc = rtDbgCfgUnpackMsCacheFile(pThis, pszPathAbs, pszFilename);
+ else
+#endif
+ {
+ RTPrintf("%Rrc on URL '%s'\n", rc, pszPathAbs);
+ RTFileDelete(pszPathAbs);
+ }
+ }
+ }
+
+ RTHttpDestroy(hHttp);
+ }
+
+ return rc;
+}
+
+
+static int rtDbgSymSrvConvertToGhidraXml(char *pszPath, const char *pszFilename)
+{
+ RTPrintf("Converting '%s' to ghidra XML into '%s'\n", pszPath, pszFilename);
+
+ /*
+ * Figuring out the argument list for the platform specific way to call pdb.exe.
+ */
+#ifdef RT_OS_WINDOWS
+ RTENV hEnv = RTENV_DEFAULT;
+ RTPathChangeToDosSlashes(pszPath, false /*fForce*/);
+ const char *papszArgs[] =
+ {
+ g_pszPdbExe,
+ pszPath,
+ NULL
+ };
+
+#else
+ const char *papszArgs[] =
+ {
+ g_pszWinePath,
+ g_pszPdbExe,
+ pszPath,
+ NULL
+ };
+
+ RTENV hEnv;
+ {
+ int rc = RTEnvCreate(&hEnv);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTEnvSetEx(hEnv, "WINEPREFIX", g_pszWinePrefix);
+ if (RT_SUCCESS(rc))
+ rc = RTEnvSetEx(hEnv, "WINEDEBUG", "-all");
+ if (RT_FAILURE(rc))
+ {
+ RTEnvDestroy(hEnv);
+ return rc;
+ }
+ }
+ }
+#endif
+
+ RTPIPE hPipeR, hPipeW;
+ int rc = RTPipeCreate(&hPipeR, &hPipeW, RTPIPE_C_INHERIT_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ RTHANDLE Handle;
+ Handle.enmType = RTHANDLETYPE_PIPE;
+ Handle.u.hPipe = hPipeW;
+
+ /*
+ * Do the conversion.
+ */
+ RTPROCESS hChild;
+ RTFILE hFile;
+
+ rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE); AssertRC(rc);
+
+ rc = RTProcCreateEx(papszArgs[0], papszArgs, hEnv,
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ RTPROC_FLAGS_NO_WINDOW | RTPROC_FLAGS_HIDDEN | RTPROC_FLAGS_SEARCH_PATH,
+#else
+ RTPROC_FLAGS_SEARCH_PATH,
+#endif
+ NULL /*phStdIn*/, &Handle, NULL /*phStdErr*/,
+ NULL /*pszAsUser*/, NULL /*pszPassword*/, NULL /*pvExtraData*/,
+ &hChild);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPipeClose(hPipeW); AssertRC(rc);
+
+ for (;;)
+ {
+ char szOutput[_4K];
+ size_t cbRead;
+ rc = RTPipeReadBlocking(hPipeR, &szOutput[0], sizeof(szOutput), &cbRead);
+ if (RT_FAILURE(rc))
+ {
+ Assert(rc == VERR_BROKEN_PIPE);
+ break;
+ }
+
+ rc = RTFileWrite(hFile, &szOutput[0], cbRead, NULL /*pcbWritten*/); AssertRC(rc);
+ }
+ rc = RTPipeClose(hPipeR); AssertRC(rc);
+
+ RTPROCSTATUS ProcStatus;
+ rc = RTProcWait(hChild, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus);
+ if (RT_SUCCESS(rc))
+ {
+ if ( ProcStatus.enmReason == RTPROCEXITREASON_NORMAL
+ && ProcStatus.iStatus == 0)
+ {
+ if (RTPathExists(pszPath))
+ {
+ RTPrintf("Successfully unpacked '%s' to '%s'.\n", pszPath, pszFilename);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ RTPrintf("Successfully ran unpacker on '%s', but '%s' is missing!\n", pszPath, pszFilename);
+ rc = VERR_FILE_NOT_FOUND;
+ }
+ }
+ else
+ {
+ RTPrintf("Unpacking '%s' failed: iStatus=%d enmReason=%d\n",
+ pszPath, ProcStatus.iStatus, ProcStatus.enmReason);
+ rc = VERR_ZIP_CORRUPTED;
+ }
+ }
+ else
+ RTPrintf("Error waiting for process: %Rrc\n", rc);
+
+ RTFileClose(hFile);
+
+ }
+ else
+ RTPrintf("Error starting unpack process '%s': %Rrc\n", papszArgs[0], rc);
+ }
+
+#ifndef RT_OS_WINDOWS
+ RTEnvDestroy(hEnv);
+#endif
+ return rc;
+}
+
+
+static DECLCALLBACK(int) dbgSymSrvOpen(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void **ppvHandle)
+{
+ RT_NOREF(pData);
+
+ char *pszPathAbs = NULL;
+ char *pszPathAbsXml = NULL;
+ int rc = rtDbgSymSrvPathResolve(pReq->pszUrl, &pszPathAbs, &pszPathAbsXml);
+ if (RT_SUCCESS(rc))
+ {
+ RTFILE hFile;
+ if ( g_pszPdbExe
+ && RTPathExists(pszPathAbsXml))
+ {
+ RTPrintf("Opening '%s'\n", pszPathAbsXml);
+ rc = RTFileOpen(&hFile, pszPathAbsXml, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ }
+ else
+ {
+ RTPrintf("Opening '%s'\n", pszPathAbs);
+ rc = RTFileOpen(&hFile, pszPathAbs, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ }
+ if (RT_SUCCESS(rc))
+ *ppvHandle = hFile;
+
+ RTStrFree(pszPathAbs);
+ RTStrFree(pszPathAbsXml);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static DECLCALLBACK(int) dbgSymSrvRead(PRTHTTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+{
+ RT_NOREF(pData);
+ return RTFileRead((RTFILE)pvHandle, pvBuf, cbBuf, pcbRead);
+}
+
+
+static DECLCALLBACK(int) dbgSymSrvClose(PRTHTTPCALLBACKDATA pData, void *pvHandle)
+{
+ RT_NOREF(pData);
+ return RTFileClose((RTFILE)pvHandle);
+}
+
+
+static DECLCALLBACK(int) dbgSymSrvQueryInfo(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char **ppszMIMEHint)
+{
+ RT_NOREF(pData, ppszMIMEHint);
+ char *pszPathAbs = NULL;
+ char *pszPathAbsXml = NULL;
+ int rc = rtDbgSymSrvPathResolve(pReq->pszUrl, &pszPathAbs, &pszPathAbsXml);
+ if (RT_SUCCESS(rc))
+ {
+ if ( !RTPathExists(pszPathAbs)
+ && g_pszSymSrvFwd)
+ rc = rtDbgSymSrvFwdDownload(pReq->pszUrl, pszPathAbs);
+
+ if ( RT_SUCCESS(rc)
+ && RTPathExists(pszPathAbs))
+ {
+ const char *pszFile = pszPathAbs;
+
+ if (g_pszPdbExe)
+ {
+ if (!RTPathExists(pszPathAbsXml))
+ rc = rtDbgSymSrvConvertToGhidraXml(pszPathAbs, pszPathAbsXml);
+ if (RT_SUCCESS(rc))
+ pszFile = pszPathAbsXml;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && RTPathExists(pszFile))
+ {
+ rc = RTPathQueryInfo(pszFile, pObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (!RTFS_IS_FILE(pObjInfo->Attr.fMode))
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ rc = VERR_FILE_NOT_FOUND;
+ }
+ else
+ rc = VERR_FILE_NOT_FOUND;
+
+ RTStrFree(pszPathAbs);
+ RTStrFree(pszPathAbsXml);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static DECLCALLBACK(int) dbgSymSrvDestroy(PRTHTTPCALLBACKDATA pData)
+{
+ RTPrintf("%s\n", __FUNCTION__);
+ RT_NOREF(pData);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Display the version of the server program.
+ *
+ * @returns exit code.
+ */
+static RTEXITCODE rtDbgSymSrvVersion(void)
+{
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Shows the usage of the cache program.
+ *
+ * @returns Exit code.
+ * @param pszArg0 Program name.
+ */
+static RTEXITCODE rtDbgSymSrvUsage(const char *pszArg0)
+{
+ RTPrintf("Usage: %s --address <interface> --port <port> --sym-cache <symbol cache root> --pdb-exe <ghidra pdb.exe path>\n"
+ "\n"
+ "Options:\n"
+ " -a, --address\n"
+ " The interface to listen on, default is localhost.\n"
+ " -p, --port\n"
+ " The port to listen on, default is 80.\n"
+ " -c, --sym-cache\n"
+ " The absolute path of the symbol cache.\n"
+ " -x, --pdb-exe\n"
+ " The path of Ghidra's pdb.exe to convert PDB files to XML on the fly.\n"
+ " -f, --sym-srv-forward\n"
+ " The symbol server to forward requests to if a file is not in the local cache\n"
+#ifndef RT_OS_WINDOWS
+ " -w, --wine-prefix\n"
+ " The prefix of the wine environment to use which has msdia140.dll set up for pdb.exe.\n"
+ " -b, --wine-bin\n"
+ " The wine binary path to run pdb.exe with.\n"
+#endif
+ , RTPathFilename(pszArg0));
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse the command line.
+ */
+ static RTGETOPTDEF const s_aOptions[] =
+ {
+ { "--address", 'a', RTGETOPT_REQ_STRING },
+ { "--port", 'p', RTGETOPT_REQ_UINT16 },
+ { "--sym-cache", 'c', RTGETOPT_REQ_STRING },
+ { "--pdb-exe", 'x', RTGETOPT_REQ_STRING },
+ { "--sym-srv-forward", 'f', RTGETOPT_REQ_STRING },
+#ifndef RT_OS_WINDOWS
+ { "--wine-prefix", 'w', RTGETOPT_REQ_STRING },
+ { "--wine-bin", 'b', RTGETOPT_REQ_STRING },
+#endif
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTGETOPTSTATE State;
+ rc = RTGetOptInit(&State, argc, argv, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc);
+
+ const char *pszAddress = "localhost";
+ uint16_t uPort = 80;
+
+ RTGETOPTUNION ValueUnion;
+ int chOpt;
+ while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0)
+ {
+ switch (chOpt)
+ {
+ case 'a':
+ pszAddress = ValueUnion.psz;
+ break;
+ case 'p':
+ uPort = ValueUnion.u16;
+ break;
+ case 'c':
+ g_pszSymCacheRoot = ValueUnion.psz;
+ break;
+ case 'x':
+ g_pszPdbExe = ValueUnion.psz;
+ break;
+ case 'f':
+ g_pszSymSrvFwd = ValueUnion.psz;
+ break;
+#ifndef RT_OS_WINDOWS
+ case 'w':
+ g_pszWinePrefix = ValueUnion.psz;
+ break;
+ case 'b':
+ g_pszWinePath = ValueUnion.psz;
+ break;
+#endif
+
+ case 'h':
+ return rtDbgSymSrvUsage(argv[0]);
+ case 'V':
+ return rtDbgSymSrvVersion();
+ default:
+ return RTGetOptPrintError(chOpt, &ValueUnion);
+ }
+ }
+
+ if (!g_pszSymCacheRoot)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "The symbol cache root needs to be set");
+
+ RTHTTPSERVER hHttpSrv;
+ rc = RTHttpServerCreate(&hHttpSrv, pszAddress, uPort, &g_SrvCallbacks,
+ NULL, 0);
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Starting HTTP server at %s:%RU16 ...\n", pszAddress, uPort);
+ RTPrintf("Root directory is '%s'\n", g_pszSymCacheRoot);
+
+ RTPrintf("Running HTTP server ...\n");
+
+ for (;;)
+ {
+ RTThreadSleep(1000);
+
+ if (g_fCanceled)
+ break;
+ }
+
+ RTPrintf("Stopping HTTP server ...\n");
+
+ int rc2 = RTHttpServerDestroy(hHttpSrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ RTPrintf("Stopped HTTP server\n");
+ }
+ else
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTHttpServerCreate failed: %Rrc", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/tools/RTEfiFatExtract.cpp b/src/VBox/Runtime/tools/RTEfiFatExtract.cpp
new file mode 100644
index 00000000..6158705f
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTEfiFatExtract.cpp
@@ -0,0 +1,263 @@
+/* $Id: RTEfiFatExtract.cpp $ */
+/** @file
+ * IPRT - Utility for extracting single files from a fat EFI binary.
+ */
+
+/*
+ * Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/formats/efi-fat.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+
+static int efiFatExtractList(const char *pszInput)
+{
+ RTFILE hFile = NIL_RTFILE;
+ int rc = RTFileOpen(&hFile, pszInput, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ EFI_FATHDR Hdr;
+ rc = RTFileReadAt(hFile, 0, &Hdr, sizeof(Hdr), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if ( RT_LE2H_U32(Hdr.u32Magic) == EFI_FATHDR_MAGIC
+ && RT_LE2H_U32(Hdr.cFilesEmbedded) <= 16 /*Arbitrary number*/)
+ {
+ RTFOFF offRead = sizeof(Hdr);
+
+ for (uint32_t i = 0; i < RT_LE2H_U32(Hdr.cFilesEmbedded); i++)
+ {
+ EFI_FATDIRENTRY Entry;
+ rc = RTFileReadAt(hFile, offRead, &Entry, sizeof(Entry), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Entry %u:\n", i);
+ RTPrintf(" CPU Type: %#x\n", RT_LE2H_U32(Entry.u32CpuType));
+ RTPrintf(" CPU Subtype: %#x\n", RT_LE2H_U32(Entry.u32CpuSubType));
+ RTPrintf(" Offset: %#x\n", RT_LE2H_U32(Entry.u32OffsetStart));
+ RTPrintf(" Size: %#x\n", RT_LE2H_U32(Entry.cbFile));
+ RTPrintf(" Alignment: %#x\n", RT_LE2H_U32(Entry.u32Alignment));
+ }
+ else
+ {
+ RTPrintf("Failed to read file entry %u of '%s': %Rrc\n", pszInput, i, rc);
+ break;
+ }
+
+ offRead += sizeof(Entry);
+ }
+ }
+ else
+ {
+ rc = VERR_INVALID_MAGIC;
+ RTPrintf("The header contains invalid values\n");
+ }
+ }
+ else
+ RTPrintf("Failed to read header of '%s': %Rrc\n", pszInput, rc);
+
+ RTFileClose(hFile);
+ }
+ else
+ RTPrintf("Failed to open file '%s': %Rrc\n", pszInput, rc);
+
+ return rc;
+}
+
+
+static int efiFatExtractSave(const char *pszInput, uint32_t idxEntry, const char *pszOut)
+{
+ RTFILE hFile = NIL_RTFILE;
+ int rc = RTFileOpen(&hFile, pszInput, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ EFI_FATHDR Hdr;
+ rc = RTFileReadAt(hFile, 0, &Hdr, sizeof(Hdr), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if ( RT_LE2H_U32(Hdr.u32Magic) == EFI_FATHDR_MAGIC
+ && RT_LE2H_U32(Hdr.cFilesEmbedded) <= 16 /*Arbitrary number*/)
+ {
+ if (idxEntry < RT_LE2H_U32(Hdr.cFilesEmbedded))
+ {
+ EFI_FATDIRENTRY Entry;
+
+ rc = RTFileReadAt(hFile, sizeof(Hdr) + idxEntry * sizeof(Entry), &Entry, sizeof(Entry), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ void *pvFile = RTMemAllocZ(RT_LE2H_U32(Entry.cbFile));
+ if (RT_LIKELY(pvFile))
+ {
+ rc = RTFileReadAt(hFile, RT_LE2H_U32(Entry.u32OffsetStart), pvFile, RT_LE2H_U32(Entry.cbFile), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ RTFILE hFileOut;
+ rc = RTFileOpen(&hFileOut, pszOut, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(hFileOut, pvFile, RT_LE2H_U32(Entry.cbFile), NULL);
+ if (RT_FAILURE(rc))
+ RTPrintf("Failed to write output file '%s': %Rrc\n", pszOut, rc);
+ RTFileClose(hFileOut);
+ }
+ else
+ RTPrintf("Failed to create output file '%s': %Rrc\n", pszOut, rc);
+ }
+ else
+ RTPrintf("Failed to read embedded file %u: %Rrc\n", idxEntry, rc);
+
+ RTMemFree(pvFile);
+ }
+ else
+ RTPrintf("Failed to allocate %u bytes of memory\n", RT_LE2H_U32(Entry.cbFile));
+ }
+ else
+ RTPrintf("Failed to read file entry %u of '%s': %Rrc\n", pszInput, idxEntry, rc);
+ }
+ else
+ {
+ rc = VERR_INVALID_PARAMETER;
+ RTPrintf("Given index out of range, maximum is %u\n", RT_LE2H_U32(Hdr.cFilesEmbedded));
+ }
+ }
+ else
+ {
+ rc = VERR_INVALID_MAGIC;
+ RTPrintf("The header contains invalid values\n");
+ }
+ }
+ else
+ RTPrintf("Failed to read header of '%s': %Rrc\n", pszInput, rc);
+
+ RTFileClose(hFile);
+ }
+ else
+ RTPrintf("Failed to open file '%s': %Rrc\n", pszInput, rc);
+
+ return rc;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--input", 'i', RTGETOPT_REQ_STRING },
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "--entry", 'e', RTGETOPT_REQ_UINT32 },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ const char *pszInput = NULL;
+ const char *pszOut = NULL;
+ uint32_t idxEntry = UINT32_C(0xffffffff);
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 'h':
+ RTPrintf("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"
+ " -i,--input=<file>\n"
+ " Input file\n"
+ " -e,--entry=<idx>\n"
+ " Selects the entry for saving\n"
+ " -o,--output=file\n"
+ " Save the specified entry to this file\n"
+ " -h, -?, --help\n"
+ " Display this help text and exit successfully.\n"
+ " -V, --version\n"
+ " Display the revision and exit successfully.\n"
+ , RTPathFilename(argv[0]));
+ return RTEXITCODE_SUCCESS;
+ case 'V':
+ RTPrintf("$Revision: 155244 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ case 'i':
+ pszInput = ValueUnion.psz;
+ break;
+ case 'o':
+ pszOut = ValueUnion.psz;
+ break;
+ case 'e':
+ idxEntry = ValueUnion.u32;
+ break;
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ if (!pszInput)
+ {
+ RTPrintf("An input path must be given\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ if (!pszOut || idxEntry == UINT32_C(0xffffffff))
+ rc = efiFatExtractList(pszInput);
+ else
+ rc = efiFatExtractSave(pszInput, idxEntry, pszOut);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTEfiSigDb.cpp b/src/VBox/Runtime/tools/RTEfiSigDb.cpp
new file mode 100644
index 00000000..911831d4
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTEfiSigDb.cpp
@@ -0,0 +1,833 @@
+/* $Id: RTEfiSigDb.cpp $ */
+/** @file
+ * IPRT - Utility for manipulating EFI signature databases.
+ */
+
+/*
+ * Copyright (C) 2021-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/err.h>
+#include <iprt/efi.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/vfs.h>
+
+#include <iprt/formats/efi-signature.h>
+#include <iprt/formats/efi-varstore.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Signature type identifier to internal type mapping. */
+struct RTEFISIGDBID2TYPEENTRY
+{
+ const char *pszId;
+ RTEFISIGTYPE enmType;
+} g_aId2SigType[] =
+{
+ { "sha256", RTEFISIGTYPE_SHA256 },
+ { "rsa2048", RTEFISIGTYPE_RSA2048 },
+ { "x509", RTEFISIGTYPE_X509 }
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Display the version of the cache program.
+ *
+ * @returns exit code.
+ */
+static RTEXITCODE rtEfiSigDbVersion(void)
+{
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Shows the usage of the program.
+ *
+ * @returns Exit code.
+ * @param pszArg0 Program name.
+ * @param pszCommand Command selector, NULL if all.
+ */
+static RTEXITCODE rtEfiSigDbUsage(const char *pszArg0, const char *pszCommand)
+{
+ if (!pszCommand || !strcmp(pszCommand, "list"))
+ RTPrintf("Usage: %s list <signature database path>\n"
+ , RTPathFilename(pszArg0));
+
+ if (!pszCommand || !strcmp(pszCommand, "add"))
+ RTPrintf("Usage: %s add <signature database path> <x509|sha256|rsa2048> <owner uuid> <signature path> ...\n"
+ , RTPathFilename(pszArg0));
+
+ if (!pszCommand || !strcmp(pszCommand, "initnvram"))
+ RTPrintf("Usage: %s initnvram <nvram path> <init options>\n"
+ "\n"
+ "Init Options:\n"
+ " --pk <path>\n"
+ " Init the PK with the given signature.\n"
+ " --pk-owner <uuid>\n"
+ " Set the given UUID as the owner of the PK.\n"
+ " --kek <path>\n"
+ " Init the KEK with the given signature.\n"
+ " --kek-owner <uuid>\n"
+ " Set the given UUID as the owner of the KEK.\n"
+ " --db <x509|sha256|rsa2048>:<owner uuid>:<path>\n"
+ " Adds the given signature with the owner UUID and type to the db, can be given multiple times.\n"
+ " --secure-boot <on|off>\n"
+ " Enables or disables secure boot\n"
+ , RTPathFilename(pszArg0));
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEFISIGTYPE rtEfiSigDbGetTypeById(const char *pszId)
+{
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aId2SigType); i++)
+ if (!strcmp(pszId, g_aId2SigType[i].pszId))
+ return g_aId2SigType[i].enmType;
+
+ return RTEFISIGTYPE_INVALID;
+}
+
+
+/**
+ * Opens the specified signature database, returning an VFS file handle on success.
+ *
+ * @returns IPRT status code.
+ * @param pszPath Path to the signature database.
+ * @param phVfsFile Where to return the VFS file handle on success.
+ */
+static int rtEfiSigDbOpen(const char *pszPath, PRTVFSFILE phVfsFile)
+{
+ int rc;
+
+ if (RTVfsChainIsSpec(pszPath))
+ {
+ RTVFSOBJ hVfsObj;
+ rc = RTVfsChainOpenObj(pszPath, RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_CREATE_NOTHING | RTPATH_F_ON_LINK,
+ &hVfsObj, NULL, NULL);
+ if ( RT_SUCCESS(rc)
+ && RTVfsObjGetType(hVfsObj) == RTVFSOBJTYPE_FILE)
+ {
+ *phVfsFile = RTVfsObjToFile(hVfsObj);
+ RTVfsObjRelease(hVfsObj);
+ }
+ else
+ {
+ RTPrintf("'%s' doesn't point to a file\n", pszPath);
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ rc = RTVfsFileOpenNormal(pszPath, RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ phVfsFile);
+
+ return rc;
+}
+
+
+/**
+ * Signature database enumeration callback.
+ */
+static DECLCALLBACK(int) rtEfiSgDbEnum(RTEFISIGDB hEfiSigDb, RTEFISIGTYPE enmSigType, PCRTUUID pUuidOwner,
+ const void *pvSig, size_t cbSig, void *pvUser)
+{
+ RT_NOREF(hEfiSigDb, pvUser);
+
+ uint32_t *pidxSig = (uint32_t *)pvUser;
+
+ RTPrintf("%02u: %s\n", (*pidxSig)++, RTEfiSigDbTypeStringify(enmSigType));
+ RTPrintf(" Owner: %RTuuid\n", pUuidOwner);
+ RTPrintf(" Signature:\n"
+ "%.*Rhxd\n\n", cbSig, pvSig);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Handles the 'list' command.
+ *
+ * @returns Program exit code.
+ * @param pszArg0 The program name.
+ * @param cArgs The number of arguments to the 'add' command.
+ * @param papszArgs The argument vector, starting after 'add'.
+ */
+static RTEXITCODE rtEfiSgDbCmdList(const char *pszArg0, int cArgs, char **papszArgs)
+{
+ RT_NOREF(pszArg0);
+
+ if (!cArgs)
+ {
+ RTPrintf("An input path must be given\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ int rc = rtEfiSigDbOpen(papszArgs[0], &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ RTEFISIGDB hEfiSigDb;
+ rc = RTEfiSigDbCreate(&hEfiSigDb);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxSig = 0;
+
+ rc = RTEfiSigDbAddFromExistingDb(hEfiSigDb, hVfsFile);
+ if (RT_SUCCESS(rc))
+ RTEfiSigDbEnum(hEfiSigDb, rtEfiSgDbEnum, &idxSig);
+ else
+ {
+ RTPrintf("Loading the signature database failed with %Rrc\n", rc);
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ RTEfiSigDbDestroy(hEfiSigDb);
+ }
+ else
+ {
+ RTPrintf("Creating the signature database failed with %Rrc\n", rc);
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ rcExit = RTEXITCODE_FAILURE;
+
+ return rcExit;
+}
+
+
+/**
+ * Handles the 'add' command.
+ *
+ * @returns Program exit code.
+ * @param pszArg0 The program name.
+ * @param cArgs The number of arguments to the 'add' command.
+ * @param papszArgs The argument vector, starting after 'add'.
+ */
+static RTEXITCODE rtEfiSgDbCmdAdd(const char *pszArg0, int cArgs, char **papszArgs)
+{
+ RT_NOREF(pszArg0);
+
+ if (!cArgs)
+ {
+ RTPrintf("The signature database path is missing\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ int rc = rtEfiSigDbOpen(papszArgs[0], &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ RTEFISIGDB hEfiSigDb;
+ rc = RTEfiSigDbCreate(&hEfiSigDb);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbSigDb = 0;
+ rc = RTVfsFileQuerySize(hVfsFile, &cbSigDb);
+ if ( RT_SUCCESS(rc)
+ && cbSigDb)
+ rc = RTEfiSigDbAddFromExistingDb(hEfiSigDb, hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ cArgs--;
+ papszArgs++;
+
+ while (cArgs >= 3)
+ {
+ RTEFISIGTYPE enmSigType = rtEfiSigDbGetTypeById(papszArgs[0]);
+ const char *pszUuidOwner = papszArgs[1];
+ const char *pszSigDataPath = papszArgs[2];
+
+ if (enmSigType == RTEFISIGTYPE_INVALID)
+ {
+ RTPrintf("Signature type '%s' is not known\n", papszArgs[0]);
+ break;
+ }
+
+ RTUUID UuidOwner;
+ rc = RTUuidFromStr(&UuidOwner, pszUuidOwner);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("UUID '%s' is malformed\n", pszUuidOwner);
+ break;
+ }
+
+ RTVFSFILE hVfsFileSigData = NIL_RTVFSFILE;
+ rc = rtEfiSigDbOpen(pszSigDataPath, &hVfsFileSigData);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("Opening '%s' failed with %Rrc\n", pszSigDataPath, rc);
+ break;
+ }
+
+ rc = RTEfiSigDbAddSignatureFromFile(hEfiSigDb, enmSigType, &UuidOwner, hVfsFileSigData);
+ RTVfsFileRelease(hVfsFileSigData);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("Adding signature data from '%s' failed with %Rrc\n", pszSigDataPath, rc);
+ break;
+ }
+ papszArgs += 3;
+ cArgs -= 3;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (!cArgs)
+ {
+ rc = RTVfsFileSeek(hVfsFile, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(rc);
+
+ rc = RTEfiSigDbWriteToFile(hEfiSigDb, hVfsFile);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("Writing the updated signature database failed with %Rrc\n", rc);
+ rcExit = RTEXITCODE_FAILURE;
+ }
+ }
+ else
+ {
+ RTPrintf("Incomplete list of entries to add given\n");
+ rcExit = RTEXITCODE_FAILURE;
+ }
+ }
+ }
+ else
+ {
+ RTPrintf("Loading the signature database failed with %Rrc\n", rc);
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ RTEfiSigDbDestroy(hEfiSigDb);
+ }
+ else
+ {
+ RTPrintf("Creating the signature database failed with %Rrc\n", rc);
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ rcExit = RTEXITCODE_FAILURE;
+
+ return rcExit;
+}
+
+
+/**
+ * Adds the given signature to the given database.
+ *
+ * @returns IPRT status code.
+ * @param hEfiSigDb The EFI signature database handle.
+ * @param pszSigPath The signature data path.
+ * @param pszSigType The signature type.
+ * @param pszUuidOwner The owner UUID.
+ */
+static int rtEfiSigDbAddSig(RTEFISIGDB hEfiSigDb, const char *pszSigPath, const char *pszSigType, const char *pszUuidOwner)
+{
+ RTEFISIGTYPE enmSigType = rtEfiSigDbGetTypeById(pszSigType);
+ if (enmSigType == RTEFISIGTYPE_INVALID)
+ return RTMsgErrorRc(VERR_INVALID_PARAMETER, "Signature type '%s' is unknown!", pszSigType);
+
+ RTUUID UuidOwner;
+ int rc = RTUuidFromStr(&UuidOwner, pszUuidOwner);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(VERR_INVALID_PARAMETER, "Owner UUID '%s' is malformed!", pszUuidOwner);
+
+ RTVFSFILE hVfsFileSigData = NIL_RTVFSFILE;
+ rc = rtEfiSigDbOpen(pszSigPath, &hVfsFileSigData);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Opening '%s' failed: %Rrc", pszSigPath, rc);
+
+ rc = RTEfiSigDbAddSignatureFromFile(hEfiSigDb, enmSigType, &UuidOwner, hVfsFileSigData);
+ RTVfsFileRelease(hVfsFileSigData);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Adding signature '%s' failed: %Rrc", pszSigPath, rc);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the given attributes for the given EFI variable store variable.
+ *
+ * @returns IPRT status code.
+ * @param hVfsVarStore Handle of the EFI variable store VFS.
+ * @param pszVar The variable to set the attributes for.
+ * @param fAttr The attributes to set, see EFI_VAR_HEADER_ATTR_XXX.
+ */
+static int rtEfiSigDbSetVarAttr(RTVFS hVfsVarStore, const char *pszVar, uint32_t fAttr)
+{
+ char szVarPath[_1K];
+ ssize_t cch = RTStrPrintf2(szVarPath, sizeof(szVarPath), "/raw/%s/attr", pszVar);
+ Assert(cch > 0); RT_NOREF(cch);
+
+ RTVFSFILE hVfsFileAttr = NIL_RTVFSFILE;
+ int rc = RTVfsFileOpen(hVfsVarStore, szVarPath,
+ RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ &hVfsFileAttr);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fAttrLe = RT_H2LE_U32(fAttr);
+ rc = RTVfsFileWrite(hVfsFileAttr, &fAttrLe, sizeof(fAttrLe), NULL /*pcbWritten*/);
+ RTVfsFileRelease(hVfsFileAttr);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Adds the given variable to the variable store.
+ *
+ * @returns IPRT status code.
+ * @param hVfsVarStore Handle of the EFI variable store VFS.
+ * @param pGuid The EFI GUID of the variable.
+ * @param pszVar The variable name.
+ * @param fAttr Attributes for the variable.
+ * @param phVfsFile Where to return the VFS file handle to the created variable on success.
+ */
+static int rtEfiSigDbVarStoreAddVar(RTVFS hVfsVarStore, PCEFI_GUID pGuid, const char *pszVar, uint32_t fAttr, PRTVFSFILE phVfsFile)
+{
+ RTUUID UuidVar;
+ RTEfiGuidToUuid(&UuidVar, pGuid);
+
+ char szVarPath[_1K];
+ ssize_t cch = RTStrPrintf2(szVarPath, sizeof(szVarPath), "/by-uuid/%RTuuid/%s", &UuidVar, pszVar);
+ Assert(cch > 0); RT_NOREF(cch);
+
+ int rc = RTVfsFileOpen(hVfsVarStore, szVarPath,
+ RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ phVfsFile);
+ if ( rc == VERR_PATH_NOT_FOUND
+ || rc == VERR_FILE_NOT_FOUND)
+ {
+ /*
+ * Try to create the owner GUID of the variable by creating the appropriate directory,
+ * ignore error if it exists already.
+ */
+ RTVFSDIR hVfsDirRoot = NIL_RTVFSDIR;
+ rc = RTVfsOpenRoot(hVfsVarStore, &hVfsDirRoot);
+ if (RT_SUCCESS(rc))
+ {
+ char szGuidPath[_1K];
+ cch = RTStrPrintf2(szGuidPath, sizeof(szGuidPath), "by-uuid/%RTuuid", &UuidVar);
+ Assert(cch > 0);
+
+ RTVFSDIR hVfsDirGuid = NIL_RTVFSDIR;
+ rc = RTVfsDirCreateDir(hVfsDirRoot, szGuidPath, 0755, 0 /*fFlags*/, &hVfsDirGuid);
+ if (RT_SUCCESS(rc))
+ RTVfsDirRelease(hVfsDirGuid);
+ else if (rc == VERR_ALREADY_EXISTS)
+ rc = VINF_SUCCESS;
+
+ RTVfsDirRelease(hVfsDirRoot);
+ }
+ else
+ rc = RTMsgErrorRc(rc, "Opening variable storage root directory failed: %Rrc", rc);
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileOpen(hVfsVarStore, szVarPath,
+ RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_CREATE,
+ phVfsFile);
+ if (RT_SUCCESS(rc))
+ rc = rtEfiSigDbSetVarAttr(hVfsVarStore, pszVar, fAttr);
+ }
+
+ if (RT_FAILURE(rc))
+ rc = RTMsgErrorRc(rc, "Creating the variable '%s' failed: %Rrc", pszVar, rc);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Creates the given variable and sets the data.
+ *
+ * @returns IPRT status code.
+ * @param hVfsVarStore Handle of the EFI variable store VFS.
+ * @param pGuid The EFI GUID of the variable.
+ * @param pszVar The variable name.
+ * @param fAttr Attributes for the variable.
+ * @param pvBuf The data to write.
+ * @param cbBuf Number of bytes of data.
+ */
+static int rtEfiSigDbVarStoreSetVar(RTVFS hVfsVarStore, PCEFI_GUID pGuid, const char *pszVar, uint32_t fAttr,
+ const void *pvBuf, size_t cbBuf)
+{
+ RTVFSFILE hVfsFileVar = NIL_RTVFSFILE;
+ int rc = rtEfiSigDbVarStoreAddVar(hVfsVarStore, pGuid, pszVar, fAttr, &hVfsFileVar);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileWrite(hVfsFileVar, pvBuf, cbBuf, NULL /*pcbWritten*/);
+ if (RT_FAILURE(rc))
+ rc = RTMsgErrorRc(rc, "Writing variable '%s' failed: %Rrc", pszVar, rc);
+ RTVfsFileRelease(hVfsFileVar);
+ }
+ else
+ rc = RTMsgErrorRc(rc, "Creating variable '%s' failed: %Rrc", pszVar, rc);
+
+ return rc;
+}
+
+
+/**
+ * Adds the given signature to the given signature database of the given EFI variable store.
+ *
+ * @returns IPRT status code.
+ * @param hVfsVarStore Handle of the EFI variable store VFS.
+ * @param pGuid The EFI GUID of the variable.
+ * @param pszDb The signature database to update.
+ * @param fWipeDbBefore Flag whether to wipe the database before adding the signature.
+ * @param cSigs Number of signatures following.
+ * @param ... A triple of signature path, signature type and owner uuid string pointers for each
+ * signature.
+ */
+static int rtEfiSigDbVarStoreAddToDb(RTVFS hVfsVarStore, PCEFI_GUID pGuid, const char *pszDb, bool fWipeDbBefore, uint32_t cSigs,
+ ... /*const char *pszSigPath, const char *pszSigType, const char *pszUuidOwner*/)
+{
+ RTVFSFILE hVfsFileSigDb = NIL_RTVFSFILE;
+ int rc = rtEfiSigDbVarStoreAddVar(hVfsVarStore, pGuid, pszDb,
+ EFI_VAR_HEADER_ATTR_NON_VOLATILE
+ | EFI_VAR_HEADER_ATTR_BOOTSERVICE_ACCESS
+ | EFI_VAR_HEADER_ATTR_RUNTIME_ACCESS
+ | EFI_AUTH_VAR_HEADER_ATTR_TIME_BASED_AUTH_WRITE_ACCESS,
+ &hVfsFileSigDb);
+ if (RT_SUCCESS(rc))
+ {
+ RTEFISIGDB hEfiSigDb;
+ rc = RTEfiSigDbCreate(&hEfiSigDb);
+ if (RT_SUCCESS(rc))
+ {
+ if (!fWipeDbBefore)
+ rc = RTEfiSigDbAddFromExistingDb(hEfiSigDb, hVfsFileSigDb);
+ if (RT_SUCCESS(rc))
+ {
+ va_list VarArgs;
+ va_start(VarArgs, cSigs);
+
+ while ( cSigs--
+ && RT_SUCCESS(rc))
+ {
+ const char *pszSigPath = va_arg(VarArgs, const char *);
+ const char *pszSigType = va_arg(VarArgs, const char *);
+ const char *pszUuidOwner = va_arg(VarArgs, const char *);
+
+ rc = rtEfiSigDbAddSig(hEfiSigDb, pszSigPath, pszSigType, pszUuidOwner);
+ }
+
+ va_end(VarArgs);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileSeek(hVfsFileSigDb, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(rc);
+
+ rc = RTEfiSigDbWriteToFile(hEfiSigDb, hVfsFileSigDb);
+ if (RT_FAILURE(rc))
+ rc = RTMsgErrorRc(rc, "Writing updated signature database failed: %Rrc", rc);
+ }
+ }
+ else
+ rc = RTMsgErrorRc(rc, "Loading signature database failed: %Rrc", rc);
+
+ RTEfiSigDbDestroy(hEfiSigDb);
+ }
+ else
+ rc = RTMsgErrorRc(rc, "Creating signature database failed: %Rrc", rc);
+
+ RTVfsFileRelease(hVfsFileSigDb);
+ }
+ else
+ rc = RTMsgErrorRc(rc, "Opening signature database '%s' failed: %Rrc", pszDb, rc);
+
+ return rc;
+}
+
+
+/**
+ * Handles the 'initnvram' command.
+ *
+ * @returns Program exit code.
+ * @param pszArg0 The program name.
+ * @param cArgs The number of arguments to the 'add' command.
+ * @param papszArgs The argument vector, starting after 'add'.
+ */
+static RTEXITCODE rtEfiSgDbCmdInitNvram(const char *pszArg0, int cArgs, char **papszArgs)
+{
+ RT_NOREF(pszArg0);
+ RTERRINFOSTATIC ErrInfo;
+
+ /*
+ * Parse the command line.
+ */
+ static RTGETOPTDEF const s_aOptions[] =
+ {
+ { "--pk", 'p', RTGETOPT_REQ_STRING },
+ { "--pk-owner", 'o', RTGETOPT_REQ_STRING },
+ { "--kek", 'k', RTGETOPT_REQ_STRING },
+ { "--kek-owner", 'w', RTGETOPT_REQ_STRING },
+ { "--db", 'd', RTGETOPT_REQ_STRING },
+ { "--secure-boot", 's', RTGETOPT_REQ_BOOL_ONOFF }
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ RTGETOPTSTATE State;
+ int rc = RTGetOptInit(&State, cArgs, papszArgs, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc);
+
+ const char *pszNvram = NULL;
+ const char *pszPkPath = NULL;
+ const char *pszUuidPkOwner = NULL;
+ const char *pszKekPath = NULL;
+ const char *pszUuidKekOwner = NULL;
+ const char **papszDb = NULL;
+ bool fSecureBoot = true;
+ bool fSetSecureBoot = false;
+ uint32_t cDbEntries = 0;
+ uint32_t cDbEntriesMax = 0;
+
+ RTGETOPTUNION ValueUnion;
+ int chOpt;
+ while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0)
+ {
+ switch (chOpt)
+ {
+ case 'p':
+ pszPkPath = ValueUnion.psz;
+ break;
+ case 'o':
+ pszUuidPkOwner = ValueUnion.psz;
+ break;
+
+ case 'k':
+ pszKekPath = ValueUnion.psz;
+ break;
+ case 'w':
+ pszUuidKekOwner = ValueUnion.psz;
+ break;
+
+ case 'd':
+ {
+ if (cDbEntries == cDbEntriesMax)
+ {
+ uint32_t cDbEntriesMaxNew = cDbEntriesMax + 10;
+ const char **papszDbNew = (const char **)RTMemRealloc(papszDb, cDbEntriesMaxNew * sizeof(const char *));
+ if (!papszDbNew)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory allocating memory for '%s'", ValueUnion.psz);
+
+ papszDb = papszDbNew;
+ cDbEntriesMax = cDbEntriesMaxNew;
+ }
+
+ papszDb[cDbEntries++] = ValueUnion.psz;
+ break;
+ }
+
+ case 's':
+ fSecureBoot = ValueUnion.f;
+ fSetSecureBoot = true;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ /* The first non-option is the NVRAM file. */
+ if (!pszNvram)
+ pszNvram = ValueUnion.psz;
+ else
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid option '%s'", ValueUnion.psz);
+ break;
+
+ default:
+ return RTGetOptPrintError(chOpt, &ValueUnion);
+ }
+ }
+
+ if (!pszNvram)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The NVRAM file path is missing");
+
+ if ( pszPkPath
+ && !pszUuidPkOwner)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The PK is missing the owner UUID");
+
+ if ( pszKekPath
+ && !pszUuidKekOwner)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The KEK is missing the owner UUID");
+
+ RTVFSFILE hVfsFileNvram = NIL_RTVFSFILE;
+ rc = RTVfsFileOpenNormal(pszNvram, RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ &hVfsFileNvram);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFS hVfsEfiVarStore = NIL_RTVFS;
+ rc = RTEfiVarStoreOpenAsVfs(hVfsFileNvram, 0 /*fMntFlags*/, 0 /*fVarStoreFlags*/, &hVfsEfiVarStore, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ EFI_GUID GuidSecurityDb = EFI_IMAGE_SECURITY_DATABASE_GUID;
+ EFI_GUID GuidGlobalVar = EFI_GLOBAL_VARIABLE_GUID;
+
+ if (pszPkPath)
+ rc = rtEfiSigDbVarStoreAddToDb(hVfsEfiVarStore, &GuidGlobalVar, "PK", true /*fWipeDbBefore*/, 1 /*cSigs*/, pszPkPath, "x509", pszUuidPkOwner);
+ if ( RT_SUCCESS(rc)
+ && pszKekPath)
+ rc = rtEfiSigDbVarStoreAddToDb(hVfsEfiVarStore, &GuidGlobalVar ,"KEK", true /*fWipeDbBefore*/, 1 /*cSigs*/, pszKekPath, "x509", pszUuidKekOwner);
+
+ if ( RT_SUCCESS(rc)
+ && cDbEntries)
+ {
+ /** @todo Optimize to avoid re-opening and re-parsing the database for every entry. */
+ for (uint32_t i = 0; i < cDbEntries && RT_SUCCESS(rc); i++)
+ {
+ const char *pszDbEntry = papszDb[i];
+
+ const char *pszSigType = pszDbEntry;
+ const char *pszUuidOwner = strchr(pszSigType, ':');
+ if (pszUuidOwner)
+ pszUuidOwner++;
+ const char *pszSigPath = pszUuidOwner ? strchr(pszUuidOwner, ':') : NULL;
+ if (pszSigPath)
+ pszSigPath++;
+
+ if ( pszUuidOwner
+ && pszSigPath)
+ {
+ char *pszSigTypeFree = RTStrDupN(pszSigType, pszUuidOwner - pszSigType - 1);
+ char *pszUuidOwnerFree = RTStrDupN(pszUuidOwner, pszSigPath - pszUuidOwner - 1);
+
+ if ( pszSigTypeFree
+ && pszUuidOwnerFree)
+ rc = rtEfiSigDbVarStoreAddToDb(hVfsEfiVarStore, &GuidSecurityDb, "db",
+ i == 0 ? true : false /*fWipeDbBefore*/,
+ 1 /*cSigs*/,
+ pszSigPath, pszSigTypeFree, pszUuidOwnerFree);
+ else
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "Out of memory!");
+
+ if (pszSigTypeFree)
+ RTStrFree(pszSigTypeFree);
+ if (pszUuidOwnerFree)
+ RTStrFree(pszUuidOwnerFree);
+ }
+ else
+ rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "DB entry '%s' is malformed!", pszDbEntry);
+ }
+
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Initializing the NVRAM '%s' failed: %Rrc", pszNvram, rc);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fSetSecureBoot)
+ {
+ EFI_GUID GuidSecureBootEnable = EFI_SECURE_BOOT_ENABLE_DISABLE_GUID;
+ uint8_t bVar = fSecureBoot ? 0x1 : 0x0;
+ rtEfiSigDbVarStoreSetVar(hVfsEfiVarStore, &GuidSecureBootEnable, "SecureBootEnable",
+ EFI_VAR_HEADER_ATTR_NON_VOLATILE
+ | EFI_VAR_HEADER_ATTR_BOOTSERVICE_ACCESS
+ | EFI_VAR_HEADER_ATTR_RUNTIME_ACCESS,
+ &bVar, sizeof(bVar));
+ }
+
+ RTVfsRelease(hVfsEfiVarStore);
+ }
+
+ RTVfsFileRelease(hVfsFileNvram);
+ }
+
+ if (papszDb)
+ RTMemFree(papszDb);
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Switch on the command.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SYNTAX;
+ if (argc < 2)
+ rtEfiSigDbUsage(argv[0], NULL);
+ else if (!strcmp(argv[1], "list"))
+ rcExit = rtEfiSgDbCmdList(argv[0], argc - 2, argv + 2);
+ else if (!strcmp(argv[1], "add"))
+ rcExit = rtEfiSgDbCmdAdd(argv[0], argc - 2, argv + 2);
+ else if (!strcmp(argv[1], "initnvram"))
+ rcExit = rtEfiSgDbCmdInitNvram(argv[0], argc - 2, argv + 2);
+ else if ( !strcmp(argv[1], "-h")
+ || !strcmp(argv[1], "-?")
+ || !strcmp(argv[1], "--help"))
+ rcExit = rtEfiSigDbUsage(argv[0], NULL);
+ else if ( !strcmp(argv[1], "-V")
+ || !strcmp(argv[1], "--version"))
+ rcExit = rtEfiSigDbVersion();
+ else
+ RTMsgError("Unknown command: '%s'", argv[1]);
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTFtpServer.cpp b/src/VBox/Runtime/tools/RTFtpServer.cpp
new file mode 100644
index 00000000..643fa7cd
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTFtpServer.cpp
@@ -0,0 +1,666 @@
+/* $Id: RTFtpServer.cpp $ */
+/** @file
+ * IPRT - Utility for running a (simple) FTP server.
+ *
+ * Use this setup to best see what's going on:
+ * VBOX_LOG=rt_ftp=~0
+ * VBOX_LOG_DEST="nofile stderr"
+ * VBOX_LOG_FLAGS="unbuffered enabled thread msprog"
+ *
+ */
+
+/*
+ * Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <signal.h>
+
+#include <iprt/ftp.h>
+
+#include <iprt/net.h> /* To make use of IPv4Addr in RTGETOPTUNION. */
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/vfs.h>
+
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Definitations *
+*********************************************************************************************************************************/
+typedef struct FTPSERVERDATA
+{
+ /** The absolute path of the FTP server's root directory. */
+ char szPathRootAbs[RTPATH_MAX];
+ /** The relative current working directory (CWD) to szRootDir. */
+ char szCWD[RTPATH_MAX];
+} FTPSERVERDATA;
+typedef FTPSERVERDATA *PFTPSERVERDATA;
+
+/**
+ * Enumeration specifying the VFS handle type of the FTP server.
+ */
+typedef enum FTPSERVERVFSHANDLETYPE
+{
+ FTPSERVERVFSHANDLETYPE_INVALID = 0,
+ FTPSERVERVFSHANDLETYPE_FILE,
+ FTPSERVERVFSHANDLETYPE_DIR,
+ /** The usual 32-bit hack. */
+ FTPSERVERVFSHANDLETYPE_32BIT_HACK = 0x7fffffff
+} FTPSERVERVFSHANDLETYPE;
+
+/**
+ * Structure for keeping a VFS handle of the FTP server.
+ */
+typedef struct FTPSERVERVFSHANDLE
+{
+ /** The type of the handle, stored in the union below. */
+ FTPSERVERVFSHANDLETYPE enmType;
+ union
+ {
+ /** The VFS (chain) handle to use for this file. */
+ RTVFSFILE hVfsFile;
+ /** The VFS (chain) handle to use for this directory. */
+ RTVFSDIR hVfsDir;
+ } u;
+} FTPSERVERVFSHANDLE;
+typedef FTPSERVERVFSHANDLE *PFTPSERVERVFSHANDLE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Set by the signal handler when the FTP server shall be terminated. */
+static volatile bool g_fCanceled = false;
+static FTPSERVERDATA g_FtpServerData;
+
+
+#ifdef RT_OS_WINDOWS
+static BOOL WINAPI signalHandler(DWORD dwCtrlType) RT_NOTHROW_DEF
+{
+ bool fEventHandled = FALSE;
+ switch (dwCtrlType)
+ {
+ /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
+ * via GenerateConsoleCtrlEvent(). */
+ case CTRL_BREAK_EVENT:
+ case CTRL_CLOSE_EVENT:
+ case CTRL_C_EVENT:
+ ASMAtomicWriteBool(&g_fCanceled, true);
+ fEventHandled = TRUE;
+ break;
+ default:
+ break;
+ /** @todo Add other events here. */
+ }
+
+ return fEventHandled;
+}
+#else /* !RT_OS_WINDOWS */
+/**
+ * Signal handler that sets g_fCanceled.
+ *
+ * This can be executed on any thread in the process, on Windows it may even be
+ * a thread dedicated to delivering this signal. Don't do anything
+ * unnecessary here.
+ */
+static void signalHandler(int iSignal) RT_NOTHROW_DEF
+{
+ NOREF(iSignal);
+ ASMAtomicWriteBool(&g_fCanceled, true);
+}
+#endif
+
+/**
+ * Installs a custom signal handler to get notified
+ * whenever the user wants to intercept the program.
+ *
+ * @todo Make this handler available for all VBoxManage modules?
+ */
+static int signalHandlerInstall(void)
+{
+ g_fCanceled = false;
+
+ int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)signalHandler, TRUE /* Add handler */))
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ RTMsgError("Unable to install console control handler, rc=%Rrc\n", rc);
+ }
+#else
+ signal(SIGINT, signalHandler);
+ signal(SIGTERM, signalHandler);
+# ifdef SIGBREAK
+ signal(SIGBREAK, signalHandler);
+# endif
+#endif
+ return rc;
+}
+
+/**
+ * Uninstalls a previously installed signal handler.
+ */
+static int signalHandlerUninstall(void)
+{
+ int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ RTMsgError("Unable to uninstall console control handler, rc=%Rrc\n", rc);
+ }
+#else
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+# ifdef SIGBREAK
+ signal(SIGBREAK, SIG_DFL);
+# endif
+#endif
+ return rc;
+}
+
+static DECLCALLBACK(int) onUserConnect(PRTFTPCALLBACKDATA pData, const char *pcszUser)
+{
+ RT_NOREF(pData, pcszUser);
+
+ RTPrintf("User '%s' connected\n", pcszUser);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onUserAuthenticate(PRTFTPCALLBACKDATA pData, const char *pcszUser, const char *pcszPassword)
+{
+ RT_NOREF(pData, pcszUser, pcszPassword);
+
+ RTPrintf("Authenticating user '%s' ...\n", pcszUser);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onUserDisonnect(PRTFTPCALLBACKDATA pData, const char *pcszUser)
+{
+ RT_NOREF(pData);
+
+ RTPrintf("User '%s' disconnected\n", pcszUser);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onFileOpen(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint32_t fMode, void **ppvHandle)
+{
+ PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(FTPSERVERDATA));
+
+ PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)RTMemAllocZ(sizeof(FTPSERVERVFSHANDLE));
+ if (!pHandle)
+ return VERR_NO_MEMORY;
+
+ char *pszPathAbs = NULL;
+ if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
+ return VERR_NO_MEMORY;
+
+ int rc = RTVfsChainOpenFile(pszPathAbs, fMode, &pHandle->u.hVfsFile, NULL /*poffError */, NULL /* pErrInfo */);
+ if (RT_SUCCESS(rc))
+ {
+ pHandle->enmType = FTPSERVERVFSHANDLETYPE_FILE;
+
+ *ppvHandle = pHandle;
+ }
+ else
+ RTMemFree(pHandle);
+
+ RTStrFree(pszPathAbs);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) onFileRead(PRTFTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbToRead, size_t *pcbRead)
+{
+ RT_NOREF(pData);
+
+ PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
+ AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
+ AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_FILE, VERR_INVALID_PARAMETER);
+
+ return RTVfsFileRead(pHandle->u.hVfsFile, pvBuf, cbToRead, pcbRead);
+}
+
+static DECLCALLBACK(int) onFileClose(PRTFTPCALLBACKDATA pData, void *pvHandle)
+{
+ RT_NOREF(pData);
+
+ PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
+ AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
+ AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_FILE, VERR_INVALID_PARAMETER);
+
+ int rc = RTVfsFileRelease(pHandle->u.hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ RTMemFree(pvHandle);
+ pvHandle = NULL;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) onFileGetSize(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint64_t *puSize)
+{
+ PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(FTPSERVERDATA));
+
+ char *pszStat = NULL;
+ if (RTStrAPrintf(&pszStat, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
+ return VERR_NO_MEMORY;
+
+ RTPrintf("Retrieving file size for '%s' ...\n", pcszPath);
+
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pcszPath,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileQuerySize(hFile, puSize);
+ if (RT_SUCCESS(rc))
+ RTPrintf("File size is: %RU64\n", *puSize);
+ RTFileClose(hFile);
+ }
+
+ RTStrFree(pszStat);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) onFileStat(PRTFTPCALLBACKDATA pData, const char *pcszPath, PRTFSOBJINFO pFsObjInfo)
+{
+ PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(FTPSERVERDATA));
+
+ char *pszStat = NULL;
+ if (RTStrAPrintf(&pszStat, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
+ return VERR_NO_MEMORY;
+
+ RTPrintf("Stat for '%s'\n", pszStat);
+
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszStat,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ RTFSOBJINFO fsObjInfo;
+ rc = RTFileQueryInfo(hFile, &fsObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (pFsObjInfo)
+ *pFsObjInfo = fsObjInfo;
+ }
+
+ RTFileClose(hFile);
+ }
+
+ RTStrFree(pszStat);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) onPathSetCurrent(PRTFTPCALLBACKDATA pData, const char *pcszCWD)
+{
+ PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(FTPSERVERDATA));
+
+ RTPrintf("Setting current directory to '%s'\n", pcszCWD);
+
+ /** @todo BUGBUG Santiy checks! */
+
+ return RTStrCopy(pThis->szCWD, sizeof(pThis->szCWD), pcszCWD);
+}
+
+static DECLCALLBACK(int) onPathGetCurrent(PRTFTPCALLBACKDATA pData, char *pszPWD, size_t cbPWD)
+{
+ PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(FTPSERVERDATA));
+
+ RTPrintf("Current directory is: '%s'\n", pThis->szCWD);
+
+ return RTStrCopy(pszPWD, cbPWD, pThis->szCWD);
+}
+
+static DECLCALLBACK(int) onPathUp(PRTFTPCALLBACKDATA pData)
+{
+ RT_NOREF(pData);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onDirOpen(PRTFTPCALLBACKDATA pData, const char *pcszPath, void **ppvHandle)
+{
+ PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(FTPSERVERDATA));
+
+ PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)RTMemAllocZ(sizeof(FTPSERVERVFSHANDLE));
+ if (!pHandle)
+ return VERR_NO_MEMORY;
+
+ /* Construct absolute path. */
+ char *pszPathAbs = NULL;
+ if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
+ return VERR_NO_MEMORY;
+
+ RTPrintf("Opening directory '%s'\n", pszPathAbs);
+
+ int rc = RTVfsChainOpenDir(pszPathAbs, 0 /*fFlags*/, &pHandle->u.hVfsDir, NULL /* poffError */, NULL /* pErrInfo */);
+ if (RT_SUCCESS(rc))
+ {
+ pHandle->enmType = FTPSERVERVFSHANDLETYPE_DIR;
+
+ *ppvHandle = pHandle;
+ }
+ else
+ {
+ RTMemFree(pHandle);
+ }
+
+ RTStrFree(pszPathAbs);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) onDirClose(PRTFTPCALLBACKDATA pData, void *pvHandle)
+{
+ RT_NOREF(pData);
+
+ PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
+ AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
+ AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_DIR, VERR_INVALID_PARAMETER);
+
+ RTVfsDirRelease(pHandle->u.hVfsDir);
+
+ RTMemFree(pHandle);
+ pHandle = NULL;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onDirRead(PRTFTPCALLBACKDATA pData, void *pvHandle, char **ppszEntry,
+ PRTFSOBJINFO pInfo, char **ppszOwner, char **ppszGroup, char **ppszTarget)
+{
+ RT_NOREF(pData);
+ RT_NOREF(ppszTarget); /* No symlinks yet */
+
+ PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
+ AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
+ AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_DIR, VERR_INVALID_PARAMETER);
+
+ size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
+ PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (!pDirEntry)
+ return VERR_NO_MEMORY;
+
+ int rc;
+
+ for (;;)
+ {
+ size_t cbDirEntry = cbDirEntryAlloced;
+ rc = RTVfsDirReadEx(pHandle->u.hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ RTMemTmpFree(pDirEntry);
+ cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
+ pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (pDirEntry)
+ continue;
+ }
+ else if (rc != VERR_NO_MORE_FILES)
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pDirEntry->Info.Attr.u.Unix.uid != NIL_RTUID)
+ {
+ RTFSOBJINFO OwnerInfo;
+ rc = RTVfsDirQueryPathInfo(pHandle->u.hVfsDir,
+ pDirEntry->szName, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK);
+ if ( RT_SUCCESS(rc)
+ && OwnerInfo.Attr.u.UnixOwner.szName[0])
+ {
+ *ppszOwner = RTStrDup(&OwnerInfo.Attr.u.UnixOwner.szName[0]);
+ if (!*ppszOwner)
+ rc = VERR_NO_MEMORY;
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pDirEntry->Info.Attr.u.Unix.gid != NIL_RTGID)
+ {
+ RTFSOBJINFO GroupInfo;
+ rc = RTVfsDirQueryPathInfo(pHandle->u.hVfsDir,
+ pDirEntry->szName, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK);
+ if ( RT_SUCCESS(rc)
+ && GroupInfo.Attr.u.UnixGroup.szName[0])
+ {
+ *ppszGroup = RTStrDup(&GroupInfo.Attr.u.UnixGroup.szName[0]);
+ if (!*ppszGroup)
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+
+ *ppszEntry = RTStrDup(pDirEntry->szName);
+ AssertPtrReturn(*ppszEntry, VERR_NO_MEMORY);
+
+ *pInfo = pDirEntry->Info;
+
+ break;
+
+ } /* for */
+
+ RTMemTmpFree(pDirEntry);
+ pDirEntry = NULL;
+
+ return rc;
+}
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /* Use some sane defaults. */
+ char szAddress[64] = "localhost";
+ uint16_t uPort = 2121;
+
+ RT_ZERO(g_FtpServerData);
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--address", 'a', RTGETOPT_REQ_IPV4ADDR }, /** @todo Use a string for DNS hostnames? */
+ /** @todo Implement IPv6 support? */
+ { "--port", 'p', RTGETOPT_REQ_UINT16 },
+ { "--root-dir", 'r', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ unsigned uVerbosityLevel = 1;
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 'a':
+ RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8", /** @todo Improve this. */
+ ValueUnion.IPv4Addr.au8[0], ValueUnion.IPv4Addr.au8[1], ValueUnion.IPv4Addr.au8[2], ValueUnion.IPv4Addr.au8[3]);
+ break;
+
+ case 'p':
+ uPort = ValueUnion.u16;
+ break;
+
+ case 'r':
+ RTStrCopy(g_FtpServerData.szPathRootAbs, sizeof(g_FtpServerData.szPathRootAbs), ValueUnion.psz);
+ break;
+
+ case 'v':
+ uVerbosityLevel++;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"
+ " -a, --address (default: localhost)\n"
+ " Specifies the address to use for listening.\n"
+ " -p, --port (default: 2121)\n"
+ " Specifies the port to use for listening.\n"
+ " -r, --root-dir (default: current dir)\n"
+ " Specifies the root directory being served.\n"
+ " -v, --verbose\n"
+ " Controls the verbosity level.\n"
+ " -h, -?, --help\n"
+ " Display this help text and exit successfully.\n"
+ " -V, --version\n"
+ " Display the revision and exit successfully.\n"
+ , RTPathFilename(argv[0]));
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("$Revision: 155244 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ if (!strlen(g_FtpServerData.szPathRootAbs))
+ {
+ /* By default use the current directory as serving root directory. */
+ rc = RTPathGetCurrent(g_FtpServerData.szPathRootAbs, sizeof(g_FtpServerData.szPathRootAbs));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Retrieving current directory failed: %Rrc", rc);
+ }
+
+ /* Initialize CWD. */
+ RTStrPrintf2(g_FtpServerData.szCWD, sizeof(g_FtpServerData.szCWD), "/");
+
+ /* Install signal handler. */
+ rc = signalHandlerInstall();
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create the FTP server instance.
+ */
+ RTFTPSERVERCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+
+ Callbacks.pfnOnUserConnect = onUserConnect;
+ Callbacks.pfnOnUserAuthenticate = onUserAuthenticate;
+ Callbacks.pfnOnUserDisconnect = onUserDisonnect;
+ Callbacks.pfnOnFileOpen = onFileOpen;
+ Callbacks.pfnOnFileRead = onFileRead;
+ Callbacks.pfnOnFileClose = onFileClose;
+ Callbacks.pfnOnFileGetSize = onFileGetSize;
+ Callbacks.pfnOnFileStat = onFileStat;
+ Callbacks.pfnOnPathSetCurrent = onPathSetCurrent;
+ Callbacks.pfnOnPathGetCurrent = onPathGetCurrent;
+ Callbacks.pfnOnPathUp = onPathUp;
+ Callbacks.pfnOnDirOpen = onDirOpen;
+ Callbacks.pfnOnDirClose = onDirClose;
+ Callbacks.pfnOnDirRead = onDirRead;
+
+ RTFTPSERVER hFTPServer;
+ rc = RTFtpServerCreate(&hFTPServer, szAddress, uPort, &Callbacks,
+ &g_FtpServerData, sizeof(g_FtpServerData));
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Starting FTP server at %s:%RU16 ...\n", szAddress, uPort);
+ RTPrintf("Root directory is '%s'\n", g_FtpServerData.szPathRootAbs);
+
+ RTPrintf("Running FTP server ...\n");
+
+ for (;;)
+ {
+ RTThreadSleep(200);
+
+ if (g_fCanceled)
+ break;
+ }
+
+ RTPrintf("Stopping FTP server ...\n");
+
+ int rc2 = RTFtpServerDestroy(hFTPServer);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ RTPrintf("Stopped FTP server\n");
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFTPServerCreate failed: %Rrc", rc);
+
+ int rc2 = signalHandlerUninstall();
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ /* Set rcExit on failure in case we forgot to do so before. */
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTFuzzClient.cpp b/src/VBox/Runtime/tools/RTFuzzClient.cpp
new file mode 100644
index 00000000..2776a921
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTFuzzClient.cpp
@@ -0,0 +1,54 @@
+/* $Id: RTFuzzClient.cpp $ */
+/** @file
+ * IPRT - Fuzzing client utility.
+ */
+
+/*
+ * Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/fuzz.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTFuzzCmdFuzzingClient(argc, argv, NULL, NULL);
+}
+
diff --git a/src/VBox/Runtime/tools/RTFuzzMaster.cpp b/src/VBox/Runtime/tools/RTFuzzMaster.cpp
new file mode 100644
index 00000000..713b7ffe
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTFuzzMaster.cpp
@@ -0,0 +1,54 @@
+/* $Id: RTFuzzMaster.cpp $ */
+/** @file
+ * IPRT - Fuzzing master Utility.
+ */
+
+/*
+ * Copyright (C) 2018-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/fuzz.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTFuzzCmdMaster(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTGzip.cpp b/src/VBox/Runtime/tools/RTGzip.cpp
new file mode 100644
index 00000000..1f904134
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTGzip.cpp
@@ -0,0 +1,55 @@
+/* $Id: RTGzip.cpp $ */
+/** @file
+ * IPRT - GZIP Utility.
+ */
+
+/*
+ * Copyright (C) 2010-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/zip.h>
+
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTZipGzipCmd(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTHttp.cpp b/src/VBox/Runtime/tools/RTHttp.cpp
new file mode 100644
index 00000000..7befe3e2
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTHttp.cpp
@@ -0,0 +1,188 @@
+/* $Id: RTHttp.cpp $ */
+/** @file
+ * IPRT - Utility for retriving URLs.
+ */
+
+/*
+ * Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/http.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Create a HTTP client instance.
+ */
+ RTHTTP hHttp;
+ rc = RTHttpCreate(&hHttp);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTHttpCreate failed: %Rrc", rc);
+ rc = RTHttpSetFollowRedirects(hHttp, 8);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTHttpSetFollowRedirects(,8) failed: %Rrc", rc);
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--set-header", 's', RTGETOPT_REQ_STRING },
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ const char *pszOutput = NULL;
+ unsigned uVerbosityLevel = 1;
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 'o':
+ pszOutput = ValueUnion.psz;
+ break;
+
+ case 'q':
+ uVerbosityLevel--;
+ break;
+ case 'v':
+ uVerbosityLevel++;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [options] URL0 [URL1 [...]]\n"
+ "\n"
+ "Options:\n"
+ " -o,--output=file\n"
+ " Output file. If not given, the file is displayed on stdout.\n"
+ " -q, --quiet\n"
+ " -v, --verbose\n"
+ " Controls the verbosity level.\n"
+ " -h, -?, --help\n"
+ " Display this help text and exit successfully.\n"
+ " -V, --version\n"
+ " Display the revision and exit successfully.\n"
+ , RTPathFilename(argv[0]));
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("$Revision: 155244 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ case 's':
+ {
+ char *pszColon = (char *)strchr(ValueUnion.psz, ':');
+ if (!pszColon)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No colon in --set-header value: %s", ValueUnion.psz);
+ *pszColon = '\0'; /* evil */
+ const char *pszValue = pszColon + 1;
+ if (RT_C_IS_BLANK(*pszValue))
+ pszValue++;
+ rc = RTHttpAddHeader(hHttp, ValueUnion.psz, pszValue, RTSTR_MAX, RTHTTPADDHDR_F_BACK);
+ *pszColon = ':';
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTHttpAddHeader failed: %Rrc (on %s)", rc, ValueUnion.psz);
+ break;
+ }
+
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ int rcHttp;
+ if (pszOutput && strcmp("-", pszOutput))
+ {
+ if (uVerbosityLevel > 0)
+ RTStrmPrintf(g_pStdErr, "Fetching '%s' into '%s'...\n", ValueUnion.psz, pszOutput);
+ rcHttp = RTHttpGetFile(hHttp, ValueUnion.psz, pszOutput);
+ }
+ else
+ {
+ if (uVerbosityLevel > 0)
+ RTStrmPrintf(g_pStdErr, "Fetching '%s'...\n", ValueUnion.psz);
+
+ void *pvResp;
+ size_t cbResp;
+ rcHttp = RTHttpGetBinary(hHttp, ValueUnion.psz, &pvResp, &cbResp);
+ if (RT_SUCCESS(rcHttp))
+ {
+ RTVFSIOSTREAM hVfsIos;
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, 0, true /*fLeaveOpen*/, &hVfsIos);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsIoStrmWrite(hVfsIos, pvResp, cbResp, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error writing to stdout: %Rrc", rc);
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error opening stdout: %Rrc", rc);
+ RTHttpFreeResponse(pvResp);
+ }
+ }
+ if (RT_FAILURE(rcHttp))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error %Rrc getting '%s'", rcHttp, ValueUnion.psz);
+ break;
+ }
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ RTHttpDestroy(hHttp);
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTHttpServer.cpp b/src/VBox/Runtime/tools/RTHttpServer.cpp
new file mode 100644
index 00000000..4fe1454a
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTHttpServer.cpp
@@ -0,0 +1,889 @@
+/* $Id: RTHttpServer.cpp $ */
+/** @file
+ * IPRT - Utility for running a (simple) HTTP server.
+ *
+ * Use this setup to best see what's going on:
+ * VBOX_LOG=rt_http=~0
+ * VBOX_LOG_DEST="nofile stderr"
+ * VBOX_LOG_FLAGS="unbuffered enabled thread msprog"
+ *
+ */
+
+/*
+ * Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <signal.h>
+
+#include <iprt/http.h>
+#include <iprt/http-server.h>
+
+#include <iprt/net.h> /* To make use of IPv4Addr in RTGETOPTUNION. */
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#define LOG_GROUP RTLOGGROUP_HTTP
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/vfs.h>
+
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Definitations *
+*********************************************************************************************************************************/
+typedef struct HTTPSERVERDATA
+{
+ /** The absolute path of the HTTP server's root directory. */
+ char szPathRootAbs[RTPATH_MAX];
+ RTFMODE fMode;
+ union
+ {
+ RTFILE File;
+ RTVFSDIR Dir;
+ } h;
+ /** Cached response data. */
+ RTHTTPSERVERRESP Resp;
+} HTTPSERVERDATA;
+typedef HTTPSERVERDATA *PHTTPSERVERDATA;
+
+/**
+ * Enumeration specifying the VFS handle type of the HTTP server.
+ */
+typedef enum HTTPSERVERVFSHANDLETYPE
+{
+ HTTPSERVERVFSHANDLETYPE_INVALID = 0,
+ HTTPSERVERVFSHANDLETYPE_FILE,
+ HTTPSERVERVFSHANDLETYPE_DIR,
+ /** The usual 32-bit hack. */
+ HTTPSERVERVFSHANDLETYPE_32BIT_HACK = 0x7fffffff
+} HTTPSERVERVFSHANDLETYPE;
+
+/**
+ * Structure for keeping a VFS handle of the HTTP server.
+ */
+typedef struct HTTPSERVERVFSHANDLE
+{
+ /** The type of the handle, stored in the union below. */
+ HTTPSERVERVFSHANDLETYPE enmType;
+ union
+ {
+ /** The VFS (chain) handle to use for this file. */
+ RTVFSFILE hVfsFile;
+ /** The VFS (chain) handle to use for this directory. */
+ RTVFSDIR hVfsDir;
+ } u;
+} HTTPSERVERVFSHANDLE;
+typedef HTTPSERVERVFSHANDLE *PHTTPSERVERVFSHANDLE;
+
+/**
+ * HTTP directory entry.
+ */
+typedef struct RTHTTPDIRENTRY
+{
+ /** The information about the entry. */
+ RTFSOBJINFO Info;
+ /** Symbolic link target (allocated after the name). */
+ const char *pszTarget;
+ /** Owner if applicable (allocated after the name). */
+ const char *pszOwner;
+ /** Group if applicable (allocated after the name). */
+ const char *pszGroup;
+ /** The length of szName. */
+ size_t cchName;
+ /** The entry name. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szName[RT_FLEXIBLE_ARRAY];
+} RTHTTPDIRENTRY;
+/** Pointer to a HTTP directory entry. */
+typedef RTHTTPDIRENTRY *PRTHTTPDIRENTRY;
+/** Pointer to a HTTP directory entry pointer. */
+typedef PRTHTTPDIRENTRY *PPRTHTTPDIRENTRY;
+
+/**
+ * Collection of HTTP directory entries.
+ * Used for also caching stuff.
+ */
+typedef struct RTHTTPDIRCOLLECTION
+{
+ /** Current size of papEntries. */
+ size_t cEntries;
+ /** Memory allocated for papEntries. */
+ size_t cEntriesAllocated;
+ /** Current entries pending sorting and display. */
+ PPRTHTTPDIRENTRY papEntries;
+
+ /** Total number of bytes allocated for the above entries. */
+ uint64_t cbTotalAllocated;
+ /** Total number of file content bytes. */
+ uint64_t cbTotalFiles;
+
+} RTHTTPDIRCOLLECTION;
+/** Pointer to a directory collection. */
+typedef RTHTTPDIRCOLLECTION *PRTHTTPDIRCOLLECTION;
+/** Pointer to a directory entry collection pointer. */
+typedef PRTHTTPDIRCOLLECTION *PPRTHTTPDIRCOLLECTION;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Set by the signal handler when the HTTP server shall be terminated. */
+static volatile bool g_fCanceled = false;
+static HTTPSERVERDATA g_HttpServerData;
+
+
+#ifdef RT_OS_WINDOWS
+static BOOL WINAPI signalHandler(DWORD dwCtrlType) RT_NOTHROW_DEF
+{
+ bool fEventHandled = FALSE;
+ switch (dwCtrlType)
+ {
+ /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
+ * via GenerateConsoleCtrlEvent(). */
+ case CTRL_BREAK_EVENT:
+ case CTRL_CLOSE_EVENT:
+ case CTRL_C_EVENT:
+ ASMAtomicWriteBool(&g_fCanceled, true);
+ fEventHandled = TRUE;
+ break;
+ default:
+ break;
+ /** @todo Add other events here. */
+ }
+
+ return fEventHandled;
+}
+#else /* !RT_OS_WINDOWS */
+/**
+ * Signal handler that sets g_fCanceled.
+ *
+ * This can be executed on any thread in the process, on Windows it may even be
+ * a thread dedicated to delivering this signal. Don't do anything
+ * unnecessary here.
+ */
+static void signalHandler(int iSignal) RT_NOTHROW_DEF
+{
+ NOREF(iSignal);
+ ASMAtomicWriteBool(&g_fCanceled, true);
+}
+#endif
+
+/**
+ * Installs a custom signal handler to get notified
+ * whenever the user wants to intercept the program.
+ *
+ * @todo Make this handler available for all VBoxManage modules?
+ */
+static int signalHandlerInstall(void)
+{
+ g_fCanceled = false;
+
+ int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)signalHandler, TRUE /* Add handler */))
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ RTMsgError("Unable to install console control handler, rc=%Rrc\n", rc);
+ }
+#else
+ signal(SIGINT, signalHandler);
+ signal(SIGTERM, signalHandler);
+# ifdef SIGBREAK
+ signal(SIGBREAK, signalHandler);
+# endif
+#endif
+ return rc;
+}
+
+/**
+ * Uninstalls a previously installed signal handler.
+ */
+static int signalHandlerUninstall(void)
+{
+ int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ RTMsgError("Unable to uninstall console control handler, rc=%Rrc\n", rc);
+ }
+#else
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+# ifdef SIGBREAK
+ signal(SIGBREAK, SIG_DFL);
+# endif
+#endif
+ return rc;
+}
+
+static int dirOpen(const char *pszPathAbs, PRTVFSDIR phVfsDir)
+{
+ return RTVfsChainOpenDir(pszPathAbs, 0 /*fFlags*/, phVfsDir, NULL /* poffError */, NULL /* pErrInfo */);
+}
+
+static int dirClose(RTVFSDIR hVfsDir)
+{
+ RTVfsDirRelease(hVfsDir);
+
+ return VINF_SUCCESS;
+}
+
+static int dirRead(RTVFSDIR hVfsDir, char **ppszEntry, PRTFSOBJINFO pInfo)
+{
+ size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
+ PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (!pDirEntry)
+ return VERR_NO_MEMORY;
+
+ int rc;
+
+ for (;;)
+ {
+ size_t cbDirEntry = cbDirEntryAlloced;
+ rc = RTVfsDirReadEx(hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ RTMemTmpFree(pDirEntry);
+ cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
+ pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (pDirEntry)
+ continue;
+ }
+ else
+ break;
+ }
+
+ /* Skip dot directories. */
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ continue;
+
+ *ppszEntry = RTStrDup(pDirEntry->szName);
+ AssertPtrReturn(*ppszEntry, VERR_NO_MEMORY);
+
+ *pInfo = pDirEntry->Info;
+
+ break;
+
+ } /* for */
+
+ RTMemTmpFree(pDirEntry);
+ pDirEntry = NULL;
+
+ return rc;
+}
+
+#ifdef IPRT_HTTP_WITH_WEBDAV
+static int dirEntryWriteDAV(char *pszBuf, size_t cbBuf,
+ const char *pszEntry, const PRTFSOBJINFO pObjInfo, size_t *pcbWritten)
+{
+ char szBirthTime[32];
+ if (RTTimeSpecToString(&pObjInfo->BirthTime, szBirthTime, sizeof(szBirthTime)) == NULL)
+ return VERR_BUFFER_UNDERFLOW;
+
+ char szModTime[32];
+ if (RTTimeSpecToString(&pObjInfo->ModificationTime, szModTime, sizeof(szModTime)) == NULL)
+ return VERR_BUFFER_UNDERFLOW;
+
+ int rc = VINF_SUCCESS;
+
+ /**
+ * !!! HACK ALERT !!!
+ ** @todo Build up and use a real XML DOM here. Works with Gnome / Gvfs-compatible apps though.
+ * !!! HACK ALERT !!!
+ */
+ ssize_t cch = RTStrPrintf(pszBuf, cbBuf,
+"<d:response>"
+"<d:href>%s</d:href>"
+"<d:propstat>"
+"<d:status>HTTP/1.1 200 OK</d:status>"
+"<d:prop>"
+"<d:displayname>%s</d:displayname>"
+"<d:getcontentlength>%RU64</d:getcontentlength>"
+"<d:getcontenttype>%s</d:getcontenttype>"
+"<d:creationdate>%s</d:creationdate>"
+"<d:getlastmodified>%s</d:getlastmodified>"
+"<d:getetag/>"
+"<d:resourcetype><d:collection/></d:resourcetype>"
+"</d:prop>"
+"</d:propstat>"
+"</d:response>",
+ pszEntry, pszEntry, pObjInfo->cbObject, "application/octet-stream", szBirthTime, szModTime);
+
+ if (cch <= 0)
+ rc = VERR_BUFFER_OVERFLOW;
+
+ *pcbWritten = cch;
+
+ return rc;
+}
+
+static int writeHeaderDAV(PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char *pszBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ /**
+ * !!! HACK ALERT !!!
+ ** @todo Build up and use a real XML DOM here. Works with Gnome / Gvfs-compatible apps though.
+ * !!! HACK ALERT !!!
+ */
+
+ size_t cbWritten = 0;
+
+ ssize_t cch = RTStrPrintf2(pszBuf, cbBuf - cbWritten, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n");
+ AssertReturn(cch, VERR_BUFFER_UNDERFLOW);
+ pszBuf += cch;
+ cbWritten += cch;
+
+ cch = RTStrPrintf2(pszBuf, cbBuf - cbWritten, "<d:multistatus xmlns:d=\"DAV:\">\r\n");
+ AssertReturn(cch, VERR_BUFFER_UNDERFLOW);
+ pszBuf += cch;
+ cbWritten += cch;
+
+ int rc = dirEntryWriteDAV(pszBuf, cbBuf - cbWritten, pReq->pszUrl, pObjInfo, (size_t *)&cch);
+ AssertRC(rc);
+ pszBuf += cch;
+ cbWritten += cch;
+
+ *pcbWritten += cbWritten;
+
+ return rc;
+}
+
+static int writeFooterDAV(PRTHTTPSERVERREQ pReq, char *pszBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ RT_NOREF(pReq, pcbWritten);
+
+ /**
+ * !!! HACK ALERT !!!
+ ** @todo Build up and use a real XML DOM here. Works with Gnome / Gvfs-compatible apps though.
+ * !!! HACK ALERT !!!
+ */
+ ssize_t cch = RTStrPrintf2(pszBuf, cbBuf, "</d:multistatus>");
+ AssertReturn(cch, VERR_BUFFER_UNDERFLOW);
+ RT_NOREF(cch);
+
+ return VINF_SUCCESS;
+}
+#endif /* IPRT_HTTP_WITH_WEBDAV */
+
+static int dirEntryWrite(RTHTTPMETHOD enmMethod, char *pszBuf, size_t cbBuf,
+ const char *pszEntry, const PRTFSOBJINFO pObjInfo, size_t *pcbWritten)
+{
+ char szModTime[32];
+ if (RTTimeSpecToString(&pObjInfo->ModificationTime, szModTime, sizeof(szModTime)) == NULL)
+ return VERR_BUFFER_UNDERFLOW;
+
+ int rc = VINF_SUCCESS;
+
+ ssize_t cch = 0;
+
+ if (enmMethod == RTHTTPMETHOD_GET)
+ {
+ cch = RTStrPrintf2(pszBuf, cbBuf, "201: %s %RU64 %s %s\r\n",
+ pszEntry, pObjInfo->cbObject, szModTime,
+ /** @todo Very crude; only files and directories are supported for now. */
+ RTFS_IS_FILE(pObjInfo->Attr.fMode) ? "FILE" : "DIRECTORY");
+ if (cch <= 0)
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+#ifdef IPRT_HTTP_WITH_WEBDAV
+ else if (enmMethod == RTHTTPMETHOD_PROPFIND)
+ {
+ char szBuf[RTPATH_MAX + _4K]; /** @todo Just a rough guesstimate. */
+ rc = dirEntryWriteDAV(szBuf, sizeof(szBuf), pszEntry, pObjInfo, (size_t *)&cch);
+ if (RT_SUCCESS(rc))
+ rc = RTStrCat(pszBuf, cbBuf, szBuf);
+ AssertRC(rc);
+ }
+#endif /* IPRT_HTTP_WITH_WEBDAV */
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ if (RT_SUCCESS(rc))
+ {
+ *pcbWritten = (size_t)cch;
+ }
+
+ return rc;
+}
+
+/**
+ * Resolves (and validates) a given URL to an absolute (local) path.
+ *
+ * @returns VBox status code.
+ * @param pThis HTTP server instance data.
+ * @param pszUrl URL to resolve.
+ * @param ppszPathAbs Where to store the resolved absolute path on success.
+ * Needs to be free'd with RTStrFree().
+ */
+static int pathResolve(PHTTPSERVERDATA pThis, const char *pszUrl, char **ppszPathAbs)
+{
+ /* Construct absolute path. */
+ char *pszPathAbs = NULL;
+ if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pszUrl) <= 0)
+ return VERR_NO_MEMORY;
+
+#ifdef VBOX_STRICT
+ RTFSOBJINFO objInfo;
+ int rc2 = RTPathQueryInfo(pszPathAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
+ AssertRCReturn(rc2, rc2); RT_NOREF(rc2);
+ AssertReturn(!RTFS_IS_SYMLINK(objInfo.Attr.fMode), VERR_NOT_SUPPORTED);
+#endif
+
+ *ppszPathAbs = pszPathAbs;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onOpen(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void **ppvHandle)
+{
+ PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
+
+ char *pszPathAbs = NULL;
+ int rc = pathResolve(pThis, pReq->pszUrl, &pszPathAbs);
+ if (RT_SUCCESS(rc))
+ {
+ RTFSOBJINFO objInfo;
+ rc = RTPathQueryInfo(pszPathAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
+ AssertRCReturn(rc, rc);
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ /* Nothing to do here;
+ * The directory listing has been cached already in onQueryInfo(). */
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ rc = RTFileOpen(&pThis->h.File, pszPathAbs,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pThis->fMode = objInfo.Attr.fMode;
+
+ uint64_t *puHandle = (uint64_t *)RTMemAlloc(sizeof(uint64_t));
+ *puHandle = 42; /** @todo Fudge. */
+ *ppvHandle = puHandle;
+ }
+
+ RTStrFree(pszPathAbs);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static DECLCALLBACK(int) onRead(PRTHTTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+{
+ PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
+
+ AssertReturn(*(uint64_t *)pvHandle == 42 /** @todo Fudge. */, VERR_NOT_FOUND);
+
+ int rc;
+
+ if (RTFS_IS_DIRECTORY(pThis->fMode))
+ {
+ PRTHTTPSERVERRESP pResp = &pThis->Resp;
+
+ const size_t cbToCopy = RT_MIN(cbBuf, pResp->Body.cbBodyUsed - pResp->Body.offBody);
+ memcpy(pvBuf, (uint8_t *)pResp->Body.pvBody + pResp->Body.offBody, cbToCopy);
+ Assert(pResp->Body.cbBodyUsed >= cbToCopy);
+ pResp->Body.offBody += cbToCopy;
+
+ *pcbRead = cbToCopy;
+
+ rc = VINF_SUCCESS;
+ }
+ else if (RTFS_IS_FILE(pThis->fMode))
+ {
+ rc = RTFileRead(pThis->h.File, pvBuf, cbBuf, pcbRead);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static DECLCALLBACK(int) onClose(PRTHTTPCALLBACKDATA pData, void *pvHandle)
+{
+ PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
+
+ AssertReturn(*(uint64_t *)pvHandle == 42 /** @todo Fudge. */, VERR_NOT_FOUND);
+
+ int rc;
+
+ if (RTFS_IS_FILE(pThis->fMode))
+ {
+ rc = RTFileClose(pThis->h.File);
+ if (RT_SUCCESS(rc))
+ pThis->h.File = NIL_RTFILE;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ RTMemFree(pvHandle);
+ pvHandle = NULL;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static DECLCALLBACK(int) onQueryInfo(PRTHTTPCALLBACKDATA pData,
+ PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char **ppszMIMEHint)
+{
+ PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
+
+ /** !!!! WARNING !!!
+ **
+ ** Not production-ready code below!
+ ** @todo Use something like bodyAdd() instead of the RTStrPrintf2() hacks.
+ **
+ ** !!!! WARNING !!! */
+
+ char *pszPathAbs = NULL;
+ int rc = pathResolve(pThis, pReq->pszUrl, &pszPathAbs);
+ if (RT_SUCCESS(rc))
+ {
+ RTFSOBJINFO objInfo;
+ rc = RTPathQueryInfo(pszPathAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ PRTHTTPSERVERRESP pResp = &pThis->Resp; /* Only one request a time for now. */
+
+ RTVFSDIR hVfsDir;
+ rc = dirOpen(pszPathAbs, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ RTHttpServerResponseDestroy(pResp);
+ RTHttpServerResponseInitEx(pResp, _64K); /** @todo Make this more dynamic. */
+
+ char *pszBody = (char *)pResp->Body.pvBody;
+ size_t cbBodyLeft = pResp->Body.cbBodyAlloc;
+
+ /*
+ * Write body header.
+ */
+ if (pReq->enmMethod == RTHTTPMETHOD_GET)
+ {
+ ssize_t cch = RTStrPrintf2(pszBody, cbBodyLeft,
+ "300: file://%s\r\n"
+ "200: filename content-length last-modified file-type\r\n",
+ pReq->pszUrl);
+ Assert(cch);
+ pszBody += cch;
+ cbBodyLeft -= cch;
+ }
+#ifdef IPRT_HTTP_WITH_WEBDAV
+ else if (pReq->enmMethod == RTHTTPMETHOD_PROPFIND)
+ {
+ size_t cbWritten = 0;
+ rc = writeHeaderDAV(pReq, &objInfo, pszBody, cbBodyLeft, &cbWritten);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbBodyLeft >= cbWritten);
+ cbBodyLeft -= cbWritten;
+ }
+
+ }
+#endif /* IPRT_HTTP_WITH_WEBDAV */
+ /*
+ * Write body entries.
+ */
+ char *pszEntry = NULL;
+ RTFSOBJINFO fsObjInfo;
+ while (RT_SUCCESS(rc = dirRead(hVfsDir, &pszEntry, &fsObjInfo)))
+ {
+ LogFlowFunc(("Entry '%s'\n", pszEntry));
+
+ size_t cbWritten = 0;
+ rc = dirEntryWrite(pReq->enmMethod, pszBody, cbBodyLeft, pszEntry, &fsObjInfo, &cbWritten);
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ pResp->Body.cbBodyAlloc += _4K; /** @todo Improve this. */
+ pResp->Body.pvBody = RTMemRealloc(pResp->Body.pvBody, pResp->Body.cbBodyAlloc);
+ AssertPtrBreakStmt(pResp->Body.pvBody, rc = VERR_NO_MEMORY);
+
+ pszBody = (char *)pResp->Body.pvBody;
+ cbBodyLeft += _4K; /** @todo Ditto. */
+
+ rc = dirEntryWrite(pReq->enmMethod, pszBody, cbBodyLeft, pszEntry, &fsObjInfo, &cbWritten);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && cbWritten)
+ {
+ pszBody += cbWritten;
+ Assert(cbBodyLeft > cbWritten);
+ cbBodyLeft -= cbWritten;
+ }
+
+ RTStrFree(pszEntry);
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ if (rc == VERR_NO_MORE_FILES) /* All entries consumed? */
+ rc = VINF_SUCCESS;
+
+ dirClose(hVfsDir);
+
+ /*
+ * Write footers, if any.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (pReq->enmMethod == RTHTTPMETHOD_GET)
+ {
+ if (ppszMIMEHint)
+ rc = RTStrAPrintf(ppszMIMEHint, "text/plain");
+ }
+#ifdef IPRT_HTTP_WITH_WEBDAV
+ else if (pReq->enmMethod == RTHTTPMETHOD_PROPFIND)
+ {
+ rc = writeFooterDAV(pReq, pszBody, cbBodyLeft, NULL);
+ }
+#endif /* IPRT_HTTP_WITH_WEBDAV */
+
+ pResp->Body.cbBodyUsed = strlen((char *)pResp->Body.pvBody);
+
+ pObjInfo->cbObject = pResp->Body.cbBodyUsed;
+ }
+ }
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pszPathAbs,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileQueryInfo(hFile, pObjInfo, RTFSOBJATTRADD_NOTHING);
+
+ RTFileClose(hFile);
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ RTStrFree(pszPathAbs);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static DECLCALLBACK(int) onDestroy(PRTHTTPCALLBACKDATA pData)
+{
+ PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
+
+ RTHttpServerResponseDestroy(&pThis->Resp);
+
+ return VINF_SUCCESS;
+}
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /* Use some sane defaults. */
+ char szAddress[64] = "localhost";
+ uint16_t uPort = 8080;
+
+ RT_ZERO(g_HttpServerData);
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--address", 'a', RTGETOPT_REQ_IPV4ADDR }, /** @todo Use a string for DNS hostnames? */
+ /** @todo Implement IPv6 support? */
+ { "--port", 'p', RTGETOPT_REQ_UINT16 },
+ { "--root-dir", 'r', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ unsigned uVerbosityLevel = 1;
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 'a':
+ RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8", /** @todo Improve this. */
+ ValueUnion.IPv4Addr.au8[0], ValueUnion.IPv4Addr.au8[1], ValueUnion.IPv4Addr.au8[2], ValueUnion.IPv4Addr.au8[3]);
+ break;
+
+ case 'p':
+ uPort = ValueUnion.u16;
+ break;
+
+ case 'r':
+ RTStrCopy(g_HttpServerData.szPathRootAbs, sizeof(g_HttpServerData.szPathRootAbs), ValueUnion.psz);
+ break;
+
+ case 'v':
+ uVerbosityLevel++;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"
+ " -a, --address (default: localhost)\n"
+ " Specifies the address to use for listening.\n"
+ " -p, --port (default: 8080)\n"
+ " Specifies the port to use for listening.\n"
+ " -r, --root-dir (default: current dir)\n"
+ " Specifies the root directory being served.\n"
+ " -v, --verbose\n"
+ " Controls the verbosity level.\n"
+ " -h, -?, --help\n"
+ " Display this help text and exit successfully.\n"
+ " -V, --version\n"
+ " Display the revision and exit successfully.\n"
+ , RTPathFilename(argv[0]));
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("$Revision: 155244 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ if (!strlen(g_HttpServerData.szPathRootAbs))
+ {
+ /* By default use the current directory as serving root directory. */
+ rc = RTPathGetCurrent(g_HttpServerData.szPathRootAbs, sizeof(g_HttpServerData.szPathRootAbs));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Retrieving current directory failed: %Rrc", rc);
+ }
+
+ /* Install signal handler. */
+ rc = signalHandlerInstall();
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create the HTTP server instance.
+ */
+ RTHTTPSERVERCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+
+ Callbacks.pfnOpen = onOpen;
+ Callbacks.pfnRead = onRead;
+ Callbacks.pfnClose = onClose;
+ Callbacks.pfnQueryInfo = onQueryInfo;
+ Callbacks.pfnDestroy = onDestroy;
+
+ g_HttpServerData.h.File = NIL_RTFILE;
+ g_HttpServerData.h.Dir = NIL_RTVFSDIR;
+
+ rc = RTHttpServerResponseInit(&g_HttpServerData.Resp);
+ AssertRC(rc);
+
+ RTHTTPSERVER hHTTPServer;
+ rc = RTHttpServerCreate(&hHTTPServer, szAddress, uPort, &Callbacks,
+ &g_HttpServerData, sizeof(g_HttpServerData));
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Starting HTTP server at %s:%RU16 ...\n", szAddress, uPort);
+ RTPrintf("Root directory is '%s'\n", g_HttpServerData.szPathRootAbs);
+
+ RTPrintf("Running HTTP server ...\n");
+
+ for (;;)
+ {
+ RTThreadSleep(200);
+
+ if (g_fCanceled)
+ break;
+ }
+
+ RTPrintf("Stopping HTTP server ...\n");
+
+ int rc2 = RTHttpServerDestroy(hHTTPServer);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ RTPrintf("Stopped HTTP server\n");
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTHttpServerCreate failed: %Rrc", rc);
+
+ int rc2 = signalHandlerUninstall();
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ /* Set rcExit on failure in case we forgot to do so before. */
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTIsoMaker.cpp b/src/VBox/Runtime/tools/RTIsoMaker.cpp
new file mode 100644
index 00000000..2fa3aa7b
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTIsoMaker.cpp
@@ -0,0 +1,54 @@
+/* $Id: RTIsoMaker.cpp $ */
+/** @file
+ * IPRT - ISO maker Utility.
+ */
+
+/*
+ * Copyright (C) 2010-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/fsisomaker.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTFsIsoMakerCmd(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTKrnlModInfo.cpp b/src/VBox/Runtime/tools/RTKrnlModInfo.cpp
new file mode 100644
index 00000000..9fa77c9d
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTKrnlModInfo.cpp
@@ -0,0 +1,187 @@
+/* $Id: RTKrnlModInfo.cpp $ */
+/** @file
+ * IPRT - Utility for getting information about loaded kernel modules.
+ */
+
+/*
+ * Copyright (C) 2017-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/krnlmod.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+/**
+ * Handles loading a kernel module by name.
+ *
+ * @returns Process status code.
+ * @param pszName THe module name to load.
+ */
+static RTEXITCODE rtKrnlModInfoHandleLoad(const char *pszName)
+{
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ int rc = RTKrnlModLoadByName(pszName);
+ if (RT_SUCCESS(rc))
+ RTPrintf("Kernel module '%s' loaded successfully\n", pszName);
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error %Rrc loading kernel module '%s'", rc, pszName);
+
+ return rcExit;
+}
+
+
+/**
+ * Handles unloading a kernel module by name.
+ *
+ * @returns Process status code.
+ * @param pszName THe module name to load.
+ */
+static RTEXITCODE rtKrnlModInfoHandleUnload(const char *pszName)
+{
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ int rc = RTKrnlModUnloadByName(pszName);
+ if (RT_SUCCESS(rc))
+ RTPrintf("Kernel module '%s' unloaded successfully\n", pszName);
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error %Rrc unloading kernel module '%s'", rc, pszName);
+
+ return rcExit;
+}
+
+
+/**
+ * Handles listing all loaded kernel modules.
+ *
+ * @returns Process status code.
+ */
+static RTEXITCODE rtKrnlModInfoHandleList(void)
+{
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ uint32_t cKrnlMods = RTKrnlModLoadedGetCount();
+ if (cKrnlMods)
+ {
+ PRTKRNLMODINFO pahKrnlModInfo = (PRTKRNLMODINFO)RTMemAllocZ(cKrnlMods * sizeof(RTKRNLMODINFO));
+ if (pahKrnlModInfo)
+ {
+ int rc = RTKrnlModLoadedQueryInfoAll(pahKrnlModInfo, cKrnlMods, &cKrnlMods);
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Index Load address Size Ref count Name \n");
+ for (unsigned i = 0; i < cKrnlMods; i++)
+ {
+ RTKRNLMODINFO hKrnlModInfo = pahKrnlModInfo[i];
+ RTPrintf("%5u %#-18RHv %-10u %-10u %s\n", i,
+ RTKrnlModInfoGetLoadAddr(hKrnlModInfo),
+ RTKrnlModInfoGetSize(hKrnlModInfo),
+ RTKrnlModInfoGetRefCnt(hKrnlModInfo),
+ RTKrnlModInfoGetName(hKrnlModInfo));
+ RTKrnlModInfoRelease(hKrnlModInfo);
+ }
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error %Rrc querying kernel modules", rc);
+
+ RTMemFree(pahKrnlModInfo);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error %Rrc allocating memory", VERR_NO_MEMORY);
+ }
+
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--load", 'l', RTGETOPT_REQ_STRING },
+ { "--unload", 'u', RTGETOPT_REQ_STRING },
+ { "--show-loaded", 's', RTGETOPT_REQ_NOTHING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING }
+ };
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 'l':
+ return rtKrnlModInfoHandleLoad(ValueUnion.psz);
+ case 'u':
+ return rtKrnlModInfoHandleUnload(ValueUnion.psz);
+ case 's':
+ return rtKrnlModInfoHandleList();
+ case 'h':
+ RTPrintf("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"
+ " -l, --load <module name>\n"
+ " Tries to load the given kernel module.\n"
+ " -s, --show-loaded\n"
+ " Lists all loaded kernel modules.\n"
+ , RTPathFilename(argv[0]));
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("$Revision: 155244 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /* No arguments means listing all loaded kernel modules. */
+ return rtKrnlModInfoHandleList();
+}
+
diff --git a/src/VBox/Runtime/tools/RTLdrCheckImports.cpp b/src/VBox/Runtime/tools/RTLdrCheckImports.cpp
new file mode 100644
index 00000000..169e304d
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTLdrCheckImports.cpp
@@ -0,0 +1,708 @@
+/* $Id: RTLdrCheckImports.cpp $ */
+/** @file
+ * IPRT - Module dependency checker.
+ */
+
+/*
+ * Copyright (C) 2010-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/buildconfig.h>
+#include <iprt/file.h>
+#include <iprt/initterm.h>
+#include <iprt/ldr.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Import checker options.
+ */
+typedef struct RTCHECKIMPORTSOPTS
+{
+ /** Number of paths to search. */
+ size_t cPaths;
+ /** Search directories. */
+ char **papszPaths;
+ /** The loader architecture. */
+ RTLDRARCH enmLdrArch;
+ /** Verbosity level. */
+ unsigned cVerbosity;
+ /** Whether to also list orinals in the export listing. */
+ bool fListOrdinals;
+} RTCHECKIMPORTSOPTS;
+/** Pointer to the checker options. */
+typedef RTCHECKIMPORTSOPTS *PRTCHECKIMPORTSOPTS;
+/** Pointer to the const checker options. */
+typedef RTCHECKIMPORTSOPTS const *PCRTCHECKIMPORTSOPTS;
+
+
+/**
+ * Import module.
+ */
+typedef struct RTCHECKIMPORTMODULE
+{
+ /** The module. If NIL, then we've got a export list (papszExports). */
+ RTLDRMOD hLdrMod;
+ /** Number of export in the export list. (Zero if hLdrMod is valid.) */
+ size_t cExports;
+ /** Export list. (NULL if hLdrMod is valid.) */
+ char **papszExports;
+ /** The module name. */
+ char szModule[256];
+} RTCHECKIMPORTMODULE;
+/** Pointer to an import module. */
+typedef RTCHECKIMPORTMODULE *PRTCHECKIMPORTMODULE;
+
+
+/**
+ * Import checker state (for each image being checked).
+ */
+typedef struct RTCHECKIMPORTSTATE
+{
+ /** The image we're processing. */
+ const char *pszImage;
+ /** The image we're processing. */
+ PCRTCHECKIMPORTSOPTS pOpts;
+ /** Status code. */
+ int iRc;
+ /** Import hint. */
+ uint32_t iHint;
+ /** Number modules. */
+ uint32_t cImports;
+ /** Import modules. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ RTCHECKIMPORTMODULE aImports[RT_FLEXIBLE_ARRAY];
+} RTCHECKIMPORTSTATE;
+/** Pointer to the import checker state. */
+typedef RTCHECKIMPORTSTATE *PRTCHECKIMPORTSTATE;
+
+
+
+/**
+ * Looks up a symbol/ordinal in the given import module.
+ *
+ * @returns IPRT status code.
+ * @param pModule The import module.
+ * @param pszSymbol The symbol name (NULL if not used).
+ * @param uSymbol The ordinal (~0 if unused).
+ * @param pValue Where to return a fake address.
+ */
+static int QuerySymbolFromImportModule(PRTCHECKIMPORTMODULE pModule, const char *pszSymbol, unsigned uSymbol, PRTLDRADDR pValue)
+{
+ if (pModule->hLdrMod != NIL_RTLDRMOD)
+ return RTLdrGetSymbolEx(pModule->hLdrMod, NULL, _128M, uSymbol, pszSymbol, pValue);
+
+ /*
+ * Search the export list. Ordinal imports are stringified: #<ordinal>
+ */
+ char szOrdinal[32];
+ if (!pszSymbol)
+ {
+ RTStrPrintf(szOrdinal, sizeof(szOrdinal), "#%u", uSymbol);
+ pszSymbol = szOrdinal;
+ }
+
+ size_t i = pModule->cExports;
+ while (i-- > 0)
+ if (strcmp(pModule->papszExports[i], pszSymbol) == 0)
+ {
+ *pValue = _128M + i*4;
+ return VINF_SUCCESS;
+ }
+ return VERR_SYMBOL_NOT_FOUND;
+}
+
+
+/**
+ * @callback_method_impl{FNRTLDRIMPORT}
+ */
+static DECLCALLBACK(int) GetImportCallback(RTLDRMOD hLdrMod, const char *pszModule, const char *pszSymbol,
+ unsigned uSymbol, PRTLDRADDR pValue, void *pvUser)
+{
+ PRTCHECKIMPORTSTATE pState = (PRTCHECKIMPORTSTATE)pvUser;
+ int rc;
+ NOREF(hLdrMod);
+
+ /*
+ * If a module is given, lookup the symbol/ordinal there.
+ */
+ if (pszModule)
+ {
+ uint32_t iModule = pState->iHint;
+ if ( iModule > pState->cImports
+ || strcmp(pState->aImports[iModule].szModule, pszModule) != 0)
+ {
+ for (iModule = 0; iModule < pState->cImports; iModule++)
+ if (strcmp(pState->aImports[iModule].szModule, pszModule) == 0)
+ break;
+ if (iModule >= pState->cImports)
+ return RTMsgErrorRc(VERR_MODULE_NOT_FOUND, "%s: Failed to locate import module '%s'", pState->pszImage, pszModule);
+ pState->iHint = iModule;
+ }
+
+ rc = QuerySymbolFromImportModule(&pState->aImports[iModule], pszSymbol, uSymbol, pValue);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc == VERR_LDR_FORWARDER)
+ rc= VINF_SUCCESS;
+ else
+ {
+ if (pszSymbol)
+ RTMsgError("%s: Missing import '%s' from '%s'!", pState->pszImage, pszSymbol, pszModule);
+ else
+ RTMsgError("%s: Missing import #%u from '%s'!", pState->pszImage, uSymbol, pszModule);
+ pState->iRc = rc;
+ rc = VINF_SUCCESS;
+ *pValue = _128M + _4K;
+ }
+ }
+ /*
+ * Otherwise we need to scan all modules.
+ */
+ else
+ {
+ Assert(pszSymbol);
+ uint32_t iModule = pState->iHint;
+ if (iModule < pState->cImports)
+ rc = QuerySymbolFromImportModule(&pState->aImports[iModule], pszSymbol, uSymbol, pValue);
+ else
+ rc = VERR_SYMBOL_NOT_FOUND;
+ if (rc == VERR_SYMBOL_NOT_FOUND)
+ {
+ for (iModule = 0; iModule < pState->cImports; iModule++)
+ {
+ rc = QuerySymbolFromImportModule(&pState->aImports[iModule], pszSymbol, uSymbol, pValue);
+ if (rc != VERR_SYMBOL_NOT_FOUND)
+ break;
+ }
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("%s: Missing import '%s'!", pState->pszImage, pszSymbol);
+ pState->iRc = rc;
+ rc = VINF_SUCCESS;
+ *pValue = _128M + _4K;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Loads an imported module.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The check program options.
+ * @param pModule The import module.
+ * @param pErrInfo Error buffer (to avoid wasting stack).
+ * @param pszImage The image we're processing (for error messages).
+ */
+static int LoadImportModule(PCRTCHECKIMPORTSOPTS pOpts, PRTCHECKIMPORTMODULE pModule, PRTERRINFO pErrInfo, const char *pszImage)
+
+{
+ /*
+ * Look for real DLLs.
+ */
+ for (uint32_t iPath = 0; iPath < pOpts->cPaths; iPath++)
+ {
+ char szPath[RTPATH_MAX];
+ int rc = RTPathJoin(szPath, sizeof(szPath), pOpts->papszPaths[iPath], pModule->szModule);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t offError;
+ RTFSOBJINFO ObjInfo;
+ rc = RTVfsChainQueryInfo(szPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK, &offError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ {
+ RTLDRMOD hLdrMod;
+ rc = RTLdrOpenVfsChain(szPath, RTLDR_O_FOR_DEBUG, pOpts->enmLdrArch, &hLdrMod, &offError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ pModule->hLdrMod = hLdrMod;
+ if (pOpts->cVerbosity > 0)
+ RTMsgInfo("Import '%s' -> '%s'\n", pModule->szModule, szPath);
+ }
+ else if (RTErrInfoIsSet(pErrInfo))
+ RTMsgError("%s: Failed opening import image '%s': %Rrc - %s", pszImage, szPath, rc, pErrInfo->pszMsg);
+ else
+ RTMsgError("%s: Failed opening import image '%s': %Rrc", pszImage, szPath, rc);
+ return rc;
+ }
+ }
+ else if ( rc != VERR_PATH_NOT_FOUND
+ && rc != VERR_FILE_NOT_FOUND)
+ RTVfsChainMsgError("RTVfsChainQueryInfo", szPath, rc, offError, pErrInfo);
+
+ /*
+ * Check for export file.
+ */
+ RTStrCat(szPath, sizeof(szPath), ".exports");
+ RTVFSFILE hVfsFile;
+ rc = RTVfsChainOpenFile(szPath, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFile, &offError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /* Read it into a memory buffer. */
+ uint64_t cbFile;
+ rc = RTVfsFileQuerySize(hVfsFile, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbFile < _4M)
+ {
+ char *pszFile = (char *)RTMemAlloc((size_t)cbFile + 1);
+ if (pszFile)
+ {
+ rc = RTVfsFileRead(hVfsFile, pszFile, (size_t)cbFile, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pszFile[(size_t)cbFile] = '\0';
+ rc = RTStrValidateEncoding(pszFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Parse it.
+ */
+ size_t iLine = 1;
+ size_t off = 0;
+ while (off < cbFile)
+ {
+ size_t const offStartLine = off;
+
+ /* skip leading blanks */
+ while (RT_C_IS_BLANK(pszFile[off]))
+ off++;
+
+ char ch = pszFile[off];
+ if ( ch != ';' /* comment */
+ && !RT_C_IS_CNTRL(ch))
+ {
+ /* find length of symbol */
+ size_t const offSymbol = off;
+ while ( (ch = pszFile[off]) != '\0'
+ && !RT_C_IS_SPACE(ch))
+ off++;
+ size_t const cchSymbol = off - offSymbol;
+
+ /* add it. */
+ if ((pModule->cExports & 127) == 0)
+ {
+ void *pvNew = RTMemRealloc(pModule->papszExports,
+ (pModule->cExports + 128) * sizeof(char *));
+ if (!pvNew)
+ {
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: %s:%u: out of memory!", pszImage, szPath, iLine);
+ break;
+ }
+ pModule->papszExports = (char **)pvNew;
+ }
+ pModule->papszExports[pModule->cExports] = RTStrDupN(&pszFile[offSymbol], cchSymbol);
+ if (pModule->papszExports[pModule->cExports])
+ pModule->cExports++;
+ else
+ {
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: %s:%u: out of memory!", pszImage, szPath, iLine);
+ break;
+ }
+
+ /* check what comes next is a comment or end of line/file */
+ while (RT_C_IS_BLANK(pszFile[off]))
+ off++;
+ ch = pszFile[off];
+ if ( ch != '\0'
+ && ch != '\n'
+ && ch != '\r'
+ && ch != ';')
+ rc = RTMsgErrorRc(VERR_PARSE_ERROR, "%s: %s:%u: Unexpected text at position %u!",
+ pszImage, szPath, iLine, off - offStartLine);
+ }
+
+ /* advance to the end of the the line */
+ while ( (ch = pszFile[off]) != '\0'
+ && ch != '\n')
+ off++;
+ off++;
+ iLine++;
+ }
+
+ if (pOpts->cVerbosity > 0)
+ RTMsgInfo("Import '%s' -> '%s' (%u exports)\n",
+ pModule->szModule, szPath, pModule->cExports);
+ }
+ else
+ RTMsgError("%s: %s: Invalid UTF-8 encoding in export file: %Rrc", pszImage, szPath, rc);
+ }
+ RTMemFree(pszFile);
+ }
+ else
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: %s: Out of memory reading export file (%#RX64 bytes)",
+ pszImage, szPath, cbFile + 1);
+ }
+ else
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: %s: Export file is too big: %#RX64 bytes, max 4MiB",
+ pszImage, szPath, cbFile);
+ }
+ else
+ RTMsgError("%s: %s: RTVfsFileQuerySize failed on export file: %Rrc", pszImage, szPath, rc);
+ RTVfsFileRelease(hVfsFile);
+ return rc;
+ }
+ else if ( rc != VERR_PATH_NOT_FOUND
+ && rc != VERR_FILE_NOT_FOUND)
+ RTVfsChainMsgError("RTVfsChainOpenFile", szPath, rc, offError, pErrInfo);
+ }
+ }
+
+ return RTMsgErrorRc(VERR_MODULE_NOT_FOUND, "%s: Import module '%s' was not found!", pszImage, pModule->szModule);
+}
+
+
+/**
+ * Checks the imports for the given image.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The check program options.
+ * @param pszImage The image to check.
+ */
+static int rtCheckImportsForImage(PCRTCHECKIMPORTSOPTS pOpts, const char *pszImage)
+{
+ if (pOpts->cVerbosity > 0)
+ RTMsgInfo("Checking '%s'...\n", pszImage);
+
+ /*
+ * Open the image.
+ */
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ RTLDRMOD hLdrMod;
+ int rc = RTLdrOpenVfsChain(pszImage, RTLDR_O_FOR_DEBUG, RTLDRARCH_WHATEVER,
+ &hLdrMod, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ {
+ if (RT_FAILURE(rc) && RTErrInfoIsSet(&ErrInfo.Core))
+ return RTMsgErrorRc(rc, "Failed opening image '%s': %Rrc - %s", pszImage, rc, ErrInfo.Core.pszMsg);
+ return RTMsgErrorRc(rc, "Failed opening image '%s': %Rrc", pszImage, rc);
+ }
+
+ /*
+ * Do the import modules first.
+ */
+ uint32_t cImports = 0;
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_IMPORT_COUNT, &cImports, sizeof(cImports));
+ if (RT_SUCCESS(rc))
+ {
+ RTCHECKIMPORTSTATE *pState = (RTCHECKIMPORTSTATE *)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCHECKIMPORTSTATE, aImports[cImports + 1]));
+ if (pState)
+ {
+ pState->pszImage = pszImage;
+ pState->pOpts = pOpts;
+ pState->cImports = cImports;
+ for (uint32_t iImport = 0; iImport < cImports; iImport++)
+ pState->aImports[iImport].hLdrMod = NIL_RTLDRMOD;
+
+ for (uint32_t iImport = 0; iImport < cImports; iImport++)
+ {
+ *(uint32_t *)&pState->aImports[iImport].szModule[0] = iImport;
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_IMPORT_MODULE, pState->aImports[iImport].szModule,
+ sizeof(pState->aImports[iImport].szModule));
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("%s: Error querying import #%u: %Rrc", pszImage, iImport, rc);
+ break;
+ }
+ rc = LoadImportModule(pOpts, &pState->aImports[iImport], &ErrInfo.Core, pszImage);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Get the image bits, indirectly resolving imports.
+ */
+ size_t cbImage = RTLdrSize(hLdrMod);
+ void *pvImage = RTMemAllocZ(cbImage);
+ if (pvImage)
+ {
+ pState->iRc = VINF_SUCCESS;
+ rc = RTLdrGetBits(hLdrMod, pvImage, _4M, GetImportCallback, pState);
+ if (RT_SUCCESS(rc))
+ rc = pState->iRc;
+ else
+ RTMsgError("%s: RTLdrGetBits failed: %Rrc", pszImage, rc);
+
+ RTMemFree(pvImage);
+ }
+ else
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: out of memory", pszImage);
+ }
+
+ for (uint32_t iImport = 0; iImport < cImports; iImport++)
+ if (pState->aImports[iImport].hLdrMod != NIL_RTLDRMOD)
+ {
+ RTLdrClose(pState->aImports[iImport].hLdrMod);
+
+ size_t i = pState->aImports[iImport].cExports;
+ while (i-- > 0)
+ RTStrFree(pState->aImports[iImport].papszExports[i]);
+ RTMemFree(pState->aImports[iImport].papszExports);
+ }
+ RTMemFree(pState);
+ }
+ else
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: out of memory", pszImage);
+ }
+ else
+ RTMsgError("%s: Querying RTLDRPROP_IMPORT_COUNT failed: %Rrc", pszImage, rc);
+ RTLdrClose(hLdrMod);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTLDRENUMSYMS}
+ */
+static DECLCALLBACK(int) PrintSymbolForExportList(RTLDRMOD hLdrMod, const char *pszSymbol, unsigned uSymbol,
+ RTLDRADDR Value, void *pvUser)
+{
+ if (pszSymbol)
+ RTPrintf("%s\n", pszSymbol);
+ if (uSymbol != ~(unsigned)0 && (!pszSymbol || ((PCRTCHECKIMPORTSOPTS)pvUser)->fListOrdinals))
+ RTPrintf("#%u\n", uSymbol);
+ RT_NOREF(hLdrMod, Value, pvUser);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Produces the export list for the given image.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The check program options.
+ * @param pszImage Path to the image.
+ */
+static int ProduceExportList(PCRTCHECKIMPORTSOPTS pOpts, const char *pszImage)
+{
+ /*
+ * Open the image.
+ */
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ RTLDRMOD hLdrMod;
+ int rc = RTLdrOpenVfsChain(pszImage, RTLDR_O_FOR_DEBUG, RTLDRARCH_WHATEVER, &hLdrMod, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Some info about the file.
+ */
+ RTPrintf(";\n"
+ "; Generated from: %s\n", pszImage);
+
+ RTFSOBJINFO ObjInfo;
+ rc = RTVfsChainQueryInfo(pszImage, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ RTPrintf("; Size file: %#RX64 (%RU64)\n", ObjInfo.cbObject, ObjInfo.cbObject);
+
+ switch (RTLdrGetFormat(hLdrMod))
+ {
+ case RTLDRFMT_AOUT: RTPrintf("; Format: a.out\n"); break;
+ case RTLDRFMT_ELF: RTPrintf("; Format: ELF\n"); break;
+ case RTLDRFMT_LX: RTPrintf("; Format: LX\n"); break;
+ case RTLDRFMT_MACHO: RTPrintf("; Format: Mach-O\n"); break;
+ case RTLDRFMT_PE: RTPrintf("; Format: PE\n"); break;
+ default: RTPrintf("; Format: %u\n", RTLdrGetFormat(hLdrMod)); break;
+
+ }
+
+ RTPrintf("; Size of image: %#x (%u)\n", RTLdrSize(hLdrMod), RTLdrSize(hLdrMod));
+
+ switch (RTLdrGetArch(hLdrMod))
+ {
+ case RTLDRARCH_AMD64: RTPrintf("; Architecture: AMD64\n"); break;
+ case RTLDRARCH_X86_32: RTPrintf("; Architecture: X86\n"); break;
+ default: RTPrintf("; Architecture: %u\n", RTLdrGetArch(hLdrMod)); break;
+ }
+
+ uint64_t uTimestamp;
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp, sizeof(uTimestamp));
+ if (RT_SUCCESS(rc))
+ {
+ RTTIMESPEC Timestamp;
+ char szTime[128];
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Timestamp, uTimestamp), szTime, sizeof(szTime));
+ char *pszEnd = strchr(szTime, '\0');
+ while (pszEnd[0] != '.')
+ pszEnd--;
+ *pszEnd = '\0';
+ RTPrintf("; Timestamp: %#RX64 - %s\n", uTimestamp, szTime);
+ }
+
+ RTUUID ImageUuid;
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_UUID, &ImageUuid, sizeof(ImageUuid));
+ if (RT_SUCCESS(rc))
+ RTPrintf("; UUID: %RTuuid\n", &ImageUuid);
+
+ RTPrintf(";\n");
+
+ /*
+ * The list of exports.
+ */
+ rc = RTLdrEnumSymbols(hLdrMod, 0 /*fFlags*/, NULL, _4M, PrintSymbolForExportList, (void *)pOpts);
+ if (RT_FAILURE(rc))
+ RTMsgError("%s: RTLdrEnumSymbols failed: %Rrc", pszImage, rc);
+
+ /* done */
+ RTLdrClose(hLdrMod);
+ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ RTMsgError("Failed opening image '%s': %Rrc - %s", pszImage, rc, ErrInfo.Core.pszMsg);
+ else
+ RTMsgError("Failed opening image '%s': %Rrc", pszImage, rc);
+ return rc;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ RTCHECKIMPORTSOPTS Opts;
+ Opts.cPaths = 0;
+ Opts.papszPaths = NULL;
+ Opts.enmLdrArch = RTLDRARCH_WHATEVER;
+ Opts.cVerbosity = 1;
+ Opts.fListOrdinals = false;
+
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--path", 'p', RTGETOPT_REQ_STRING },
+ { "--export", 'e', RTGETOPT_REQ_STRING },
+ { "--list-ordinals", 'O', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ };
+ RTGETOPTSTATE State;
+ rc = RTGetOptInit(&State, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ RTGETOPTUNION ValueUnion;
+ while ((rc = RTGetOpt(&State, &ValueUnion)) != 0)
+ {
+ switch (rc)
+ {
+ case 'p':
+ if ((Opts.cPaths % 16) == 0)
+ {
+ void *pvNew = RTMemRealloc(Opts.papszPaths, sizeof(Opts.papszPaths[0]) * (Opts.cPaths + 16));
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ Opts.papszPaths = (char **)pvNew;
+ }
+ Opts.papszPaths[Opts.cPaths] = RTStrDup(ValueUnion.psz);
+ AssertReturn(Opts.papszPaths[Opts.cPaths], RTEXITCODE_FAILURE);
+ Opts.cPaths++;
+ break;
+
+ case 'e':
+ rc = ProduceExportList(&Opts, ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+ break;
+
+ case 'O':
+ Opts.fListOrdinals = true;
+ break;
+
+ case 'q':
+ Opts.cVerbosity = 0;
+ break;
+
+ case 'v':
+ Opts.cVerbosity = 0;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ rc = rtCheckImportsForImage(&Opts, ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: RTCheckImports [-p|--path <dir>] [-v|--verbose] [-q|--quiet] <image [..]>\n"
+ " or: RTCheckImports -e <image>\n"
+ " or: RTCheckImports <-h|--help>\n"
+ " or: RTCheckImports <-V|--version>\n"
+ "Checks library imports. VFS chain syntax supported.\n"
+ "\n"
+ "Options:\n"
+ " -p, --path <dir>\n"
+ " Search the specified directory for imported modules or their export lists.\n"
+ " -e, --export <image>\n"
+ " Write export list for the file to stdout. (Redirect to a .export file.)\n"
+ " -O, --list-ordinals\n"
+ " Whether to list ordinals as well as names in the export list.\n"
+ " -q, --quiet\n"
+ " Quiet execution.\n"
+ " -v, --verbose\n"
+ " Increases verbosity.\n"
+ ""
+ );
+ return RTEXITCODE_SUCCESS;
+
+#ifndef IPRT_IN_BUILD_TOOL
+ case 'V':
+ RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ return RTEXITCODE_SUCCESS;
+#endif
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTLdrFlt.cpp b/src/VBox/Runtime/tools/RTLdrFlt.cpp
new file mode 100644
index 00000000..9f582479
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTLdrFlt.cpp
@@ -0,0 +1,598 @@
+/* $Id: RTLdrFlt.cpp $ */
+/** @file
+ * IPRT - Utility for translating addresses into symbols+offset.
+ */
+
+/*
+ * Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/mem.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dbg.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+
+/** Worker for ProduceKAllSyms. */
+static void PrintSymbolForKAllSyms(const char *pszModule, PCRTDBGSYMBOL pSymInfo, PCRTDBGSEGMENT pSegInfo,
+ RTUINTPTR uBaseAddr, bool fOneSeg)
+{
+ RTUINTPTR uAddr;
+ char chType = 't';
+ if (pSymInfo->iSeg < RTDBGSEGIDX_SPECIAL_FIRST)
+ {
+ uAddr = uBaseAddr + pSymInfo->offSeg;
+ if (!fOneSeg)
+ uAddr += pSegInfo->uRva;
+ if (pSegInfo->szName[0])
+ {
+ if (strstr(pSegInfo->szName, "rodata") != NULL)
+ chType = 'r';
+ else if (strstr(pSegInfo->szName, "bss") != NULL)
+ chType = 'b';
+ else if (strstr(pSegInfo->szName, "data") != NULL)
+ chType = 'd';
+ }
+ }
+ else if (pSymInfo->iSeg == RTDBGSEGIDX_ABS)
+ {
+ chType = 'a';
+ uAddr = pSymInfo->offSeg;
+ }
+ else if (pSymInfo->iSeg == RTDBGSEGIDX_RVA)
+ {
+ Assert(!fOneSeg);
+ uAddr = uBaseAddr + pSymInfo->offSeg;
+ }
+ else
+ {
+ RTMsgError("Unsupported special segment %#x for %s in %s!", pSymInfo->iSeg, pSymInfo->szName, pszModule);
+ return;
+ }
+
+ RTPrintf("%RTptr %c %s\t[%s]\n", uAddr, chType, pSymInfo->szName, pszModule);
+}
+
+
+/**
+ * Produces a /proc/kallsyms compatible symbol listing of @a hDbgAs on standard
+ * output.
+ *
+ * @returns Exit code.
+ * @param hDbgAs The address space to dump.
+ */
+static RTEXITCODE ProduceKAllSyms(RTDBGAS hDbgAs)
+{
+ /*
+ * Iterate modules.
+ */
+ uint32_t cModules = RTDbgAsModuleCount(hDbgAs);
+ for (uint32_t iModule = 0; iModule < cModules; iModule++)
+ {
+ RTDBGMOD const hDbgMod = RTDbgAsModuleByIndex(hDbgAs, iModule);
+ const char * const pszModule = RTDbgModName(hDbgMod);
+
+ /*
+ * Iterate mappings of the module.
+ */
+ RTDBGASMAPINFO aMappings[128];
+ uint32_t cMappings = RT_ELEMENTS(aMappings);
+ int rc = RTDbgAsModuleQueryMapByIndex(hDbgAs, iModule, &aMappings[0], &cMappings, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ for (uint32_t iMapping = 0; iMapping < cMappings; iMapping++)
+ {
+ RTDBGSEGMENT SegInfo = {0};
+ if (aMappings[iMapping].iSeg == NIL_RTDBGSEGIDX)
+ {
+ /*
+ * Flat mapping of the entire module.
+ */
+ SegInfo.iSeg = NIL_RTDBGSEGIDX;
+ uint32_t cSymbols = RTDbgModSymbolCount(hDbgMod);
+ for (uint32_t iSymbol = 0; iSymbol < cSymbols; iSymbol++)
+ {
+ RTDBGSYMBOL SymInfo;
+ rc = RTDbgModSymbolByOrdinal(hDbgMod, iSymbol, &SymInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if ( SymInfo.iSeg != SegInfo.iSeg
+ && SymInfo.iSeg < RTDBGSEGIDX_SPECIAL_FIRST)
+ {
+ rc = RTDbgModSegmentByIndex(hDbgMod, SymInfo.iSeg, &SegInfo);
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("RTDbgModSegmentByIndex(%s, %u) failed: %Rrc", pszModule, SymInfo.iSeg, rc);
+ continue;
+ }
+ }
+ PrintSymbolForKAllSyms(pszModule, &SymInfo, &SegInfo, aMappings[iMapping].Address, false);
+ }
+ else
+ RTMsgError("RTDbgModSymbolByOrdinal(%s, %u) failed: %Rrc", pszModule, iSymbol, rc);
+ }
+ }
+ else
+ {
+ /*
+ * Just one segment.
+ */
+ rc = RTDbgModSegmentByIndex(hDbgMod, aMappings[iMapping].iSeg, &SegInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo */
+ }
+ else
+ RTMsgError("RTDbgModSegmentByIndex(%s, %u) failed: %Rrc", pszModule, aMappings[iMapping].iSeg, rc);
+ }
+ }
+ }
+ else
+ RTMsgError("RTDbgAsModuleQueryMapByIndex failed: %Rrc", rc);
+ RTDbgModRelease(hDbgMod);
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Dumps the address space.
+ */
+static void DumpAddressSpace(RTDBGAS hDbgAs, unsigned cVerbosityLevel)
+{
+ RTPrintf("*** Address Space Dump ***\n");
+ uint32_t cModules = RTDbgAsModuleCount(hDbgAs);
+ for (uint32_t iModule = 0; iModule < cModules; iModule++)
+ {
+ RTDBGMOD hDbgMod = RTDbgAsModuleByIndex(hDbgAs, iModule);
+ RTPrintf("Module #%u: %s\n", iModule, RTDbgModName(hDbgMod));
+
+ RTDBGASMAPINFO aMappings[128];
+ uint32_t cMappings = RT_ELEMENTS(aMappings);
+ int rc = RTDbgAsModuleQueryMapByIndex(hDbgAs, iModule, &aMappings[0], &cMappings, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ for (uint32_t iMapping = 0; iMapping < cMappings; iMapping++)
+ {
+ if (aMappings[iMapping].iSeg == NIL_RTDBGSEGIDX)
+ {
+ RTPrintf(" mapping #%u: %RTptr-%RTptr\n",
+ iMapping,
+ aMappings[iMapping].Address,
+ aMappings[iMapping].Address + RTDbgModImageSize(hDbgMod) - 1);
+ if (cVerbosityLevel > 2)
+ {
+ uint32_t cSegments = RTDbgModSegmentCount(hDbgMod);
+ for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++)
+ {
+ RTDBGSEGMENT SegInfo;
+ rc = RTDbgModSegmentByIndex(hDbgMod, iSeg, &SegInfo);
+ if (RT_SUCCESS(rc))
+ RTPrintf(" seg #%u: %RTptr LB %RTptr '%s'\n",
+ iSeg, SegInfo.uRva, SegInfo.cb, SegInfo.szName);
+ else
+ RTPrintf(" seg #%u: %Rrc\n", iSeg, rc);
+ }
+ }
+ }
+ else
+ {
+ RTDBGSEGMENT SegInfo;
+ rc = RTDbgModSegmentByIndex(hDbgMod, aMappings[iMapping].iSeg, &SegInfo);
+ if (RT_SUCCESS(rc))
+ RTPrintf(" mapping #%u: %RTptr-%RTptr (segment #%u - '%s')\n",
+ iMapping,
+ aMappings[iMapping].Address,
+ aMappings[iMapping].Address + SegInfo.cb,
+ SegInfo.iSeg, SegInfo.szName);
+ else
+ RTPrintf(" mapping #%u: %RTptr-???????? (segment #%u) rc=%Rrc\n",
+ iMapping, aMappings[iMapping].Address, aMappings[iMapping].iSeg, rc);
+ }
+
+ if (cVerbosityLevel > 1)
+ {
+ uint32_t cSymbols = RTDbgModSymbolCount(hDbgMod);
+ RTPrintf(" %u symbols\n", cSymbols);
+ for (uint32_t iSymbol = 0; iSymbol < cSymbols; iSymbol++)
+ {
+ RTDBGSYMBOL SymInfo;
+ rc = RTDbgModSymbolByOrdinal(hDbgMod, iSymbol, &SymInfo);
+ if (RT_SUCCESS(rc))
+ RTPrintf(" #%04u at %08x:%RTptr (%RTptr) %05llx %s\n",
+ SymInfo.iOrdinal, SymInfo.iSeg, SymInfo.offSeg, SymInfo.Value,
+ (uint64_t)SymInfo.cb, SymInfo.szName);
+ }
+ }
+ }
+ }
+ else
+ RTMsgError("RTDbgAsModuleQueryMapByIndex failed: %Rrc", rc);
+ RTDbgModRelease(hDbgMod);
+ }
+ RTPrintf("*** End of Address Space Dump ***\n");
+}
+
+
+/**
+ * Tries to parse out an address at the head of the string.
+ *
+ * @returns true if found address, false if not.
+ * @param psz Where to start parsing.
+ * @param pcchAddress Where to store the address length.
+ * @param pu64Address Where to store the address value.
+ */
+static bool TryParseAddress(const char *psz, size_t *pcchAddress, uint64_t *pu64Address)
+{
+ const char *pszStart = psz;
+
+ /*
+ * Hex prefix?
+ */
+ if (psz[0] == '0' && (psz[1] == 'x' || psz[1] == 'X'))
+ psz += 2;
+
+ /*
+ * How many hex digits? We want at least 4 and at most 16.
+ */
+ size_t off = 0;
+ while (RT_C_IS_XDIGIT(psz[off]))
+ off++;
+ if (off < 4 || off > 16)
+ return false;
+
+ /*
+ * Check for separator (xxxxxxxx'yyyyyyyy).
+ */
+ bool fHave64bitSep = off <= 8
+ && psz[off] == '\''
+ && RT_C_IS_XDIGIT(psz[off + 1])
+ && RT_C_IS_XDIGIT(psz[off + 2])
+ && RT_C_IS_XDIGIT(psz[off + 3])
+ && RT_C_IS_XDIGIT(psz[off + 4])
+ && RT_C_IS_XDIGIT(psz[off + 5])
+ && RT_C_IS_XDIGIT(psz[off + 6])
+ && RT_C_IS_XDIGIT(psz[off + 7])
+ && RT_C_IS_XDIGIT(psz[off + 8])
+ && !RT_C_IS_XDIGIT(psz[off + 9]);
+ if (fHave64bitSep)
+ {
+ uint32_t u32High;
+ int rc = RTStrToUInt32Ex(psz, NULL, 16, &u32High);
+ if (rc != VWRN_TRAILING_CHARS)
+ return false;
+
+ uint32_t u32Low;
+ rc = RTStrToUInt32Ex(&psz[off + 1], NULL, 16, &u32Low);
+ if ( rc != VINF_SUCCESS
+ && rc != VWRN_TRAILING_SPACES
+ && rc != VWRN_TRAILING_CHARS)
+ return false;
+
+ *pu64Address = RT_MAKE_U64(u32Low, u32High);
+ off += 1 + 8;
+ }
+ else
+ {
+ int rc = RTStrToUInt64Ex(psz, NULL, 16, pu64Address);
+ if ( rc != VINF_SUCCESS
+ && rc != VWRN_TRAILING_SPACES
+ && rc != VWRN_TRAILING_CHARS)
+ return false;
+ }
+
+ *pcchAddress = psz + off - pszStart;
+ return true;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Create an empty address space that we can load modules and stuff into
+ * as we parse the parameters.
+ */
+ RTDBGAS hDbgAs;
+ rc = RTDbgAsCreate(&hDbgAs, 0, RTUINTPTR_MAX, "");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDBgAsCreate -> %Rrc", rc);
+
+ /*
+ * Create a debugging configuration instance to work with so that we can
+ * make use of (i.e. test) path searching and such.
+ */
+ RTDBGCFG hDbgCfg;
+ rc = RTDbgCfgCreate(&hDbgCfg, "IPRT", true /*fNativePaths*/);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDbgCfgCreate -> %Rrc", rc);
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--input", 'i', RTGETOPT_REQ_STRING },
+ { "--local-file", 'l', RTGETOPT_REQ_NOTHING },
+ { "--cache-file", 'c', RTGETOPT_REQ_NOTHING },
+ { "--pe-image", 'p', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--x86", '8', RTGETOPT_REQ_NOTHING },
+ { "--amd64", '6', RTGETOPT_REQ_NOTHING },
+ { "--whatever", '*', RTGETOPT_REQ_NOTHING },
+ { "--kallsyms", 'k', RTGETOPT_REQ_NOTHING },
+ };
+
+ PRTSTREAM pInput = g_pStdIn;
+ PRTSTREAM pOutput = g_pStdOut;
+ unsigned cVerbosityLevel = 0;
+ enum {
+ kOpenMethod_FromImage,
+ kOpenMethod_FromPeImage
+ } enmOpenMethod = kOpenMethod_FromImage;
+ bool fCacheFile = false;
+ RTLDRARCH enmArch = RTLDRARCH_WHATEVER;
+ bool fKAllSyms = false;
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 'i':
+ rc = RTStrmOpen(ValueUnion.psz, "r", &pInput);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open '%s' for reading: %Rrc", ValueUnion.psz, rc);
+ break;
+
+ case 'c':
+ fCacheFile = true;
+ break;
+
+ case 'k':
+ fKAllSyms = true;
+ break;
+
+ case 'l':
+ fCacheFile = false;
+ break;
+
+ case 'p':
+ enmOpenMethod = kOpenMethod_FromPeImage;
+ break;
+
+ case 'v':
+ cVerbosityLevel++;
+ break;
+
+ case '8':
+ enmArch = RTLDRARCH_X86_32;
+ break;
+
+ case '6':
+ enmArch = RTLDRARCH_AMD64;
+ break;
+
+ case '*':
+ enmArch = RTLDRARCH_WHATEVER;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [options] <module> <address> [<module> <address> [..]]\n"
+ "\n"
+ "Options:\n"
+ " -i,--input=file\n"
+ " Specify a input file instead of standard input.\n"
+ " --pe-image\n"
+ " Use RTDbgModCreateFromPeImage to open the file."
+ " -v, --verbose\n"
+ " Display the address space before doing the filtering.\n"
+ " --amd64,--x86,--whatever\n"
+ " Selects the desired architecture.\n"
+ " -k,--kallsyms\n"
+ " Produce a /proc/kallsyms compatible symbol listing and quit.\n"
+ " -h, -?, --help\n"
+ " Display this help text and exit successfully.\n"
+ " -V, --version\n"
+ " Display the revision and exit successfully.\n"
+ , RTPathFilename(argv[0]));
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("$Revision: 155244 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ /* <module> <address> */
+ const char *pszModule = ValueUnion.psz;
+
+ rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_HEX);
+ if (RT_FAILURE(rc))
+ return RTGetOptPrintError(rc, &ValueUnion);
+ uint64_t u64Address = ValueUnion.u64;
+
+ uint32_t cbImage = 0;
+ uint32_t uTimestamp = 0;
+ if (fCacheFile)
+ {
+ rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_HEX);
+ if (RT_FAILURE(rc))
+ return RTGetOptPrintError(rc, &ValueUnion);
+ cbImage = ValueUnion.u32;
+
+ rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_HEX);
+ if (RT_FAILURE(rc))
+ return RTGetOptPrintError(rc, &ValueUnion);
+ uTimestamp = ValueUnion.u32;
+ }
+
+ RTDBGMOD hMod;
+ if (enmOpenMethod == kOpenMethod_FromImage)
+ rc = RTDbgModCreateFromImage(&hMod, pszModule, NULL, enmArch, hDbgCfg);
+ else
+ rc = RTDbgModCreateFromPeImage(&hMod, pszModule, NULL, NULL, cbImage, uTimestamp, hDbgCfg);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDbgModCreateFromImage(,%s,,) -> %Rrc", pszModule, rc);
+
+ rc = RTDbgAsModuleLink(hDbgAs, hMod, u64Address, 0 /* fFlags */);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDbgAsModuleLink(,%s,%llx,) -> %Rrc", pszModule, u64Address, rc);
+ break;
+ }
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * Display the address space.
+ */
+ if (cVerbosityLevel)
+ DumpAddressSpace(hDbgAs, cVerbosityLevel);
+
+ /*
+ * Produce the /proc/kallsyms output.
+ */
+ if (fKAllSyms)
+ return ProduceKAllSyms(hDbgAs);
+
+ /*
+ * Read text from standard input and see if there is anything we can translate.
+ */
+ for (;;)
+ {
+ /* Get a line. */
+ char szLine[_64K];
+ rc = RTStrmGetLine(pInput, szLine, sizeof(szLine));
+ if (rc == VERR_EOF)
+ break;
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTStrmGetLine() -> %Rrc\n", rc);
+
+ /*
+ * Search the line for potential addresses and replace them with
+ * symbols+offset.
+ */
+ const char *pszStart = szLine;
+ const char *psz = szLine;
+ char ch;
+ while ((ch = *psz) != '\0')
+ {
+ size_t cchAddress;
+ uint64_t u64Address;
+
+ if ( ( ch == '0'
+ && (psz[1] == 'x' || psz[1] == 'X')
+ && TryParseAddress(psz, &cchAddress, &u64Address))
+ || ( RT_C_IS_XDIGIT(ch)
+ && TryParseAddress(psz, &cchAddress, &u64Address))
+ )
+ {
+ /* Print. */
+ psz += cchAddress;
+ if (pszStart != psz)
+ RTStrmWrite(pOutput, pszStart, psz - pszStart);
+ pszStart = psz;
+
+ /* Try get the module. */
+ RTUINTPTR uAddr;
+ RTDBGSEGIDX iSeg;
+ RTDBGMOD hDbgMod;
+ rc = RTDbgAsModuleByAddr(hDbgAs, u64Address, &hDbgMod, &uAddr, &iSeg);
+ if (RT_SUCCESS(rc))
+ {
+ if (iSeg != UINT32_MAX)
+ RTStrmPrintf(pOutput, "=[%s:%u", RTDbgModName(hDbgMod), iSeg);
+ else
+ RTStrmPrintf(pOutput, "=[%s", RTDbgModName(hDbgMod));
+
+ /*
+ * Do we have symbols?
+ */
+ RTDBGSYMBOL Symbol;
+ RTINTPTR offSym;
+ rc = RTDbgAsSymbolByAddr(hDbgAs, u64Address, RTDBGSYMADDR_FLAGS_LESS_OR_EQUAL, &offSym, &Symbol, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (!offSym)
+ RTStrmPrintf(pOutput, "!%s", Symbol.szName);
+ else if (offSym > 0)
+ RTStrmPrintf(pOutput, "!%s+%#llx", Symbol.szName, offSym);
+ else
+ RTStrmPrintf(pOutput, "!%s-%#llx", Symbol.szName, -offSym);
+ }
+ else
+ RTStrmPrintf(pOutput, "+%#llx", u64Address - uAddr);
+
+ /*
+ * Do we have line numbers?
+ */
+ RTDBGLINE Line;
+ RTINTPTR offLine;
+ rc = RTDbgAsLineByAddr(hDbgAs, u64Address, &offLine, &Line, NULL);
+ if (RT_SUCCESS(rc))
+ RTStrmPrintf(pOutput, " %Rbn(%u)", Line.szFilename, Line.uLineNo);
+
+ RTStrmPrintf(pOutput, "]");
+ RTDbgModRelease(hDbgMod);
+ }
+ }
+ else
+ psz++;
+ }
+
+ if (pszStart != psz)
+ RTStrmWrite(pOutput, pszStart, psz - pszStart);
+ RTStrmPutCh(pOutput, '\n');
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/tools/RTLs.cpp b/src/VBox/Runtime/tools/RTLs.cpp
new file mode 100644
index 00000000..f6d34889
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTLs.cpp
@@ -0,0 +1,54 @@
+/* $Id: RTLs.cpp $ */
+/** @file
+ * IPRT - /bin/ls like utility for testing the VFS code.
+ */
+
+/*
+ * Copyright (C) 2017-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/errcore.h>
+#include <iprt/fs.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTFsCmdLs(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTManifest.cpp b/src/VBox/Runtime/tools/RTManifest.cpp
new file mode 100644
index 00000000..3a2d1180
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTManifest.cpp
@@ -0,0 +1,404 @@
+/* $Id: RTManifest.cpp $ */
+/** @file
+ * IPRT - Manifest Utility.
+ */
+
+/*
+ * Copyright (C) 2010-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/manifest.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+
+
+/**
+ * Verify a manifest.
+ *
+ * @returns Program exit code, failures with error message.
+ * @param pszManifest The manifest file. NULL if standard input.
+ * @param fStdFormat Whether to expect standard format (true) or
+ * java format (false).
+ * @param pszChDir The directory to change into before processing
+ * the files in the manifest.
+ */
+static RTEXITCODE rtManifestDoVerify(const char *pszManifest, bool fStdFormat, const char *pszChDir)
+{
+ RT_NOREF_PV(pszChDir); /** @todo implement pszChDir! */
+
+ /*
+ * Open the manifest.
+ */
+ int rc;
+ RTVFSIOSTREAM hVfsIos;
+ if (!pszManifest)
+ {
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT, RTFILE_O_READ, false /*fLeaveOpen*/, &hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to prepare standard input for reading: %Rrc", rc);
+ }
+ else
+ {
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenIoStream(pszManifest, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pszManifest, rc, offError, &ErrInfo.Core);
+ }
+
+ /*
+ * Read it.
+ */
+ RTMANIFEST hManifest;
+ rc = RTManifestCreate(0 /*fFlags*/, &hManifest);
+ if (RT_SUCCESS(rc))
+ {
+ if (fStdFormat)
+ {
+ char szErr[4096 + 1024];
+ rc = RTManifestReadStandardEx(hManifest, hVfsIos, szErr, sizeof(szErr));
+ if (RT_SUCCESS(rc))
+ {
+ RTVfsIoStrmRelease(hVfsIos);
+ hVfsIos = NIL_RTVFSIOSTREAM;
+
+ /*
+ * Do the verification.
+ */
+ /** @todo We're missing some enumeration APIs here! */
+ RTMsgError("The manifest read fine, but the actual verification code is yet to be written. Sorry.");
+ rc = VERR_NOT_IMPLEMENTED;
+#if 1 /* For now, just write the manifest to stdout so we can test the read routine. */
+ RTVFSIOSTREAM hVfsIosOut;
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, false /*fLeaveOpen*/, &hVfsIosOut);
+ if (RT_SUCCESS(rc))
+ {
+ RTManifestWriteStandard(hManifest, hVfsIosOut);
+ RTVfsIoStrmRelease(hVfsIosOut);
+ }
+#endif
+ }
+ else if (szErr[0])
+ RTMsgError("Error reading manifest: %s", szErr);
+ else
+ RTMsgError("Error reading manifest: %Rrc", rc);
+ }
+ else
+ {
+ RTMsgError("Support for Java manifest files is not implemented yet");
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+ RTManifestRelease(hManifest);
+ }
+
+ RTVfsIoStrmRelease(hVfsIos);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Adds a file to the manifest.
+ *
+ * @returns IPRT status code, failures with error message.
+ * @param hManifest The manifest to add it to.
+ * @param pszFilename The name of the file to add.
+ * @param fAttr The manifest attributes to add.
+ */
+static int rtManifestAddFileToManifest(RTMANIFEST hManifest, const char *pszFilename, uint32_t fAttr)
+{
+ RTVFSIOSTREAM hVfsIos;
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ int rc = RTVfsChainOpenIoStream(pszFilename, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ {
+ RTVfsChainMsgError("RTVfsChainOpenIoStream", pszFilename, rc, offError, &ErrInfo.Core);
+ return rc;
+ }
+
+ rc = RTManifestEntryAddIoStream(hManifest, hVfsIos, pszFilename, fAttr);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTManifestEntryAddIoStream failed for '%s': %Rrc", pszFilename, rc);
+
+ RTVfsIoStrmRelease(hVfsIos);
+ return rc;
+}
+
+
+/**
+ * Create a manifest from the specified input files.
+ *
+ * @returns Program exit code, failures with error message.
+ * @param pszManifest The name of the output manifest file. NULL if
+ * it should be written to standard output.
+ * @param fStdFormat Whether to expect standard format (true) or
+ * java format (false).
+ * @param pszChDir The directory to change into before processing
+ * the file arguments.
+ * @param fAttr The file attributes to put in the manifest.
+ * @param pGetState The RTGetOpt state.
+ * @param pUnion What the last RTGetOpt() call returned.
+ * @param chOpt What the last RTGetOpt() call returned.
+ */
+static RTEXITCODE rtManifestDoCreate(const char *pszManifest, bool fStdFormat, const char *pszChDir, uint32_t fAttr,
+ PRTGETOPTSTATE pGetState, PRTGETOPTUNION pUnion, int chOpt)
+{
+ /*
+ * Open the manifest file.
+ */
+ int rc;
+ RTVFSIOSTREAM hVfsIos;
+ if (!pszManifest)
+ {
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, false /*fLeaveOpen*/, &hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to prepare standard output for writing: %Rrc", rc);
+ }
+ else
+ {
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ rc = RTVfsChainOpenIoStream(pszManifest, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pszManifest, rc, offError, &ErrInfo.Core);
+ }
+
+ /*
+ * Create the internal manifest.
+ */
+ RTMANIFEST hManifest;
+ rc = RTManifestCreate(0 /*fFlags*/, &hManifest);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Change directory and start processing the specified files.
+ */
+ if (pszChDir)
+ {
+ rc = RTPathSetCurrent(pszChDir);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to change directory to '%s': %Rrc", pszChDir, rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ while (chOpt == VINF_GETOPT_NOT_OPTION)
+ {
+ rc = rtManifestAddFileToManifest(hManifest, pUnion->psz, fAttr);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* next */
+ chOpt = RTGetOpt(pGetState, pUnion);
+ }
+ if (RT_SUCCESS(rc) && chOpt != 0)
+ {
+ RTGetOptPrintError(chOpt, pUnion);
+ rc = chOpt < 0 ? chOpt : -chOpt;
+ }
+ }
+
+ /*
+ * Write the manifest.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (fStdFormat)
+ {
+ rc = RTManifestWriteStandard(hManifest, hVfsIos);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTManifestWriteStandard failed: %Rrc", rc);
+ }
+ else
+ {
+ RTMsgError("Support for Java manifest files is not implemented yet");
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+ }
+
+ RTManifestRelease(hManifest);
+ }
+
+ RTVfsIoStrmRelease(hVfsIos);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse arguments.
+ */
+ static RTGETOPTDEF const s_aOptions[] =
+ {
+ { "--manifest", 'm', RTGETOPT_REQ_STRING },
+ { "--java", 'j', RTGETOPT_REQ_NOTHING },
+ { "--chdir", 'C', RTGETOPT_REQ_STRING },
+ { "--attribute", 'a', RTGETOPT_REQ_STRING },
+ { "--verify", 'v', RTGETOPT_REQ_NOTHING },
+ };
+
+ bool fVerify = false;
+ bool fStdFormat = true;
+ const char *pszManifest = NULL;
+ const char *pszChDir = NULL;
+ uint32_t fAttr = RTMANIFEST_ATTR_UNKNOWN;
+
+ RTGETOPTSTATE GetState;
+ rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc);
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ case 'a':
+ {
+ static struct
+ {
+ const char *pszAttr;
+ uint32_t fAttr;
+ } s_aAttributes[] =
+ {
+ { "size", RTMANIFEST_ATTR_SIZE },
+ { "md5", RTMANIFEST_ATTR_MD5 },
+ { "sha1", RTMANIFEST_ATTR_SHA1 },
+ { "sha256", RTMANIFEST_ATTR_SHA256 },
+ { "sha512", RTMANIFEST_ATTR_SHA512 }
+ };
+ uint32_t fThisAttr = RTMANIFEST_ATTR_UNKNOWN;
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aAttributes); i++)
+ if (!RTStrICmp(s_aAttributes[i].pszAttr, ValueUnion.psz))
+ {
+ fThisAttr = s_aAttributes[i].fAttr;
+ break;
+ }
+ if (fThisAttr == RTMANIFEST_ATTR_UNKNOWN)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown attribute type '%s'", ValueUnion.psz);
+
+ if (fAttr == RTMANIFEST_ATTR_UNKNOWN)
+ fAttr = fThisAttr;
+ else
+ fAttr |= fThisAttr;
+ break;
+ }
+
+ case 'j':
+ fStdFormat = false;
+ break;
+
+ case 'm':
+ if (pszManifest)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Only one manifest can be specified");
+ pszManifest = ValueUnion.psz;
+ break;
+
+ case 'v':
+ fVerify = true;
+ break;
+
+ case 'C':
+ if (pszChDir)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Only one directory change can be specified");
+ pszChDir = ValueUnion.psz;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [--manifest <file>] [--chdir <dir>] [--attribute <attrib-name> [..]] <files>\n"
+ " or %s --verify [--manifest <file>] [--chdir <dir>]\n"
+ "\n"
+ "attrib-name: size, md5, sha1, sha256 or sha512\n"
+ , RTProcShortName(), RTProcShortName());
+ return RTEXITCODE_SUCCESS;
+
+#ifndef IN_BLD_PROG /* RTBldCfgVersion or RTBldCfgRevision in build time IPRT lib. */
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+#endif
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * Take action.
+ */
+ RTEXITCODE rcExit;
+ if (!fVerify)
+ {
+ if (rc != VINF_GETOPT_NOT_OPTION)
+ RTMsgWarning("No files specified, the manifest will be empty.");
+ if (fAttr == RTMANIFEST_ATTR_UNKNOWN)
+ fAttr = RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_MD5
+ | RTMANIFEST_ATTR_SHA1 | RTMANIFEST_ATTR_SHA256 | RTMANIFEST_ATTR_SHA512;
+ rcExit = rtManifestDoCreate(pszManifest, fStdFormat, pszChDir, fAttr, &GetState, &ValueUnion, rc);
+ }
+ else
+ {
+ if (rc == VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "No files should be specified when verifying a manifest (--verfiy), "
+ "only a manifest via the --manifest option");
+ if (fAttr != RTMANIFEST_ATTR_UNKNOWN)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "The --attribute (-a) option does not combine with --verify (-v)");
+
+
+ rcExit = rtManifestDoVerify(pszManifest, fStdFormat, pszChDir);
+ }
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTMkDir.cpp b/src/VBox/Runtime/tools/RTMkDir.cpp
new file mode 100644
index 00000000..060d476e
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTMkDir.cpp
@@ -0,0 +1,379 @@
+/* $Id: RTMkDir.cpp $ */
+/** @file
+ * IPRT - Creates directory.
+ */
+
+/*
+ * Copyright (C) 2013-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/path.h>
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+#include <iprt/vfs.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/getopt.h>
+#include <iprt/buildconfig.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RTCMDMKDIROPTS
+{
+ /** -v, --verbose */
+ bool fVerbose;
+ /** -p, --parents */
+ bool fParents;
+ /** Whether to always use the VFS chain API (for testing). */
+ bool fAlwaysUseChainApi;
+ /** Directory creation flags (RTDIRCREATE_FLAGS_XXX). */
+ uint32_t fCreateFlags;
+ /** The directory mode. */
+ RTFMODE fMode;
+} RTCMDMKDIROPTS;
+
+
+/**
+ * Create one directory and any missing parent directories.
+ *
+ * @returns exit code
+ * @param pOpts The mkdir option.
+ * @param pszDir The path to the new directory.
+ */
+static int rtCmdMkDirOneWithParents(RTCMDMKDIROPTS const *pOpts, const char *pszDir)
+{
+ int rc;
+ if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszDir) )
+ {
+ /*
+ * Use the API for doing the entire job. Unfortuantely, this means we
+ * can't be very verbose about what we're doing.
+ */
+ rc = RTDirCreateFullPath(pszDir, pOpts->fMode);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to create directory '%s' (or a parent): %Rrc", pszDir, rc);
+ else if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszDir);
+ }
+ else
+ {
+ /*
+ * Strip the final path element from the pszDir spec.
+ */
+ char *pszCopy = RTStrDup(pszDir);
+ if (!pszCopy)
+ return RTMsgErrorExitFailure("Out of string memory!");
+
+ char *pszFinalPath;
+ char *pszSpec;
+ uint32_t offError;
+ rc = RTVfsChainSplitOffFinalPath(pszCopy, &pszSpec, &pszFinalPath, &offError);
+ if (RT_SUCCESS(rc))
+ {
+ const char * const pszFullFinalPath = pszFinalPath;
+
+ /*
+ * Open the root director/whatever.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ RTVFSDIR hVfsCurDir;
+ if (pszSpec)
+ {
+ rc = RTVfsChainOpenDir(pszSpec, 0 /*fOpen*/, &hVfsCurDir, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ RTVfsChainMsgError("RTVfsChainOpenDir", pszSpec, rc, offError, &ErrInfo.Core);
+ else if (!pszFinalPath)
+ pszFinalPath = RTStrEnd(pszSpec, RTSTR_MAX);
+ }
+ else if (!RTPathStartsWithRoot(pszFinalPath))
+ {
+ rc = RTVfsDirOpenNormal(".", 0 /*fOpen*/, &hVfsCurDir);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to open '.' (for %s): %Rrc", rc, pszFinalPath);
+ }
+ else
+ {
+ char *pszRoot = pszFinalPath;
+ pszFinalPath = RTPathSkipRootSpec(pszFinalPath);
+ char const chSaved = *pszFinalPath;
+ *pszFinalPath = '\0';
+ rc = RTVfsDirOpenNormal(pszRoot, 0 /*fOpen*/, &hVfsCurDir);
+ *pszFinalPath = chSaved;
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to open root dir for '%s': %Rrc", rc, pszRoot);
+ }
+
+ /*
+ * Walk the path component by component.
+ */
+ while (RT_SUCCESS(rc))
+ {
+ /*
+ * Strip leading slashes.
+ */
+ while (RTPATH_IS_SLASH(*pszFinalPath))
+ pszFinalPath++;
+ if (*pszFinalPath == '\0')
+ {
+ RTVfsDirRelease(hVfsCurDir);
+ break;
+ }
+
+ /*
+ * Find the end of the next path component.
+ */
+ size_t cchComponent = 0;
+ char ch;
+ while ( (ch = pszFinalPath[cchComponent]) != '\0'
+ && !RTPATH_IS_SLASH(ch))
+ cchComponent++;
+
+ /*
+ * Open or create the component.
+ */
+ pszFinalPath[cchComponent] = '\0';
+ RTVFSDIR hVfsNextDir = NIL_RTVFSDIR;
+ for (uint32_t cTries = 0; cTries < 8; cTries++)
+ {
+ /* Try open it. */
+ rc = RTVfsDirOpenDir(hVfsCurDir, pszFinalPath, 0 /*fFlags*/, &hVfsNextDir);
+ if (RT_SUCCESS(rc))
+ break;
+ if ( rc != VERR_FILE_NOT_FOUND
+ && rc != VERR_PATH_NOT_FOUND)
+ {
+ if (ch == '\0')
+ RTMsgError("Failed opening directory '%s': %Rrc", pszDir, rc);
+ else
+ RTMsgError("Failed opening dir '%s' (for creating '%s'): %Rrc", pszFullFinalPath, pszDir, rc);
+ break;
+ }
+
+ /* Not found, so try create it. */
+ rc = RTVfsDirCreateDir(hVfsCurDir, pszFinalPath, pOpts->fMode, pOpts->fCreateFlags, &hVfsNextDir);
+ if (rc == VERR_ALREADY_EXISTS)
+ continue; /* We lost a creation race, try again. */
+ if (RT_SUCCESS(rc) && pOpts->fVerbose)
+ {
+ if (pszSpec)
+ RTPrintf("%s:%s\n", pszSpec, pszFullFinalPath);
+ else
+ RTPrintf("%s\n", pszFullFinalPath);
+ }
+ else if (RT_FAILURE(rc))
+ {
+ if (ch == '\0')
+ RTMsgError("Failed creating directory '%s': %Rrc", pszDir, rc);
+ else
+ RTMsgError("Failed creating dir '%s' (for '%s'): %Rrc", pszFullFinalPath, pszDir, rc);
+ }
+ break;
+ }
+ pszFinalPath[cchComponent] = ch;
+
+ RTVfsDirRelease(hVfsCurDir);
+ hVfsCurDir = hVfsNextDir;
+ pszFinalPath += cchComponent;
+ }
+ }
+ else
+ RTVfsChainMsgError("RTVfsChainOpenParentDir", pszCopy, rc, offError, NULL);
+ RTStrFree(pszCopy);
+ }
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Create one directory.
+ *
+ * @returns exit code
+ * @param pOpts The mkdir option.
+ * @param pszDir The path to the new directory.
+ */
+static RTEXITCODE rtCmdMkDirOne(RTCMDMKDIROPTS const *pOpts, const char *pszDir)
+{
+ int rc;
+ if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszDir) )
+ rc = RTDirCreate(pszDir, pOpts->fMode, 0);
+ else
+ {
+ RTVFSDIR hVfsDir;
+ const char *pszChild;
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenParentDir(pszDir, 0 /*fOpen*/, &hVfsDir, &pszChild, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsDirCreateDir(hVfsDir, pszChild, pOpts->fMode, 0 /*fFlags*/, NULL);
+ RTVfsDirRelease(hVfsDir);
+ }
+ else
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenParentDir", pszDir, rc, offError, &ErrInfo.Core);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszDir);
+ return RTEXITCODE_SUCCESS;
+ }
+ return RTMsgErrorExitFailure("Failed to create '%s': %Rrc", pszDir, rc);
+}
+
+
+static RTEXITCODE RTCmdMkDir(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* operations */
+ { "--mode", 'm', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
+ { "--parents", 'p', RTGETOPT_REQ_NOTHING },
+ { "--always-use-vfs-chain-api", 'A', RTGETOPT_REQ_NOTHING },
+ { "--allow-content-indexing", 'i', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc);
+
+ RTCMDMKDIROPTS Opts;
+ Opts.fVerbose = false;
+ Opts.fParents = false;
+ Opts.fAlwaysUseChainApi = false;
+ Opts.fCreateFlags = RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_SET;
+ Opts.fMode = 0775 | RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY;
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ case 'm':
+ /** @todo DOS+NT attributes and symbolic notation. */
+ Opts.fMode &= ~07777;
+ Opts.fMode |= ValueUnion.u32 & 07777;
+ break;
+
+ case 'p':
+ Opts.fParents = true;
+ break;
+
+ case 'v':
+ Opts.fVerbose = true;
+ break;
+
+ case 'A':
+ Opts.fAlwaysUseChainApi = true;
+ break;
+
+ case 'i':
+ Opts.fCreateFlags &= ~RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_SET;
+ Opts.fCreateFlags |= RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [options] <dir> [..]\n"
+ "\n"
+ "Options:\n"
+ " -m <mode>, --mode <mode>\n"
+ " The creation mode. Default is 0775.\n"
+ " -p, --parent\n"
+ " Create parent directories too. Ignore any existing directories.\n"
+ " -v, --verbose\n"
+ " Tell which directories get created.\n"
+ " -A, --always-use-vfs-chain-api\n"
+ " Always use the VFS API.\n"
+ " -i, --allow-content-indexing\n"
+ " Don't set flags to disable context indexing on windows.\n"
+ , papszArgs[0]);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+
+ /*
+ * No files means error.
+ */
+ if (rc != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No directories specified.\n");
+
+ /*
+ * Work thru the specified dirs.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ while (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ if (Opts.fParents)
+ rc = rtCmdMkDirOneWithParents(&Opts, ValueUnion.psz);
+ else
+ rc = rtCmdMkDirOne(&Opts, ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+
+ /* next */
+ rc = RTGetOpt(&GetState, &ValueUnion);
+ }
+ if (rc != 0)
+ rcExit = RTGetOptPrintError(rc, &ValueUnion);
+
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTCmdMkDir(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTNtDbgHelp.cpp b/src/VBox/Runtime/tools/RTNtDbgHelp.cpp
new file mode 100644
index 00000000..39cd2da7
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTNtDbgHelp.cpp
@@ -0,0 +1,393 @@
+/* $Id: RTNtDbgHelp.cpp $ */
+/** @file
+ * IPRT - RTNtDbgHelp - Tool for working/exploring DbgHelp.dll.
+ */
+
+/*
+ * Copyright (C) 2013-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/win/windows.h>
+#include <iprt/win/dbghelp.h>
+
+#include <iprt/alloca.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/env.h>
+#include <iprt/initterm.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/errcore.h>
+
+#include <iprt/win/lazy-dbghelp.h>
+
+#include <iprt/ldrlazy.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Debug module record.
+ *
+ * Used for dumping the whole context.
+ */
+typedef struct RTNTDBGHELPMOD
+{
+ /** The list bits. */
+ RTLISTNODE ListEntry;
+ /** The module address. */
+ uint64_t uModAddr;
+ /** Pointer to the name part of szFullName. */
+ char *pszName;
+ /** The module name. */
+ char szFullName[1];
+} RTNTDBGHELPMOD;
+/** Pointer to a debug module. */
+typedef RTNTDBGHELPMOD *PRTNTDBGHELPMOD;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Verbosity level. */
+static int g_iOptVerbose = 1;
+
+/** Fake process handle. */
+static HANDLE g_hFake = (HANDLE)0x1234567;
+/** Number of modules in the list. */
+static uint32_t g_cModules = 0;
+/** Module list. */
+static RTLISTANCHOR g_ModuleList;
+/** Set when initialized, clear until then. Lazy init on first operation. */
+static bool g_fInitialized = false;
+
+/** The current address register. */
+static uint64_t g_uCurAddress = 0;
+
+
+
+/**
+ * For debug/verbose output.
+ *
+ * @param iMin The minimum verbosity level for this message.
+ * @param pszFormat The format string.
+ * @param ... The arguments referenced in the format string.
+ */
+static void infoPrintf(int iMin, const char *pszFormat, ...)
+{
+ if (g_iOptVerbose >= iMin)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ RTPrintf("info: ");
+ RTPrintfV(pszFormat, va);
+ va_end(va);
+ }
+}
+
+static BOOL CALLBACK symDebugCallback64(HANDLE hProcess, ULONG uAction, ULONG64 ullData, ULONG64 ullUserCtx)
+{
+ NOREF(hProcess); NOREF(ullUserCtx);
+ switch (uAction)
+ {
+ case CBA_DEBUG_INFO:
+ {
+ const char *pszMsg = (const char *)(uintptr_t)ullData;
+ size_t cchMsg = strlen(pszMsg);
+ if (cchMsg > 0 && pszMsg[cchMsg - 1] == '\n')
+ RTPrintf("cba_debug_info: %s", pszMsg);
+ else
+ RTPrintf("cba_debug_info: %s\n", pszMsg);
+ return TRUE;
+ }
+
+ case CBA_DEFERRED_SYMBOL_LOAD_CANCEL:
+ return FALSE;
+
+ case CBA_EVENT:
+ return FALSE;
+
+ default:
+ RTPrintf("cba_???: uAction=%#x ullData=%#llx\n", uAction, ullData);
+ break;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Lazy initialization.
+ * @returns Exit code with any relevant complaints printed.
+ */
+static RTEXITCODE ensureInitialized(void)
+{
+ if (!g_fInitialized)
+ {
+ if (!SymInitialize(g_hFake, NULL, FALSE))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "SymInitialied failed: %u\n", GetLastError());
+ if (!SymRegisterCallback64(g_hFake, symDebugCallback64, 0))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "SymRegisterCallback64 failed: %u\n", GetLastError());
+ g_fInitialized = true;
+ infoPrintf(2, "SymInitialized(,,)\n");
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Loads the given module, the address is either automatic or a previously given
+ * one.
+ *
+ * @returns Exit code with any relevant complaints printed.
+ * @param pszFile The file to load.
+ */
+static RTEXITCODE loadModule(const char *pszFile)
+{
+ RTEXITCODE rcExit = ensureInitialized();
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ uint64_t uModAddrReq = g_uCurAddress == 0 ? UINT64_C(0x1000000) * g_cModules : g_uCurAddress;
+ uint64_t uModAddrGot = SymLoadModuleEx(g_hFake, NULL /*hFile*/, pszFile, NULL /*pszModuleName*/,
+ uModAddrReq, 0, NULL /*pData*/, 0 /*fFlags*/);
+ if (uModAddrGot == 0)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "SymLoadModuleEx failed: %u\n", GetLastError());
+
+ size_t cbFullName = strlen(pszFile) + 1;
+ PRTNTDBGHELPMOD pMod = (PRTNTDBGHELPMOD)RTMemAlloc(RT_UOFFSETOF_DYN(RTNTDBGHELPMOD, szFullName[cbFullName + 1]));
+ memcpy(pMod->szFullName, pszFile, cbFullName);
+ pMod->pszName = RTPathFilename(pMod->szFullName);
+ pMod->uModAddr = uModAddrGot;
+ RTListAppend(&g_ModuleList, &pMod->ListEntry);
+ infoPrintf(1, "%#018RX64 %s\n", pMod->uModAddr, pMod->pszName);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Translates SYM_TYPE to string.
+ *
+ * @returns String.
+ * @param enmType The symbol type value.
+ */
+static const char *symTypeName(SYM_TYPE enmType)
+{
+ switch (enmType)
+ {
+ case SymCoff: return "SymCoff";
+ case SymCv: return "SymCv";
+ case SymPdb: return "SymPdb";
+ case SymExport: return "SymExport";
+ case SymDeferred: return "SymDeferred";
+ case SymSym: return "SymSym";
+ case SymDia: return "SymDia";
+ case SymVirtual: return "SymVirtual";
+ default:
+ {
+ static char s_szBuf[32];
+ RTStrPrintf(s_szBuf, sizeof(s_szBuf), "Unknown-%#x", enmType);
+ return s_szBuf;
+ }
+ }
+}
+
+
+/**
+ * Symbol enumeration callback.
+ *
+ * @returns TRUE (continue enum).
+ * @param pSymInfo The symbol info.
+ * @param cbSymbol The symbol length (calculated).
+ * @param pvUser NULL.
+ */
+static BOOL CALLBACK dumpSymbolCallback(PSYMBOL_INFO pSymInfo, ULONG cbSymbol, PVOID pvUser)
+{
+ NOREF(pvUser);
+ RTPrintf(" %#018RX64 LB %#07x %s\n", pSymInfo->Address, cbSymbol, pSymInfo->Name);
+ return TRUE;
+}
+
+/**
+ * Dumps all info.
+ * @returns Exit code with any relevant complaints printed.
+ */
+static RTEXITCODE dumpAll(void)
+{
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ PRTNTDBGHELPMOD pMod;
+ RTListForEach(&g_ModuleList, pMod, RTNTDBGHELPMOD, ListEntry)
+ {
+ RTPrintf("*** %#018RX64 - %s ***\n", pMod->uModAddr, pMod->szFullName);
+
+ static const int8_t s_acbVariations[] = { 0, -4, -8, -12, -16, -20, -24, -28, -32, 4, 8, 12, 16, 20, 24, 28, 32 };
+ unsigned iVariation = 0;
+ union
+ {
+ IMAGEHLP_MODULE64 ModInfo;
+ uint8_t abPadding[sizeof(IMAGEHLP_MODULE64) + 64];
+ } u;
+
+ BOOL fRc;
+ do
+ {
+ RT_ZERO(u.ModInfo);
+ u.ModInfo.SizeOfStruct = sizeof(u.ModInfo) + s_acbVariations[iVariation++];
+ fRc = SymGetModuleInfo64(g_hFake, pMod->uModAddr, &u.ModInfo);
+ } while (!fRc && GetLastError() == ERROR_INVALID_PARAMETER && iVariation < RT_ELEMENTS(s_acbVariations));
+
+ if (fRc)
+ {
+ RTPrintf(" BaseOfImage = %#018llx\n", u.ModInfo.BaseOfImage);
+ RTPrintf(" ImageSize = %#010x\n", u.ModInfo.ImageSize);
+ RTPrintf(" TimeDateStamp = %#010x\n", u.ModInfo.TimeDateStamp);
+ RTPrintf(" CheckSum = %#010x\n", u.ModInfo.CheckSum);
+ RTPrintf(" NumSyms = %#010x (%u)\n", u.ModInfo.NumSyms, u.ModInfo.NumSyms);
+ RTPrintf(" SymType = %s\n", symTypeName(u.ModInfo.SymType));
+ RTPrintf(" ModuleName = %.32s\n", u.ModInfo.ModuleName);
+ RTPrintf(" ImageName = %.256s\n", u.ModInfo.ImageName);
+ RTPrintf(" LoadedImageName = %.256s\n", u.ModInfo.LoadedImageName);
+ RTPrintf(" LoadedPdbName = %.256s\n", u.ModInfo.LoadedPdbName);
+ RTPrintf(" CVSig = %#010x\n", u.ModInfo.CVSig);
+ /** @todo CVData. */
+ RTPrintf(" PdbSig = %#010x\n", u.ModInfo.PdbSig);
+ RTPrintf(" PdbSig70 = %RTuuid\n", &u.ModInfo.PdbSig70);
+ RTPrintf(" PdbAge = %#010x\n", u.ModInfo.PdbAge);
+ RTPrintf(" PdbUnmatched = %RTbool\n", u.ModInfo.PdbUnmatched);
+ RTPrintf(" DbgUnmatched = %RTbool\n", u.ModInfo.DbgUnmatched);
+ RTPrintf(" LineNumbers = %RTbool\n", u.ModInfo.LineNumbers);
+ RTPrintf(" GlobalSymbols = %RTbool\n", u.ModInfo.GlobalSymbols);
+ RTPrintf(" TypeInfo = %RTbool\n", u.ModInfo.TypeInfo);
+ RTPrintf(" SourceIndexed = %RTbool\n", u.ModInfo.SourceIndexed);
+ RTPrintf(" Publics = %RTbool\n", u.ModInfo.Publics);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "SymGetModuleInfo64 failed: %u\n", GetLastError());
+
+ if (!SymEnumSymbols(g_hFake, pMod->uModAddr, NULL, dumpSymbolCallback, NULL))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "SymEnumSymbols failed: %u\n", GetLastError());
+
+ }
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ RTListInit(&g_ModuleList);
+
+ /*
+ * Parse options.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--dump-all", 'd', RTGETOPT_REQ_NOTHING },
+ { "--load", 'l', RTGETOPT_REQ_STRING },
+ { "--set-address", 'a', RTGETOPT_REQ_UINT64 },
+#define OPT_SET_DEBUG_INFO 0x1000
+ { "--set-debug-info", OPT_SET_DEBUG_INFO, RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ //const char *pszOutput = "-";
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ switch (ch)
+ {
+ case 'v':
+ g_iOptVerbose++;
+ break;
+
+ case 'q':
+ g_iOptVerbose++;
+ break;
+
+ case 'l':
+ rcExit = loadModule(ValueUnion.psz);
+ break;
+
+ case 'a':
+ g_uCurAddress = ValueUnion.u64;
+ break;
+
+ case 'd':
+ rcExit = dumpAll();
+ break;
+
+ case OPT_SET_DEBUG_INFO:
+ rcExit = ensureInitialized();
+ if (rcExit == RTEXITCODE_SUCCESS && !SymSetOptions(SymGetOptions() | SYMOPT_DEBUG))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "SymSetOptions failed: %u\n", GetLastError());
+ break;
+
+
+ case 'V':
+ RTPrintf("$Revision: 155244 $");
+ break;
+
+ case 'h':
+ RTPrintf("usage: %s [-v|--verbose] [-q|--quiet] [--set-debug-info] [-a <addr>] [-l <file>] [-d] [...]\n"
+ " or: %s [-V|--version]\n"
+ " or: %s [-h|--help]\n",
+ argv[0], argv[0], argv[0]);
+ return RTEXITCODE_SUCCESS;
+
+ case VINF_GETOPT_NOT_OPTION:
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+ }
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTRm.cpp b/src/VBox/Runtime/tools/RTRm.cpp
new file mode 100644
index 00000000..46df757d
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTRm.cpp
@@ -0,0 +1,54 @@
+/* $Id: RTRm.cpp $ */
+/** @file
+ * IPRT - Remove Directory Entries Utility.
+ */
+
+/*
+ * Copyright (C) 2013-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/path.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTPathRmCmd(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTRmDir.cpp b/src/VBox/Runtime/tools/RTRmDir.cpp
new file mode 100644
index 00000000..6d9dd26f
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTRmDir.cpp
@@ -0,0 +1,369 @@
+/* $Id: RTRmDir.cpp $ */
+/** @file
+ * IPRT - Removes directory.
+ */
+
+/*
+ * Copyright (C) 2013-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/path.h>
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+#include <iprt/vfs.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/getopt.h>
+#include <iprt/buildconfig.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RTCMDRMDIROPTS
+{
+ /** -v, --verbose */
+ bool fVerbose;
+ /** -p, --parents */
+ bool fParents;
+ /** Don't fail if directories that aren't empty. */
+ bool fIgnoreNotEmpty;
+ /** Don't fail a directory doesn't exist (i.e. has already been removed). */
+ bool fIgnoreNonExisting;
+ /** Whether to always use the VFS chain API (for testing). */
+ bool fAlwaysUseChainApi;
+} RTCMDRMDIROPTS;
+
+
+/**
+ * Create one directory and any missing parent directories.
+ *
+ * @returns exit code
+ * @param pOpts The mkdir option.
+ * @param pszDir The path to the new directory.
+ */
+static int rtCmdRmDirOneWithParents(RTCMDRMDIROPTS const *pOpts, const char *pszDir)
+{
+ /* We need a copy we can work with here. */
+ char *pszCopy = RTStrDup(pszDir);
+ if (!pszCopy)
+ return RTMsgErrorExitFailure("Out of string memory!");
+
+ int rc;
+ if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszDir) )
+ {
+ size_t cchCopy = strlen(pszCopy);
+ do
+ {
+ rc = RTDirRemove(pszCopy);
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszCopy);
+ }
+ else if ((rc == VERR_PATH_NOT_FOUND || rc == VERR_FILE_NOT_FOUND) && pOpts->fIgnoreNonExisting)
+ rc = VINF_SUCCESS;
+ else
+ {
+ if ((rc == VERR_DIR_NOT_EMPTY || rc == VERR_SHARING_VIOLATION) && pOpts->fIgnoreNotEmpty)
+ rc = VINF_SUCCESS;
+ else
+ RTMsgError("Failed to remove directory '%s': %Rrc", pszCopy, rc);
+ break;
+ }
+
+ /* Strip off a component. */
+ while (cchCopy > 0 && RTPATH_IS_SLASH(pszCopy[cchCopy - 1]))
+ cchCopy--;
+ while (cchCopy > 0 && !RTPATH_IS_SLASH(pszCopy[cchCopy - 1]))
+ cchCopy--;
+ while (cchCopy > 0 && RTPATH_IS_SLASH(pszCopy[cchCopy - 1]))
+ cchCopy--;
+ pszCopy[cchCopy] = '\0';
+ } while (cchCopy > 0);
+ }
+ else
+ {
+ /*
+ * Strip the final path element from the pszDir spec.
+ */
+ char *pszFinalPath;
+ char *pszSpec;
+ uint32_t offError;
+ rc = RTVfsChainSplitOffFinalPath(pszCopy, &pszSpec, &pszFinalPath, &offError);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Open the root director/whatever.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ RTVFSDIR hVfsBaseDir;
+ if (pszSpec)
+ {
+ rc = RTVfsChainOpenDir(pszSpec, 0 /*fOpen*/, &hVfsBaseDir, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ RTVfsChainMsgError("RTVfsChainOpenDir", pszSpec, rc, offError, &ErrInfo.Core);
+ else if (!pszFinalPath)
+ pszFinalPath = RTStrEnd(pszSpec, RTSTR_MAX);
+ }
+ else if (!RTPathStartsWithRoot(pszFinalPath))
+ {
+ rc = RTVfsDirOpenNormal(".", 0 /*fOpen*/, &hVfsBaseDir);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to open '.' (for %s): %Rrc", rc, pszFinalPath);
+ }
+ else
+ {
+ char *pszRoot = pszFinalPath;
+ pszFinalPath = RTPathSkipRootSpec(pszFinalPath);
+ char const chSaved = *pszFinalPath;
+ *pszFinalPath = '\0';
+ rc = RTVfsDirOpenNormal(pszRoot, 0 /*fOpen*/, &hVfsBaseDir);
+ *pszFinalPath = chSaved;
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to open root dir for '%s': %Rrc", rc, pszRoot);
+ }
+
+ /*
+ * Walk the path component by component, starting at the end.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchFinalPath = strlen(pszFinalPath);
+ while (RT_SUCCESS(rc) && cchFinalPath > 0)
+ {
+ rc = RTVfsDirRemoveDir(hVfsBaseDir, pszFinalPath, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszCopy);
+ }
+ else if ((rc == VERR_PATH_NOT_FOUND || rc == VERR_FILE_NOT_FOUND) && pOpts->fIgnoreNonExisting)
+ rc = VINF_SUCCESS;
+ else
+ {
+ if ((rc == VERR_DIR_NOT_EMPTY || rc == VERR_SHARING_VIOLATION) && pOpts->fIgnoreNotEmpty)
+ rc = VINF_SUCCESS;
+ else if (pszSpec)
+ RTMsgError("Failed to remove directory '%s:%s': %Rrc", pszSpec, pszFinalPath, rc);
+ else
+ RTMsgError("Failed to remove directory '%s': %Rrc", pszFinalPath, rc);
+ break;
+ }
+
+ /* Strip off a component. */
+ while (cchFinalPath > 0 && RTPATH_IS_SLASH(pszFinalPath[cchFinalPath - 1]))
+ cchFinalPath--;
+ while (cchFinalPath > 0 && !RTPATH_IS_SLASH(pszFinalPath[cchFinalPath - 1]))
+ cchFinalPath--;
+ while (cchFinalPath > 0 && RTPATH_IS_SLASH(pszFinalPath[cchFinalPath - 1]))
+ cchFinalPath--;
+ pszFinalPath[cchFinalPath] = '\0';
+ }
+
+ RTVfsDirRelease(hVfsBaseDir);
+ }
+ }
+ else
+ RTVfsChainMsgError("RTVfsChainOpenParentDir", pszCopy, rc, offError, NULL);
+ }
+ RTStrFree(pszCopy);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Removes one directory.
+ *
+ * @returns exit code
+ * @param pOpts The mkdir option.
+ * @param pszDir The path to the new directory.
+ */
+static RTEXITCODE rtCmdRmDirOne(RTCMDRMDIROPTS const *pOpts, const char *pszDir)
+{
+ int rc;
+ if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszDir) )
+ rc = RTDirRemove(pszDir);
+ else
+ {
+ RTVFSDIR hVfsDir;
+ const char *pszChild;
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenParentDir(pszDir, 0 /*fOpen*/, &hVfsDir, &pszChild, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsDirRemoveDir(hVfsDir, pszChild, 0 /*fFlags*/);
+ RTVfsDirRelease(hVfsDir);
+ }
+ else
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenParentDir", pszDir, rc, offError, &ErrInfo.Core);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszDir);
+ return RTEXITCODE_SUCCESS;
+ }
+ if ((rc == VERR_DIR_NOT_EMPTY || rc == VERR_SHARING_VIOLATION) && pOpts->fIgnoreNotEmpty)
+ return RTEXITCODE_SUCCESS; /** @todo be verbose about this? */
+ if ((rc == VERR_PATH_NOT_FOUND || rc == VERR_FILE_NOT_FOUND) && pOpts->fIgnoreNonExisting)
+ return RTEXITCODE_SUCCESS; /** @todo be verbose about this? */
+ return RTMsgErrorExitFailure("Failed to remove '%s': %Rrc", pszDir, rc);
+}
+
+
+static RTEXITCODE RTCmdRmDir(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* operations */
+ { "--parents", 'p', RTGETOPT_REQ_NOTHING },
+ { "--ignore-fail-on-non-empty", 'F', RTGETOPT_REQ_NOTHING },
+ { "--ignore-non-existing", 'E', RTGETOPT_REQ_NOTHING },
+ { "--always-use-vfs-chain-api", 'A', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc);
+
+ RTCMDRMDIROPTS Opts;
+ Opts.fVerbose = false;
+ Opts.fParents = false;
+ Opts.fIgnoreNotEmpty = false;
+ Opts.fIgnoreNonExisting = false;
+ Opts.fAlwaysUseChainApi = false;
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ case 'p':
+ Opts.fParents = true;
+ break;
+
+ case 'v':
+ Opts.fVerbose = true;
+ break;
+
+ case 'A':
+ Opts.fAlwaysUseChainApi = true;
+ break;
+
+ case 'E':
+ Opts.fIgnoreNonExisting = true;
+ break;
+
+ case 'F':
+ Opts.fIgnoreNotEmpty = true;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [options] <dir> [..]\n"
+ "\n"
+ "Removes empty directories.\n"
+ "\n"
+ "Options:\n"
+ " -p, --parent\n"
+ " Remove specified parent directories too.\n"
+ " -F, --ignore-fail-on-non-empty\n"
+ " Do not fail if a directory is not empty, just ignore it.\n"
+ " This is really handy with the -p option.\n"
+ " -E, --ignore-non-existing\n"
+ " Do not fail if a specified directory is not there.\n"
+ " -v, --verbose\n"
+ " Tell which directories get remove.\n"
+ " -A, --always-use-vfs-chain-api\n"
+ " Always use the VFS API.\n"
+ , papszArgs[0]);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+
+ /*
+ * No files means error.
+ */
+ if (rc != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No directories specified.\n");
+
+ /*
+ * Work thru the specified dirs.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ while (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ if (Opts.fParents)
+ rc = rtCmdRmDirOneWithParents(&Opts, ValueUnion.psz);
+ else
+ rc = rtCmdRmDirOne(&Opts, ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+
+ /* next */
+ rc = RTGetOpt(&GetState, &ValueUnion);
+ }
+ if (rc != 0)
+ rcExit = RTGetOptPrintError(rc, &ValueUnion);
+
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTCmdRmDir(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTShutdown.cpp b/src/VBox/Runtime/tools/RTShutdown.cpp
new file mode 100644
index 00000000..d632e474
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTShutdown.cpp
@@ -0,0 +1,114 @@
+/* $Id: RTShutdown.cpp $ */
+/** @file
+ * IPRT Testcase - System Shutdown.
+ */
+
+/*
+ * Copyright (C) 2012-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/system.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--halt", 'H', RTGETOPT_REQ_NOTHING },
+ { "--poweroff", 'p', RTGETOPT_REQ_NOTHING },
+ { "--reboot", 'r', RTGETOPT_REQ_NOTHING },
+ { "--force", 'f', RTGETOPT_REQ_NOTHING },
+ { "--delay", 'd', RTGETOPT_REQ_UINT32 },
+ { "--message", 'm', RTGETOPT_REQ_STRING }
+ };
+
+ const char *pszMsg = "RTShutdown";
+ RTMSINTERVAL cMsDelay = 0;
+ uint32_t fFlags = RTSYSTEM_SHUTDOWN_POWER_OFF | RTSYSTEM_SHUTDOWN_PLANNED;
+
+ RTGETOPTSTATE GetState;
+ rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ for (;;)
+ {
+ RTGETOPTUNION ValueUnion;
+ rc = RTGetOpt(&GetState, &ValueUnion);
+ if (rc == 0)
+ break;
+ switch (rc)
+ {
+ case 'H': fFlags = (fFlags & ~RTSYSTEM_SHUTDOWN_ACTION_MASK) | RTSYSTEM_SHUTDOWN_HALT; break;
+ case 'p': fFlags = (fFlags & ~RTSYSTEM_SHUTDOWN_ACTION_MASK) | RTSYSTEM_SHUTDOWN_POWER_OFF_HALT; break;
+ case 'r': fFlags = (fFlags & ~RTSYSTEM_SHUTDOWN_ACTION_MASK) | RTSYSTEM_SHUTDOWN_REBOOT; break;
+ case 'f': fFlags |= RTSYSTEM_SHUTDOWN_FORCE; break;
+ case 'd': cMsDelay = ValueUnion.u32; break;
+ case 'm': pszMsg = ValueUnion.psz; break;
+
+ case 'h':
+ RTPrintf("Usage: RTShutdown [-H|-p|-r] [-f] [-d <milliseconds>] [-m <msg>]\n");
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * Do the deed.
+ */
+ rc = RTSystemShutdown(cMsDelay, fFlags, pszMsg);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTSystemShutdown(%u, %#x, \"%s\") returned %Rrc\n", cMsDelay, fFlags, pszMsg, rc);
+ return RTEXITCODE_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/tools/RTSignTool.cpp b/src/VBox/Runtime/tools/RTSignTool.cpp
new file mode 100644
index 00000000..99cffa44
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTSignTool.cpp
@@ -0,0 +1,6330 @@
+/* $Id: RTSignTool.cpp $ */
+/** @file
+ * IPRT - Signing Tool.
+ */
+
+/*
+ * Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/file.h>
+#include <iprt/initterm.h>
+#include <iprt/ldr.h>
+#include <iprt/message.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#ifdef RT_OS_WINDOWS
+# include <iprt/utf16.h>
+#endif
+#include <iprt/uuid.h>
+#include <iprt/zero.h>
+#include <iprt/formats/asn1.h>
+#include <iprt/formats/mach-o.h>
+#ifndef RT_OS_WINDOWS
+# include <iprt/formats/pecoff.h>
+#else
+# define WIN_CERTIFICATE_ALIGNMENT UINT32_C(8) /* from pecoff.h */
+#endif
+#include <iprt/crypto/applecodesign.h>
+#include <iprt/crypto/digest.h>
+#include <iprt/crypto/key.h>
+#include <iprt/crypto/x509.h>
+#include <iprt/crypto/pkcs7.h>
+#include <iprt/crypto/store.h>
+#include <iprt/crypto/spc.h>
+#include <iprt/crypto/tsp.h>
+#include <iprt/cpp/ministring.h>
+#ifdef VBOX
+# include <VBox/sup.h> /* Certificates */
+#endif
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+# include <iprt/win/imagehlp.h>
+# include <wincrypt.h>
+# include <ncrypt.h>
+#endif
+#include "internal/ldr.h" /* for IMAGE_XX_SIGNATURE defines */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define OPT_OFF_CERT_FILE 0 /**< signtool /f file */
+#define OPT_OFF_CERT_SHA1 1 /**< signtool /sha1 thumbprint */
+#define OPT_OFF_CERT_SUBJECT 2 /**< signtool /n name */
+#define OPT_OFF_CERT_STORE 3 /**< signtool /s store */
+#define OPT_OFF_CERT_STORE_MACHINE 4 /**< signtool /sm */
+#define OPT_OFF_KEY_FILE 5 /**< no signtool equivalent, other than maybe /f. */
+#define OPT_OFF_KEY_PASSWORD 6 /**< signtool /p pass */
+#define OPT_OFF_KEY_PASSWORD_FILE 7 /**< no signtool equivalent. */
+#define OPT_OFF_KEY_NAME 8 /**< signtool /kc name */
+#define OPT_OFF_KEY_PROVIDER 9 /**< signtool /csp name (CSP = cryptographic service provider) */
+
+#define OPT_CERT_KEY_SWITCH_CASES(a_Instance, a_uBase, a_chOpt, a_ValueUnion, a_rcExit) \
+ case (a_uBase) + OPT_OFF_CERT_FILE: \
+ case (a_uBase) + OPT_OFF_CERT_SHA1: \
+ case (a_uBase) + OPT_OFF_CERT_SUBJECT: \
+ case (a_uBase) + OPT_OFF_CERT_STORE: \
+ case (a_uBase) + OPT_OFF_CERT_STORE_MACHINE: \
+ case (a_uBase) + OPT_OFF_KEY_FILE: \
+ case (a_uBase) + OPT_OFF_KEY_PASSWORD: \
+ case (a_uBase) + OPT_OFF_KEY_PASSWORD_FILE: \
+ case (a_uBase) + OPT_OFF_KEY_NAME: \
+ case (a_uBase) + OPT_OFF_KEY_PROVIDER: \
+ a_rcExit = a_Instance.handleOption((a_chOpt) - (a_uBase), &(a_ValueUnion)); \
+ break
+
+#define OPT_CERT_KEY_GETOPTDEF_ENTRIES(a_szPrefix, a_szSuffix, a_uBase) \
+ { a_szPrefix "cert-file" a_szSuffix, (a_uBase) + OPT_OFF_CERT_FILE, RTGETOPT_REQ_STRING }, \
+ { a_szPrefix "cert-sha1" a_szSuffix, (a_uBase) + OPT_OFF_CERT_SHA1, RTGETOPT_REQ_STRING }, \
+ { a_szPrefix "cert-subject" a_szSuffix, (a_uBase) + OPT_OFF_CERT_SUBJECT, RTGETOPT_REQ_STRING }, \
+ { a_szPrefix "cert-store" a_szSuffix, (a_uBase) + OPT_OFF_CERT_STORE, RTGETOPT_REQ_STRING }, \
+ { a_szPrefix "cert-machine-store" a_szSuffix, (a_uBase) + OPT_OFF_CERT_STORE_MACHINE, RTGETOPT_REQ_NOTHING }, \
+ { a_szPrefix "key-file" a_szSuffix, (a_uBase) + OPT_OFF_KEY_FILE, RTGETOPT_REQ_STRING }, \
+ { a_szPrefix "key-password" a_szSuffix, (a_uBase) + OPT_OFF_KEY_PASSWORD, RTGETOPT_REQ_STRING }, \
+ { a_szPrefix "key-password-file" a_szSuffix, (a_uBase) + OPT_OFF_KEY_PASSWORD_FILE, RTGETOPT_REQ_STRING }, \
+ { a_szPrefix "key-name" a_szSuffix, (a_uBase) + OPT_OFF_KEY_NAME, RTGETOPT_REQ_STRING }, \
+ { a_szPrefix "key-provider" a_szSuffix, (a_uBase) + OPT_OFF_KEY_PROVIDER, RTGETOPT_REQ_STRING }
+
+#define OPT_CERT_KEY_GETOPTDEF_COMPAT_ENTRIES(a_uBase) \
+ { "/f", (a_uBase) + OPT_OFF_CERT_FILE, RTGETOPT_REQ_STRING }, \
+ { "/sha1", (a_uBase) + OPT_OFF_CERT_SHA1, RTGETOPT_REQ_STRING }, \
+ { "/n", (a_uBase) + OPT_OFF_CERT_SUBJECT, RTGETOPT_REQ_STRING }, \
+ { "/s", (a_uBase) + OPT_OFF_CERT_STORE, RTGETOPT_REQ_STRING }, \
+ { "/sm", (a_uBase) + OPT_OFF_CERT_STORE_MACHINE, RTGETOPT_REQ_NOTHING }, \
+ { "/p", (a_uBase) + OPT_OFF_KEY_PASSWORD, RTGETOPT_REQ_STRING }, \
+ { "/kc", (a_uBase) + OPT_OFF_KEY_NAME, RTGETOPT_REQ_STRING }, \
+ { "/csp", (a_uBase) + OPT_OFF_KEY_PROVIDER, RTGETOPT_REQ_STRING }
+
+#define OPT_CERT_KEY_SYNOPSIS(a_szPrefix, a_szSuffix) \
+ "[" a_szPrefix "cert-file" a_szSuffix " <file.pem|file.crt>] " \
+ "[" a_szPrefix "cert-sha1" a_szSuffix " <fingerprint>] " \
+ "[" a_szPrefix "cert-subject" a_szSuffix " <part-name>] " \
+ "[" a_szPrefix "cert-store" a_szSuffix " <store>] " \
+ "[" a_szPrefix "cert-machine-store" a_szSuffix "] " \
+ "[" a_szPrefix "key-file" a_szSuffix " <file.pem|file.p12>] " \
+ "[" a_szPrefix "key-password" a_szSuffix " <password>] " \
+ "[" a_szPrefix "key-password-file" a_szSuffix " <file>|stdin] " \
+ "[" a_szPrefix "key-name" a_szSuffix " <name>] " \
+ "[" a_szPrefix "key-provider" a_szSuffix " <csp>] "
+
+#define OPT_HASH_PAGES 1200
+#define OPT_NO_HASH_PAGES 1201
+#define OPT_ADD_CERT 1202
+#define OPT_TIMESTAMP_TYPE 1203
+#define OPT_TIMESTAMP_TYPE_2 1204
+#define OPT_TIMESTAMP_OVERRIDE 1205
+#define OPT_NO_SIGNING_TIME 1206
+#define OPT_FILE_TYPE 1207
+#define OPT_IGNORED 1208
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Help detail levels. */
+typedef enum RTSIGNTOOLHELP
+{
+ RTSIGNTOOLHELP_USAGE,
+ RTSIGNTOOLHELP_FULL
+} RTSIGNTOOLHELP;
+
+
+/** Filetypes. */
+typedef enum RTSIGNTOOLFILETYPE
+{
+ RTSIGNTOOLFILETYPE_INVALID = 0,
+ RTSIGNTOOLFILETYPE_DETECT,
+ RTSIGNTOOLFILETYPE_EXE,
+ RTSIGNTOOLFILETYPE_CAT,
+ RTSIGNTOOLFILETYPE_UNKNOWN,
+ RTSIGNTOOLFILETYPE_END
+} RTSIGNTOOLFILETYPE;
+
+
+/**
+ * PKCS\#7 signature data.
+ */
+typedef struct SIGNTOOLPKCS7
+{
+ /** The file type. */
+ RTSIGNTOOLFILETYPE enmType;
+ /** The raw signature. */
+ uint8_t *pbBuf;
+ /** Size of the raw signature. */
+ size_t cbBuf;
+ /** The filename. */
+ const char *pszFilename;
+ /** The outer content info wrapper. */
+ RTCRPKCS7CONTENTINFO ContentInfo;
+ /** Pointer to the decoded SignedData inside the ContentInfo member. */
+ PRTCRPKCS7SIGNEDDATA pSignedData;
+
+ /** Newly encoded raw signature.
+ * @sa SignToolPkcs7_Encode() */
+ uint8_t *pbNewBuf;
+ /** Size of newly encoded raw signature. */
+ size_t cbNewBuf;
+
+} SIGNTOOLPKCS7;
+typedef SIGNTOOLPKCS7 *PSIGNTOOLPKCS7;
+
+
+/**
+ * PKCS\#7 signature data for executable.
+ */
+typedef struct SIGNTOOLPKCS7EXE : public SIGNTOOLPKCS7
+{
+ /** The module handle. */
+ RTLDRMOD hLdrMod;
+} SIGNTOOLPKCS7EXE;
+typedef SIGNTOOLPKCS7EXE *PSIGNTOOLPKCS7EXE;
+
+
+/**
+ * Data for the show exe (signature) command.
+ */
+typedef struct SHOWEXEPKCS7 : public SIGNTOOLPKCS7EXE
+{
+ /** The verbosity. */
+ unsigned cVerbosity;
+ /** The prefix buffer. */
+ char szPrefix[256];
+ /** Temporary buffer. */
+ char szTmp[4096];
+} SHOWEXEPKCS7;
+typedef SHOWEXEPKCS7 *PSHOWEXEPKCS7;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static RTEXITCODE HandleHelp(int cArgs, char **papszArgs);
+static RTEXITCODE HelpHelp(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel);
+static RTEXITCODE HandleVersion(int cArgs, char **papszArgs);
+static int HandleShowExeWorkerPkcs7DisplaySignerInfo(PSHOWEXEPKCS7 pThis, size_t offPrefix, PCRTCRPKCS7SIGNERINFO pSignerInfo);
+static int HandleShowExeWorkerPkcs7Display(PSHOWEXEPKCS7 pThis, PRTCRPKCS7SIGNEDDATA pSignedData, size_t offPrefix,
+ PCRTCRPKCS7CONTENTINFO pContentInfo);
+
+
+/*********************************************************************************************************************************
+* Certificate and Private Key Handling (options, ++). *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+
+/** @todo create a better fake certificate. */
+const unsigned char g_abFakeCertificate[] =
+{
+ 0x30, 0x82, 0x03, 0xb2, 0x30, 0x82, 0x02, 0x9a, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x31, /* 0x00000000: 0...0..........1 */
+ 0xba, 0xd6, 0xbc, 0x5d, 0x9a, 0xe0, 0xb0, 0x4e, 0xd4, 0xfa, 0xcc, 0xfb, 0x47, 0x00, 0x5c, 0x30, /* 0x00000010: ...]...N....G.\0 */
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x71, /* 0x00000020: ...*.H........0q */
+ 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x13, 0x54, 0x69, 0x6d, 0x65, 0x73, /* 0x00000030: 1.0...U....Times */
+ 0x74, 0x61, 0x6d, 0x70, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x32, 0x31, 0x0c, /* 0x00000040: tamp Signing 21. */
+ 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x03, 0x44, 0x65, 0x76, 0x31, 0x15, 0x30, 0x13, /* 0x00000050: 0...U....Dev1.0. */
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0c, 0x54, 0x65, 0x73, 0x74, 0x20, 0x43, 0x6f, 0x6d, 0x70, /* 0x00000060: ..U....Test Comp */
+ 0x61, 0x6e, 0x79, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x09, 0x53, 0x74, /* 0x00000070: any1.0...U....St */
+ 0x75, 0x74, 0x74, 0x67, 0x61, 0x72, 0x74, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, /* 0x00000080: uttgart1.0...U.. */
+ 0x0c, 0x02, 0x42, 0x42, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x44, /* 0x00000090: ..BB1.0...U....D */
+ 0x45, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x31, 0x30, /* 0x000000a0: E0...00010100010 */
+ 0x31, 0x5a, 0x17, 0x0d, 0x33, 0x36, 0x31, 0x32, 0x33, 0x31, 0x32, 0x32, 0x35, 0x39, 0x35, 0x39, /* 0x000000b0: 1Z..361231225959 */
+ 0x5a, 0x30, 0x71, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x13, 0x54, 0x69, /* 0x000000c0: Z0q1.0...U....Ti */
+ 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x20, /* 0x000000d0: mestamp Signing */
+ 0x32, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x03, 0x44, 0x65, 0x76, 0x31, /* 0x000000e0: 21.0...U....Dev1 */
+ 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0c, 0x54, 0x65, 0x73, 0x74, 0x20, 0x43, /* 0x000000f0: .0...U....Test C */
+ 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, /* 0x00000100: ompany1.0...U... */
+ 0x09, 0x53, 0x74, 0x75, 0x74, 0x74, 0x67, 0x61, 0x72, 0x74, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, /* 0x00000110: .Stuttgart1.0... */
+ 0x55, 0x04, 0x08, 0x0c, 0x02, 0x42, 0x42, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, /* 0x00000120: U....BB1.0...U.. */
+ 0x13, 0x02, 0x44, 0x45, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, /* 0x00000130: ..DE0.."0...*.H. */
+ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, /* 0x00000140: ............0... */
+ 0x02, 0x82, 0x01, 0x01, 0x00, 0xdb, 0x18, 0x63, 0x33, 0xf2, 0x08, 0x90, 0x5a, 0xab, 0xda, 0x88, /* 0x00000150: .......c3...Z... */
+ 0x73, 0x86, 0x49, 0xea, 0x8b, 0xaf, 0xcf, 0x67, 0x15, 0xa5, 0x39, 0xe6, 0xa2, 0x94, 0x0c, 0x3f, /* 0x00000160: s.I....g..9....? */
+ 0xa1, 0x2e, 0x6c, 0xd2, 0xdf, 0x01, 0x65, 0x6d, 0xed, 0x6c, 0x4c, 0xac, 0xe7, 0x77, 0x7a, 0x45, /* 0x00000170: ..l...em.lL..wzE */
+ 0x05, 0x6b, 0x24, 0xf3, 0xaf, 0x45, 0x35, 0x6e, 0x64, 0x0a, 0xac, 0x1d, 0x37, 0xe1, 0x33, 0xa4, /* 0x00000180: .k$..E5nd...7.3. */
+ 0x92, 0xec, 0x45, 0xe8, 0x99, 0xc1, 0xde, 0x6f, 0xab, 0x7c, 0xf0, 0xdc, 0xe2, 0xc5, 0x42, 0xa3, /* 0x00000190: ..E....o.|....B. */
+ 0xea, 0xf5, 0x8a, 0xf9, 0x0e, 0xe7, 0xb3, 0x35, 0xa2, 0x75, 0x5e, 0x87, 0xd2, 0x2a, 0xd1, 0x27, /* 0x000001a0: .......5.u^..*.' */
+ 0xa6, 0x79, 0x9e, 0xfe, 0x90, 0xbf, 0x97, 0xa4, 0xa1, 0xd8, 0xf7, 0xd7, 0x05, 0x59, 0x44, 0x27, /* 0x000001b0: .y...........YD' */
+ 0x39, 0x6e, 0x33, 0x01, 0x2e, 0x46, 0x92, 0x47, 0xbe, 0x50, 0x91, 0x26, 0x27, 0xe5, 0x4b, 0x3a, /* 0x000001c0: 9n3..F.G.P.&'.K: */
+ 0x76, 0x26, 0x64, 0x92, 0x0c, 0xa0, 0x54, 0x43, 0x6f, 0x56, 0xcc, 0x7b, 0xd0, 0xe3, 0xd8, 0x39, /* 0x000001d0: v&d...TCoV.{...9 */
+ 0x5f, 0xb9, 0x41, 0xda, 0x1c, 0x62, 0x88, 0x0c, 0x45, 0x03, 0x63, 0xf8, 0xff, 0xe5, 0x3e, 0x87, /* 0x000001e0: _.A..b..E.c...>. */
+ 0x0c, 0x75, 0xc9, 0xdd, 0xa2, 0xc0, 0x1b, 0x63, 0x19, 0xeb, 0x09, 0x9d, 0xa1, 0xbb, 0x0f, 0x63, /* 0x000001f0: .u.....c.......c */
+ 0x67, 0x1c, 0xa3, 0xfd, 0x2f, 0xd1, 0x2a, 0xda, 0xd8, 0x93, 0x66, 0x45, 0x54, 0xef, 0x8b, 0x6d, /* 0x00000200: g.....*...fET..m */
+ 0x12, 0x15, 0x0f, 0xd4, 0xb5, 0x04, 0x17, 0x30, 0x5b, 0xfa, 0x12, 0x96, 0x48, 0x5b, 0x38, 0x65, /* 0x00000210: .......0[...H[8e */
+ 0xfd, 0x8f, 0x0c, 0xa3, 0x11, 0x46, 0x49, 0xe0, 0x62, 0xc3, 0xcc, 0x34, 0xe6, 0xfb, 0xab, 0x51, /* 0x00000220: .....FI.b..4...Q */
+ 0xc3, 0xd4, 0x0b, 0xdc, 0x39, 0x93, 0x87, 0x90, 0x10, 0x9f, 0xce, 0x43, 0x27, 0x31, 0xd5, 0x4e, /* 0x00000230: ....9......C'1.N */
+ 0x52, 0x60, 0xf1, 0x93, 0xd5, 0x06, 0xc4, 0x4e, 0x65, 0xb6, 0x35, 0x4a, 0x64, 0x15, 0xf8, 0xaf, /* 0x00000240: R`.....Ne.5Jd... */
+ 0x71, 0xb2, 0x42, 0x50, 0x89, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x46, 0x30, 0x44, 0x30, 0x0e, /* 0x00000250: q.BP.......F0D0. */
+ 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x07, 0x80, 0x30, 0x13, /* 0x00000260: ..U...........0. */
+ 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x0c, 0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, /* 0x00000270: ..U.%..0...+.... */
+ 0x07, 0x03, 0x08, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x52, 0x9d, /* 0x00000280: ...0...U......R. */
+ 0x4d, 0xcd, 0x41, 0xe1, 0xd2, 0x68, 0x22, 0xd3, 0x10, 0x33, 0x01, 0xca, 0xff, 0x00, 0x1d, 0x27, /* 0x00000290: M.A..h"..3.....' */
+ 0xa4, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, /* 0x000002a0: ..0...*.H....... */
+ 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xc5, 0x5a, 0x51, 0x83, 0x68, 0x3f, 0x06, 0x39, 0x79, 0x13, /* 0x000002b0: .......ZQ.h?.9y. */
+ 0xa6, 0xf0, 0x1a, 0xf9, 0x29, 0x16, 0x2d, 0xa2, 0x07, 0xaa, 0x9b, 0xc3, 0x13, 0x88, 0x39, 0x69, /* 0x000002c0: ....).-.......9i */
+ 0xba, 0xf7, 0x0d, 0xfb, 0xc0, 0x6e, 0x3a, 0x0b, 0x49, 0x10, 0xd1, 0xbe, 0x36, 0x91, 0x3f, 0x9d, /* 0x000002d0: .....n:.I...6.?. */
+ 0xa1, 0xe8, 0xc4, 0x91, 0xf9, 0x02, 0xe1, 0xf1, 0x01, 0x15, 0x09, 0xb7, 0xa1, 0xf1, 0xec, 0x43, /* 0x000002e0: ...............C */
+ 0x0d, 0x73, 0xd1, 0x31, 0x02, 0x4a, 0xce, 0x21, 0xf2, 0xa7, 0x99, 0x7c, 0xee, 0x85, 0x54, 0xc0, /* 0x000002f0: .s.1.J.!...|..T. */
+ 0x55, 0x9b, 0x19, 0x37, 0xe8, 0xcf, 0x94, 0x41, 0x10, 0x6e, 0x67, 0xdd, 0x86, 0xaf, 0xb7, 0xfe, /* 0x00000300: U..7...A.ng..... */
+ 0x50, 0x05, 0xf6, 0xfb, 0x0a, 0xdf, 0x88, 0xb5, 0x59, 0x69, 0x98, 0x27, 0xf8, 0x81, 0x6a, 0x4a, /* 0x00000310: P.......Yi.'..jJ */
+ 0x7c, 0xf3, 0x63, 0xa9, 0x41, 0x78, 0x76, 0x12, 0xdb, 0x0e, 0x94, 0x0a, 0xdb, 0x1d, 0x3c, 0x87, /* 0x00000320: |.c.Axv.......<. */
+ 0x35, 0xca, 0x28, 0xeb, 0xb0, 0x62, 0x27, 0x69, 0xe2, 0xf3, 0x84, 0x48, 0xa2, 0x2d, 0xd7, 0x0e, /* 0x00000330: 5.(..b'i...H.-.. */
+ 0x4b, 0x6d, 0x39, 0xa7, 0x3e, 0x04, 0x94, 0x8e, 0xb6, 0x4b, 0x91, 0x01, 0x68, 0xf9, 0xd2, 0x75, /* 0x00000340: Km9.>....K..h..u */
+ 0x1b, 0xac, 0x42, 0x3b, 0x85, 0xfc, 0x5b, 0x48, 0x3a, 0x13, 0xe7, 0x1c, 0x17, 0xcd, 0x84, 0x89, /* 0x00000350: ..B;..[H:....... */
+ 0x9e, 0x5f, 0xe3, 0x77, 0xc0, 0xae, 0x34, 0xc3, 0x87, 0x76, 0x4a, 0x23, 0x30, 0xa0, 0xe1, 0x45, /* 0x00000360: ._.w..4..vJ#0..E */
+ 0x94, 0x2a, 0x5b, 0x6b, 0x5a, 0xf0, 0x1a, 0x7e, 0xa6, 0xc4, 0xed, 0xe4, 0xac, 0x5d, 0xdf, 0x87, /* 0x00000370: .*[kZ..~.....].. */
+ 0x8f, 0xc5, 0xb4, 0x8c, 0xbc, 0x70, 0xc1, 0xf7, 0xb2, 0x72, 0xbd, 0x73, 0xc9, 0x4e, 0xed, 0x8d, /* 0x00000380: .....p...r.s.N.. */
+ 0x29, 0x33, 0xe9, 0x14, 0xc1, 0x5e, 0xff, 0x39, 0xa8, 0xe7, 0x9a, 0x3b, 0x7a, 0x3c, 0xce, 0x5d, /* 0x00000390: )3...^.9...;z<.] */
+ 0x0f, 0x3c, 0x82, 0x90, 0xff, 0x81, 0x82, 0x00, 0x82, 0x5f, 0xba, 0x08, 0x79, 0xb1, 0x97, 0xc3, /* 0x000003a0: .<......._..y... */
+ 0x09, 0x75, 0xc0, 0x04, 0x9b, 0x67, /* 0x000003b0: .u...g */
+};
+
+const unsigned char g_abFakeRsaKey[] =
+{
+ 0x30, 0x82, 0x04, 0xa4, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xdb, 0x18, 0x63, 0x33, /* 0x00000000: 0.............c3 */
+ 0xf2, 0x08, 0x90, 0x5a, 0xab, 0xda, 0x88, 0x73, 0x86, 0x49, 0xea, 0x8b, 0xaf, 0xcf, 0x67, 0x15, /* 0x00000010: ...Z...s.I....g. */
+ 0xa5, 0x39, 0xe6, 0xa2, 0x94, 0x0c, 0x3f, 0xa1, 0x2e, 0x6c, 0xd2, 0xdf, 0x01, 0x65, 0x6d, 0xed, /* 0x00000020: .9....?..l...em. */
+ 0x6c, 0x4c, 0xac, 0xe7, 0x77, 0x7a, 0x45, 0x05, 0x6b, 0x24, 0xf3, 0xaf, 0x45, 0x35, 0x6e, 0x64, /* 0x00000030: lL..wzE.k$..E5nd */
+ 0x0a, 0xac, 0x1d, 0x37, 0xe1, 0x33, 0xa4, 0x92, 0xec, 0x45, 0xe8, 0x99, 0xc1, 0xde, 0x6f, 0xab, /* 0x00000040: ...7.3...E....o. */
+ 0x7c, 0xf0, 0xdc, 0xe2, 0xc5, 0x42, 0xa3, 0xea, 0xf5, 0x8a, 0xf9, 0x0e, 0xe7, 0xb3, 0x35, 0xa2, /* 0x00000050: |....B........5. */
+ 0x75, 0x5e, 0x87, 0xd2, 0x2a, 0xd1, 0x27, 0xa6, 0x79, 0x9e, 0xfe, 0x90, 0xbf, 0x97, 0xa4, 0xa1, /* 0x00000060: u^..*.'.y....... */
+ 0xd8, 0xf7, 0xd7, 0x05, 0x59, 0x44, 0x27, 0x39, 0x6e, 0x33, 0x01, 0x2e, 0x46, 0x92, 0x47, 0xbe, /* 0x00000070: ....YD'9n3..F.G. */
+ 0x50, 0x91, 0x26, 0x27, 0xe5, 0x4b, 0x3a, 0x76, 0x26, 0x64, 0x92, 0x0c, 0xa0, 0x54, 0x43, 0x6f, /* 0x00000080: P.&'.K:v&d...TCo */
+ 0x56, 0xcc, 0x7b, 0xd0, 0xe3, 0xd8, 0x39, 0x5f, 0xb9, 0x41, 0xda, 0x1c, 0x62, 0x88, 0x0c, 0x45, /* 0x00000090: V.{...9_.A..b..E */
+ 0x03, 0x63, 0xf8, 0xff, 0xe5, 0x3e, 0x87, 0x0c, 0x75, 0xc9, 0xdd, 0xa2, 0xc0, 0x1b, 0x63, 0x19, /* 0x000000a0: .c...>..u.....c. */
+ 0xeb, 0x09, 0x9d, 0xa1, 0xbb, 0x0f, 0x63, 0x67, 0x1c, 0xa3, 0xfd, 0x2f, 0xd1, 0x2a, 0xda, 0xd8, /* 0x000000b0: ......cg.....*.. */
+ 0x93, 0x66, 0x45, 0x54, 0xef, 0x8b, 0x6d, 0x12, 0x15, 0x0f, 0xd4, 0xb5, 0x04, 0x17, 0x30, 0x5b, /* 0x000000c0: .fET..m.......0[ */
+ 0xfa, 0x12, 0x96, 0x48, 0x5b, 0x38, 0x65, 0xfd, 0x8f, 0x0c, 0xa3, 0x11, 0x46, 0x49, 0xe0, 0x62, /* 0x000000d0: ...H[8e.....FI.b */
+ 0xc3, 0xcc, 0x34, 0xe6, 0xfb, 0xab, 0x51, 0xc3, 0xd4, 0x0b, 0xdc, 0x39, 0x93, 0x87, 0x90, 0x10, /* 0x000000e0: ..4...Q....9.... */
+ 0x9f, 0xce, 0x43, 0x27, 0x31, 0xd5, 0x4e, 0x52, 0x60, 0xf1, 0x93, 0xd5, 0x06, 0xc4, 0x4e, 0x65, /* 0x000000f0: ..C'1.NR`.....Ne */
+ 0xb6, 0x35, 0x4a, 0x64, 0x15, 0xf8, 0xaf, 0x71, 0xb2, 0x42, 0x50, 0x89, 0x02, 0x03, 0x01, 0x00, /* 0x00000100: .5Jd...q.BP..... */
+ 0x01, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd0, 0x5e, 0x09, 0x3a, 0xc5, 0xdc, 0xcf, 0x2c, 0xec, 0x74, /* 0x00000110: .......^.:...,.t */
+ 0x11, 0x81, 0x8d, 0x1d, 0x8f, 0x2a, 0xfa, 0x31, 0x4d, 0xe0, 0x90, 0x1a, 0xd8, 0xf5, 0x95, 0xc7, /* 0x00000120: .....*.1M....... */
+ 0x70, 0x5c, 0x62, 0x42, 0xac, 0xe9, 0xd9, 0xf2, 0x14, 0xf1, 0xd0, 0x25, 0xbb, 0xeb, 0x06, 0xfe, /* 0x00000130: p\bB.......%.... */
+ 0x09, 0xd6, 0x75, 0x67, 0xd7, 0x39, 0xc1, 0xa0, 0x67, 0x34, 0x4d, 0xd2, 0x12, 0x97, 0xaa, 0x5d, /* 0x00000140: ..ug.9..g4M....] */
+ 0xeb, 0x0e, 0xb0, 0x16, 0x6c, 0x78, 0x8e, 0xa0, 0x75, 0xa3, 0xaa, 0x57, 0x88, 0x3b, 0x43, 0x4f, /* 0x00000150: ....lx..u..W.;CO */
+ 0x75, 0x85, 0x67, 0xb0, 0x9b, 0xdd, 0x49, 0x0e, 0x6e, 0xdb, 0xea, 0xb3, 0xd4, 0x88, 0x54, 0xa0, /* 0x00000160: u.g...I.n.....T. */
+ 0x46, 0x0d, 0x55, 0x6d, 0x98, 0xbd, 0x20, 0xf9, 0x9f, 0x61, 0x2d, 0x6f, 0xc7, 0xd7, 0x16, 0x66, /* 0x00000170: F.Um.. ..a-o...f */
+ 0x72, 0xc7, 0x73, 0xbe, 0x9e, 0x48, 0xdc, 0x65, 0x12, 0x46, 0x35, 0x69, 0x55, 0xd8, 0x6b, 0x81, /* 0x00000180: r.s..H.e.F5iU.k. */
+ 0x78, 0x40, 0x15, 0x93, 0x60, 0x31, 0x4e, 0x87, 0x15, 0x2a, 0x74, 0x74, 0x7b, 0xa0, 0x1f, 0x59, /* 0x00000190: x@..`1N..*tt{..Y */
+ 0x8d, 0xc8, 0x3f, 0xdd, 0xf0, 0x13, 0x88, 0x2a, 0x4a, 0xf2, 0xf5, 0xf1, 0x9e, 0xf3, 0x2d, 0x9c, /* 0x000001a0: ..?....*J.....-. */
+ 0x8e, 0xbc, 0xb1, 0x21, 0x45, 0xc7, 0x44, 0x0c, 0x6a, 0xfe, 0x4c, 0x20, 0xdc, 0x73, 0xda, 0x62, /* 0x000001b0: ...!E.D.j.L .s.b */
+ 0x21, 0xcb, 0xdf, 0x06, 0xfc, 0x90, 0xc2, 0xbd, 0xd6, 0xde, 0xfb, 0xf6, 0x08, 0x69, 0x5d, 0xea, /* 0x000001c0: !............i]. */
+ 0xb3, 0x7f, 0x93, 0x61, 0xf2, 0xc1, 0xd0, 0x61, 0x4f, 0xd5, 0x5b, 0x63, 0xba, 0xb0, 0x3b, 0x07, /* 0x000001d0: ...a...aO.[c..;. */
+ 0x7a, 0x55, 0xcd, 0xa1, 0xae, 0x8a, 0x92, 0x21, 0xcc, 0x2f, 0x5b, 0xf8, 0x40, 0x6a, 0xcd, 0xd5, /* 0x000001e0: zU.....!..[.@j.. */
+ 0x5f, 0x15, 0xf4, 0xb6, 0xbd, 0xe5, 0x91, 0xb9, 0xa8, 0xcc, 0x2a, 0xa8, 0xa6, 0x67, 0x57, 0x2b, /* 0x000001f0: _.........*..gW+ */
+ 0x4b, 0xe9, 0x88, 0xe0, 0xbb, 0x58, 0xac, 0x69, 0x5f, 0x3c, 0x76, 0x28, 0xa6, 0x9d, 0xbc, 0x71, /* 0x00000200: K....X.i_<v(...q */
+ 0x7f, 0xcb, 0x0c, 0xc0, 0xbd, 0x61, 0x02, 0x81, 0x81, 0x00, 0xfc, 0x62, 0x79, 0x5b, 0xac, 0xf6, /* 0x00000210: .....a.....by[.. */
+ 0x9b, 0x8c, 0xaa, 0x76, 0x2a, 0x30, 0x0e, 0xcf, 0x6b, 0x88, 0x72, 0x54, 0x8c, 0xdf, 0xf3, 0x9d, /* 0x00000220: ...v*0..k.rT.... */
+ 0x84, 0xbb, 0xe7, 0x9d, 0xd4, 0x04, 0x29, 0x3c, 0xb5, 0x9d, 0x60, 0x9a, 0xcc, 0x12, 0xf3, 0xfa, /* 0x00000230: ......)<..`..... */
+ 0x64, 0x30, 0x23, 0x47, 0xc6, 0xa4, 0x8b, 0x6c, 0x73, 0x6c, 0x6b, 0x78, 0x82, 0xec, 0x05, 0x19, /* 0x00000240: d0#G...lslkx.... */
+ 0xde, 0xdd, 0xde, 0x52, 0xc5, 0x20, 0xd1, 0x11, 0x58, 0x19, 0x07, 0x5a, 0x90, 0xdd, 0x22, 0x91, /* 0x00000250: ...R. ..X..Z..". */
+ 0x89, 0x22, 0x3f, 0x12, 0x54, 0x1a, 0xb8, 0x79, 0xd8, 0x6c, 0xbc, 0xf5, 0x0d, 0xc7, 0x73, 0x5c, /* 0x00000260: ."?.T..y.l....s\ */
+ 0xed, 0xba, 0x40, 0x2b, 0x72, 0x34, 0x34, 0x97, 0xfa, 0x49, 0xf6, 0x43, 0x7c, 0xbc, 0x61, 0x30, /* 0x00000270: ..@+r44..I.C|.a0 */
+ 0x54, 0x22, 0x21, 0x5f, 0x77, 0x68, 0x6b, 0x83, 0x95, 0xc6, 0x8d, 0xb8, 0x25, 0x3a, 0xd3, 0xb2, /* 0x00000280: T"!_whk.....%:.. */
+ 0xbe, 0x29, 0x94, 0x01, 0x15, 0xf0, 0x36, 0x9d, 0x3e, 0xff, 0x02, 0x81, 0x81, 0x00, 0xde, 0x3b, /* 0x00000290: .)....6.>......; */
+ 0xd6, 0x4b, 0x38, 0x69, 0x9b, 0x71, 0x29, 0x89, 0xd4, 0x6d, 0x8c, 0x41, 0xee, 0xe2, 0x4d, 0xfc, /* 0x000002a0: .K8i.q)..m.A..M. */
+ 0xf0, 0x9a, 0x73, 0xf1, 0x15, 0x94, 0xac, 0x1b, 0x68, 0x5f, 0x79, 0x15, 0x3a, 0x41, 0x55, 0x09, /* 0x000002b0: ..s.....h_y.:AU. */
+ 0xc7, 0x1e, 0xec, 0x27, 0x67, 0xe2, 0xdc, 0x54, 0xa8, 0x09, 0xe6, 0x46, 0x92, 0x92, 0x03, 0x8d, /* 0x000002c0: ...'g..T...F.... */
+ 0xe5, 0x96, 0xfb, 0x1a, 0xdd, 0x59, 0x6f, 0x92, 0xf1, 0xf6, 0x8f, 0x76, 0xb0, 0xc5, 0xe6, 0xd7, /* 0x000002d0: .....Yo....v.... */
+ 0x1b, 0x25, 0xaf, 0x04, 0x9f, 0xd8, 0x71, 0x27, 0x97, 0x99, 0x23, 0x09, 0x7d, 0xef, 0x06, 0x13, /* 0x000002e0: .%....q'..#.}... */
+ 0xab, 0xdc, 0xa2, 0xd8, 0x5f, 0xc5, 0xec, 0xf3, 0x62, 0x20, 0x72, 0x7b, 0xa8, 0xc7, 0x09, 0x24, /* 0x000002f0: ...._...b r{...$ */
+ 0xaf, 0x72, 0xc9, 0xea, 0xb8, 0x2d, 0xda, 0x00, 0xc8, 0xfe, 0xb4, 0x9f, 0x9f, 0xc7, 0xa9, 0xf7, /* 0x00000300: .r...-.......... */
+ 0x1d, 0xce, 0xb1, 0xdb, 0xc5, 0x8a, 0x4e, 0xe8, 0x88, 0x77, 0x68, 0xdd, 0xf8, 0x77, 0x02, 0x81, /* 0x00000310: ......N..wh..w.. */
+ 0x80, 0x5b, 0xa5, 0x8e, 0x98, 0x01, 0xa8, 0xd3, 0x37, 0x33, 0x37, 0x11, 0x7e, 0xbe, 0x02, 0x07, /* 0x00000320: .[......737.~... */
+ 0xf4, 0x56, 0x3f, 0xe9, 0x9f, 0xf1, 0x20, 0xc3, 0xf0, 0x4f, 0xdc, 0xf9, 0xfe, 0x40, 0xd3, 0x30, /* 0x00000330: .V?... ..O...@.0 */
+ 0xc7, 0xe3, 0x2a, 0x92, 0xec, 0x56, 0xf8, 0x17, 0xa5, 0x7b, 0x4a, 0x37, 0x11, 0xcd, 0x27, 0x26, /* 0x00000340: ..*..V...{J7..'& */
+ 0x8a, 0xba, 0x43, 0xda, 0x96, 0xc6, 0x0b, 0x6c, 0xe8, 0x78, 0x30, 0xea, 0x30, 0x4e, 0x7a, 0xd3, /* 0x00000350: ..C....l.x0.0Nz. */
+ 0xd8, 0xd2, 0xd8, 0xca, 0x3d, 0xe2, 0xad, 0xa2, 0x74, 0x73, 0x1e, 0xbe, 0xb7, 0xad, 0x41, 0x61, /* 0x00000360: ....=...ts....Aa */
+ 0x9b, 0xaa, 0xc9, 0xf9, 0xa4, 0xf1, 0x79, 0x4f, 0x42, 0x10, 0xc7, 0x36, 0x03, 0x4b, 0x0d, 0xdc, /* 0x00000370: ......yOB..6.K.. */
+ 0xef, 0x3a, 0xa3, 0xab, 0x09, 0xe4, 0xe8, 0xdd, 0xc4, 0x3f, 0x06, 0x21, 0xa0, 0x23, 0x5a, 0x76, /* 0x00000380: .:.......?.!.#Zv */
+ 0xea, 0xd0, 0xcf, 0x8b, 0x85, 0x5f, 0x16, 0x4b, 0x03, 0x62, 0x21, 0x3a, 0xcc, 0x2d, 0xa8, 0xd0, /* 0x00000390: ....._.K.b!:.-.. */
+ 0x15, 0x02, 0x81, 0x80, 0x51, 0xf6, 0x89, 0xbb, 0xa6, 0x6b, 0xb4, 0xcb, 0xd0, 0xc1, 0x27, 0xda, /* 0x000003a0: ....Q....k....'. */
+ 0xdb, 0x6e, 0xf9, 0xd6, 0xf7, 0x62, 0x81, 0xae, 0xc5, 0x72, 0x36, 0x3e, 0x66, 0x17, 0x99, 0xb0, /* 0x000003b0: .n...b...r6>f... */
+ 0x14, 0xad, 0x52, 0x96, 0x03, 0xf2, 0x1e, 0x41, 0x76, 0x61, 0xb6, 0x3c, 0x02, 0x7d, 0x2a, 0x98, /* 0x000003c0: ..R....Ava.<.}*. */
+ 0xb4, 0x18, 0x75, 0x38, 0x6b, 0x1d, 0x2b, 0x7f, 0x3a, 0xcf, 0x96, 0xb1, 0xc4, 0xa7, 0xd2, 0x9b, /* 0x000003d0: ..u8k.+.:....... */
+ 0xd8, 0x1f, 0xb3, 0x64, 0xda, 0x15, 0x9d, 0xca, 0x91, 0x39, 0x48, 0x67, 0x00, 0x9c, 0xd4, 0x99, /* 0x000003e0: ...d.....9Hg.... */
+ 0xc3, 0x45, 0x5d, 0xf0, 0x09, 0x32, 0xba, 0x21, 0x1e, 0xe2, 0x64, 0xb8, 0x50, 0x03, 0x17, 0xbe, /* 0x000003f0: .E]..2.!..d.P... */
+ 0xd5, 0xda, 0x6b, 0xce, 0x34, 0xbe, 0x16, 0x03, 0x65, 0x1b, 0x2f, 0xa0, 0xa1, 0x95, 0xc6, 0x8b, /* 0x00000400: ..k.4...e....... */
+ 0xc2, 0x3c, 0x59, 0x26, 0xbf, 0xb6, 0x07, 0x85, 0x53, 0x2d, 0xb6, 0x36, 0xa3, 0x91, 0xb9, 0xbb, /* 0x00000410: .<Y&....S-.6.... */
+ 0x28, 0xaf, 0x2d, 0x53, 0x02, 0x81, 0x81, 0x00, 0xd7, 0xbc, 0x70, 0xd8, 0x18, 0x4f, 0x65, 0x8c, /* 0x00000420: (.-S......p..Oe. */
+ 0x68, 0xca, 0x35, 0x77, 0x43, 0x50, 0x9b, 0xa1, 0xa3, 0x9a, 0x0e, 0x2d, 0x7b, 0x38, 0xf8, 0xba, /* 0x00000430: h.5wCP.....-{8.. */
+ 0x14, 0x91, 0x3b, 0xc3, 0x3b, 0x1b, 0xa0, 0x6d, 0x45, 0xe4, 0xa8, 0x28, 0x97, 0xf6, 0x89, 0x13, /* 0x00000440: ..;.;..mE..(.... */
+ 0xb6, 0x16, 0x6d, 0x65, 0x47, 0x8c, 0xa6, 0x21, 0xf8, 0x6a, 0xce, 0x4e, 0x44, 0x5e, 0x81, 0x47, /* 0x00000450: ..meG..!.j.ND^.G */
+ 0xd9, 0xad, 0x8a, 0xb9, 0xd9, 0xe9, 0x3e, 0x33, 0x1e, 0x5f, 0xe9, 0xe9, 0xa7, 0xea, 0x60, 0x75, /* 0x00000460: ......>3._....`u */
+ 0x02, 0x57, 0x71, 0xb5, 0xed, 0x47, 0x77, 0xda, 0x1a, 0x40, 0x38, 0xab, 0x82, 0xd2, 0x0d, 0xf5, /* 0x00000470: .Wq..Gw..@8..... */
+ 0x0e, 0x8e, 0xa9, 0x24, 0xdc, 0x30, 0xc9, 0x98, 0xa2, 0x05, 0xcd, 0xca, 0x01, 0xcf, 0xae, 0x1d, /* 0x00000480: ...$.0.......... */
+ 0xe9, 0x02, 0x47, 0x0e, 0x46, 0x1d, 0x52, 0x02, 0x9a, 0x99, 0x22, 0x23, 0x7f, 0xf8, 0x9e, 0xc2, /* 0x00000490: ..G.F.R..."#.... */
+ 0x16, 0x86, 0xca, 0xa0, 0xa7, 0x34, 0xfb, 0xbc, /* 0x000004a0: .....4.. */
+};
+
+#endif /* RT_OS_WINDOWS */
+
+
+/**
+ * Certificate w/ public key + private key pair for signing.
+ */
+class SignToolKeyPair
+{
+protected:
+ /* Context: */
+ const char *m_pszWhat;
+ bool m_fMandatory;
+
+ /* Parameters kept till finalizing parsing: */
+ const char *m_pszCertFile;
+ const char *m_pszCertSha1;
+ uint8_t m_abCertSha1[RTSHA1_HASH_SIZE];
+ const char *m_pszCertSubject;
+ const char *m_pszCertStore;
+ bool m_fMachineStore; /**< false = personal store */
+
+ const char *m_pszKeyFile;
+ const char *m_pszKeyPassword;
+ const char *m_pszKeyName;
+ const char *m_pszKeyProvider;
+
+ /** String buffer for m_pszKeyPassword when read from file. */
+ RTCString m_strPassword;
+ /** Storage for pCertificate when it's loaded from a file. */
+ RTCRX509CERTIFICATE m_DecodedCert;
+#ifdef RT_OS_WINDOWS
+ /** For the fake certificate */
+ RTCRX509CERTIFICATE m_DecodedFakeCert;
+ /** The certificate store. */
+ HCERTSTORE m_hStore;
+ /** The windows certificate context. */
+ PCCERT_CONTEXT m_pCertCtx;
+ /** Whether hNCryptPrivateKey/hLegacyPrivateKey needs freeing or not. */
+ BOOL m_fFreePrivateHandle;
+#endif
+
+ /** Set if already finalized. */
+ bool m_fFinalized;
+
+ /** Store containing the intermediate certificates available to the host.
+ * */
+ static RTCRSTORE s_hStoreIntermediate;
+ /** Instance counter for helping cleaning up m_hStoreIntermediate. */
+ static uint32_t s_cInstances;
+
+public: /* used to be a struct, thus not prefix either. */
+ /* Result: */
+ PCRTCRX509CERTIFICATE pCertificate;
+ RTCRKEY hPrivateKey;
+#ifdef RT_OS_WINDOWS
+ PCRTCRX509CERTIFICATE pCertificateReal;
+ NCRYPT_KEY_HANDLE hNCryptPrivateKey;
+ HCRYPTPROV hLegacyPrivateKey;
+#endif
+
+public:
+ SignToolKeyPair(const char *a_pszWhat, bool a_fMandatory = false)
+ : m_pszWhat(a_pszWhat)
+ , m_fMandatory(a_fMandatory)
+ , m_pszCertFile(NULL)
+ , m_pszCertSha1(NULL)
+ , m_pszCertSubject(NULL)
+ , m_pszCertStore("MY")
+ , m_fMachineStore(false)
+ , m_pszKeyFile(NULL)
+ , m_pszKeyPassword(NULL)
+ , m_pszKeyName(NULL)
+ , m_pszKeyProvider(NULL)
+#ifdef RT_OS_WINDOWS
+ , m_hStore(NULL)
+ , m_pCertCtx(NULL)
+ , m_fFreePrivateHandle(FALSE)
+#endif
+ , m_fFinalized(false)
+ , pCertificate(NULL)
+ , hPrivateKey(NIL_RTCRKEY)
+#ifdef RT_OS_WINDOWS
+ , pCertificateReal(NULL)
+ , hNCryptPrivateKey(0)
+ , hLegacyPrivateKey(0)
+#endif
+ {
+ RT_ZERO(m_DecodedCert);
+#ifdef RT_OS_WINDOWS
+ RT_ZERO(m_DecodedFakeCert);
+#endif
+ s_cInstances++;
+ }
+
+ virtual ~SignToolKeyPair()
+ {
+ if (hPrivateKey != NIL_RTCRKEY)
+ {
+ RTCrKeyRelease(hPrivateKey);
+ hPrivateKey = NIL_RTCRKEY;
+ }
+ if (pCertificate == &m_DecodedCert)
+ {
+ RTCrX509Certificate_Delete(&m_DecodedCert);
+ pCertificate = NULL;
+ }
+#ifdef RT_OS_WINDOWS
+ if (pCertificate == &m_DecodedFakeCert)
+ {
+ RTCrX509Certificate_Delete(&m_DecodedFakeCert);
+ RTCrX509Certificate_Delete(&m_DecodedCert);
+ pCertificate = NULL;
+ pCertificateReal = NULL;
+ }
+#endif
+#ifdef RT_OS_WINDOWS
+ if (m_pCertCtx != NULL)
+ {
+ CertFreeCertificateContext(m_pCertCtx);
+ m_pCertCtx = NULL;
+ }
+ if (m_hStore != NULL)
+ {
+ CertCloseStore(m_hStore, 0);
+ m_hStore = NULL;
+ }
+#endif
+ s_cInstances--;
+ if (s_cInstances == 0)
+ {
+ RTCrStoreRelease(s_hStoreIntermediate);
+ s_hStoreIntermediate = NIL_RTCRSTORE;
+ }
+ }
+
+ bool isComplete(void) const
+ {
+ return pCertificate && hPrivateKey != NIL_RTCRKEY;
+ }
+
+ bool isNull(void) const
+ {
+ return pCertificate == NULL && hPrivateKey == NIL_RTCRKEY;
+ }
+
+ RTEXITCODE handleOption(unsigned offOpt, PRTGETOPTUNION pValueUnion)
+ {
+ AssertReturn(!m_fFinalized, RTMsgErrorExitFailure("Cannot handle options after finalizeOptions was called!"));
+ switch (offOpt)
+ {
+ case OPT_OFF_CERT_FILE:
+ m_pszCertFile = pValueUnion->psz;
+ m_pszCertSha1 = NULL;
+ m_pszCertSubject = NULL;
+ break;
+ case OPT_OFF_CERT_SHA1:
+ {
+ /* Crude normalization of input separators to colons, since it's likely
+ to use spaces and our conversion function only does colons or nothing. */
+ char szDigest[RTSHA1_DIGEST_LEN * 3 + 1];
+ int rc = RTStrCopy(szDigest, sizeof(szDigest), pValueUnion->psz);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszDigest = RTStrStrip(szDigest);
+ size_t offDst = 0;
+ size_t offSrc = 0;
+ char ch;
+ while ((ch = pszDigest[offSrc++]) != '\0')
+ {
+ if (ch == ' ' || ch == '\t' || ch == ':')
+ {
+ while ((ch = pszDigest[offSrc]) == ' ' || ch == '\t' || ch == ':')
+ offSrc++;
+ ch = ch ? ':' : '\0';
+ }
+ pszDigest[offDst++] = ch;
+ }
+ pszDigest[offDst] = '\0';
+
+ /** @todo add a more relaxed input mode to RTStrConvertHexBytes that can deal
+ * with spaces as well as multi-byte cluster of inputs. */
+ rc = RTStrConvertHexBytes(pszDigest, m_abCertSha1, RTSHA1_HASH_SIZE, RTSTRCONVERTHEXBYTES_F_SEP_COLON);
+ if (RT_SUCCESS(rc))
+ {
+ m_pszCertFile = NULL;
+ m_pszCertSha1 = pValueUnion->psz;
+ m_pszCertSubject = NULL;
+ break;
+ }
+ }
+ return RTMsgErrorExitFailure("malformed SHA-1 certificate fingerprint (%Rrc): %s", rc, pValueUnion->psz);
+ }
+ case OPT_OFF_CERT_SUBJECT:
+ m_pszCertFile = NULL;
+ m_pszCertSha1 = NULL;
+ m_pszCertSubject = pValueUnion->psz;
+ break;
+ case OPT_OFF_CERT_STORE:
+ m_pszCertStore = pValueUnion->psz;
+ break;
+ case OPT_OFF_CERT_STORE_MACHINE:
+ m_fMachineStore = true;
+ break;
+
+ case OPT_OFF_KEY_FILE:
+ m_pszKeyFile = pValueUnion->psz;
+ m_pszKeyName = NULL;
+ break;
+ case OPT_OFF_KEY_NAME:
+ m_pszKeyFile = NULL;
+ m_pszKeyName = pValueUnion->psz;
+ break;
+ case OPT_OFF_KEY_PROVIDER:
+ m_pszKeyProvider = pValueUnion->psz;
+ break;
+ case OPT_OFF_KEY_PASSWORD:
+ m_pszKeyPassword = pValueUnion->psz;
+ break;
+ case OPT_OFF_KEY_PASSWORD_FILE:
+ {
+ m_pszKeyPassword = NULL;
+
+ size_t const cchMax = 512;
+ int rc = m_strPassword.reserveNoThrow(cchMax + 1);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("out of memory");
+
+ PRTSTREAM pStrm = g_pStdIn;
+ bool const fClose = strcmp(pValueUnion->psz, "stdin") != 0;
+ if (fClose)
+ {
+ rc = RTStrmOpen(pValueUnion->psz, "r", &pStrm);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to open password file '%s' for reading: %Rrc", pValueUnion->psz, rc);
+ }
+ rc = RTStrmGetLine(pStrm, m_strPassword.mutableRaw(), cchMax);
+ if (fClose)
+ RTStrmClose(pStrm);
+ if (rc == VERR_BUFFER_OVERFLOW || rc == VINF_BUFFER_OVERFLOW)
+ return RTMsgErrorExitFailure("Password from '%s' is too long (max %zu)", pValueUnion->psz, cchMax);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Error reading password from '%s': %Rrc", pValueUnion->psz, rc);
+
+ m_strPassword.jolt();
+ m_strPassword.stripRight();
+ m_pszKeyPassword = m_strPassword.c_str();
+ break;
+ }
+ default:
+ AssertFailedReturn(RTMsgErrorExitFailure("Invalid offOpt=%u!\n", offOpt));
+ }
+ return RTEXITCODE_SUCCESS;
+ }
+
+ RTEXITCODE finalizeOptions(unsigned cVerbosity)
+ {
+ RT_NOREF(cVerbosity);
+
+ /* Only do this once. */
+ if (m_fFinalized)
+ return RTEXITCODE_SUCCESS;
+ m_fFinalized = true;
+
+ /*
+ * Got a cert? Is it required?
+ */
+ bool const fHasKey = ( m_pszKeyFile != NULL
+ || m_pszKeyName != NULL);
+ bool const fHasCert = ( m_pszCertFile != NULL
+ || m_pszCertSha1 != NULL
+ || m_pszCertSubject != NULL);
+ if (!fHasCert)
+ {
+ if (m_fMandatory)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Specifying a %s certificiate is required.", m_pszWhat);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ /*
+ * Get the certificate.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ /* From file: */
+ if (m_pszCertFile)
+ {
+ int rc = RTCrX509Certificate_ReadFromFile(&m_DecodedCert, m_pszCertFile, 0, &g_RTAsn1DefaultAllocator,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Error reading %s certificate from '%s': %Rrc%#RTeim",
+ m_pszWhat, m_pszCertFile, rc, &ErrInfo.Core);
+ pCertificate = &m_DecodedCert;
+ }
+ /* From certificate store by name (substring) or fingerprint: */
+ else
+ {
+#ifdef RT_OS_WINDOWS
+ m_hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM_A, X509_ASN_ENCODING, NULL,
+ CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG | CERT_STORE_READONLY_FLAG
+ | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_ENUM_ARCHIVED_FLAG
+ | (m_fMachineStore ? CERT_SYSTEM_STORE_LOCAL_MACHINE : CERT_SYSTEM_STORE_CURRENT_USER),
+ m_pszCertStore);
+ if (m_hStore == NULL)
+ return RTMsgErrorExitFailure("Failed to open %s store '%s': %Rwc (%u)", m_fMachineStore ? "machine" : "user",
+ m_pszCertStore, GetLastError(), GetLastError());
+
+ CRYPT_HASH_BLOB Thumbprint = { RTSHA1_HASH_SIZE, m_abCertSha1 };
+ PRTUTF16 pwszSubject = NULL;
+ void const *pvFindParam = &Thumbprint;
+ DWORD fFind = CERT_FIND_SHA1_HASH;
+ if (!m_pszCertSha1)
+ {
+ int rc = RTStrToUtf16(m_pszCertSubject, &pwszSubject);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTStrToUtf16 failed: %Rrc, input %.*Rhxs",
+ rc, strlen(m_pszCertSubject), m_pszCertSubject);
+ pvFindParam = pwszSubject;
+ fFind = CERT_FIND_SUBJECT_STR;
+ }
+
+ while ((m_pCertCtx = CertFindCertificateInStore(m_hStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0 /*fFlags*/,
+ fFind, pvFindParam, m_pCertCtx)) != NULL)
+ {
+ if (m_pCertCtx->dwCertEncodingType & X509_ASN_ENCODING)
+ {
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ RTAsn1CursorInitPrimary(&PrimaryCursor, m_pCertCtx->pbCertEncoded, m_pCertCtx->cbCertEncoded,
+ RTErrInfoInitStatic(&ErrInfo),
+ &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, "CurCtx");
+ int rc = RTCrX509Certificate_DecodeAsn1(&PrimaryCursor.Cursor, 0, &m_DecodedCert, "Cert");
+ if (RT_SUCCESS(rc))
+ {
+ pCertificate = &m_DecodedCert;
+ break;
+ }
+ RTMsgError("failed to decode certificate %p: %Rrc%#RTeim", m_pCertCtx, rc, &ErrInfo.Core);
+ }
+ }
+
+ RTUtf16Free(pwszSubject);
+ if (!m_pCertCtx)
+ return RTMsgErrorExitFailure("No certificate found matching %s '%s' (%Rwc / %u)",
+ m_pszCertSha1 ? "thumbprint" : "subject substring",
+ m_pszCertSha1 ? m_pszCertSha1 : m_pszCertSubject, GetLastError(), GetLastError());
+
+ /* Use this for private key too? */
+ if (!fHasKey)
+ {
+ HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hTmpPrivateKey = 0;
+ DWORD dwKeySpec = 0;
+ if (CryptAcquireCertificatePrivateKey(m_pCertCtx,
+ CRYPT_ACQUIRE_SILENT_FLAG | CRYPT_ACQUIRE_COMPARE_KEY_FLAG
+ | CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG
+ | CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG,
+ NULL, &hTmpPrivateKey, &dwKeySpec, &m_fFreePrivateHandle))
+ {
+ if (cVerbosity > 1)
+ RTMsgInfo("hTmpPrivateKey=%p m_fFreePrivateHandle=%d dwKeySpec=%#x",
+ hTmpPrivateKey, m_fFreePrivateHandle, dwKeySpec);
+ Assert(dwKeySpec == CERT_NCRYPT_KEY_SPEC);
+ if (dwKeySpec == CERT_NCRYPT_KEY_SPEC)
+ hNCryptPrivateKey = hTmpPrivateKey;
+ else
+ hLegacyPrivateKey = hTmpPrivateKey; /** @todo remove or drop CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG */
+ return loadFakePrivateKeyAndCert();
+ }
+ return RTMsgErrorExitFailure("CryptAcquireCertificatePrivateKey failed: %Rwc (%d)", GetLastError(), GetLastError());
+ }
+#else
+ return RTMsgErrorExitFailure("Certificate store support is missing on this host");
+#endif
+ }
+
+ /*
+ * Get hold of the private key (if someone above already did, they'd returned already).
+ */
+ Assert(hPrivateKey == NIL_RTCRKEY);
+ /* Use cert file if nothing else specified. */
+ if (!fHasKey && m_pszCertFile)
+ m_pszKeyFile = m_pszCertFile;
+
+ /* Load from file:*/
+ if (m_pszKeyFile)
+ {
+ int rc = RTCrKeyCreateFromFile(&hPrivateKey, 0 /*fFlags*/, m_pszKeyFile, m_pszKeyPassword,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Error reading the %s private key from '%s': %Rrc%#RTeim",
+ m_pszWhat, m_pszKeyFile, rc, &ErrInfo.Core);
+ }
+ /* From key store: */
+ else
+ {
+ return RTMsgErrorExitFailure("Key store support is missing on this host");
+ }
+
+ return RTEXITCODE_SUCCESS;
+ }
+
+ /** Returns the real certificate. */
+ PCRTCRX509CERTIFICATE getRealCertificate() const
+ {
+#ifdef RT_OS_WINDOWS
+ if (pCertificateReal)
+ return pCertificateReal;
+#endif
+ return pCertificate;
+ }
+
+#ifdef RT_OS_WINDOWS
+ RTEXITCODE loadFakePrivateKeyAndCert()
+ {
+ int rc = RTCrX509Certificate_ReadFromBuffer(&m_DecodedFakeCert, g_abFakeCertificate, sizeof(g_abFakeCertificate),
+ 0 /*fFlags*/, &g_RTAsn1DefaultAllocator, NULL, NULL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrX509Certificate_ReadFromBuffer/g_abFakeCertificate failed: %Rrc", rc);
+ pCertificateReal = pCertificate;
+ pCertificate = &m_DecodedFakeCert;
+
+ rc = RTCrKeyCreateFromBuffer(&hPrivateKey, 0 /*fFlags*/, g_abFakeRsaKey, sizeof(g_abFakeRsaKey), NULL, NULL, NULL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrKeyCreateFromBuffer/g_abFakeRsaKey failed: %Rrc", rc);
+ return RTEXITCODE_SUCCESS;
+ }
+
+#endif
+
+ /**
+ * Search for intermediate CA.
+ *
+ * Currently this only do a single certificate path, so this may go south if
+ * there are multiple paths available. It may work fine for a cross signing
+ * path, as long as the cross over is at the level immediately below the root.
+ */
+ PCRTCRCERTCTX findNextIntermediateCert(PCRTCRCERTCTX pPrev)
+ {
+ /*
+ * Make sure the store is loaded before we start.
+ */
+ if (s_hStoreIntermediate == NIL_RTCRSTORE)
+ {
+ Assert(!pPrev);
+ RTERRINFOSTATIC ErrInfo;
+ int rc = RTCrStoreCreateSnapshotById(&s_hStoreIntermediate,
+ !m_fMachineStore
+ ? RTCRSTOREID_USER_INTERMEDIATE_CAS : RTCRSTOREID_SYSTEM_INTERMEDIATE_CAS,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("RTCrStoreCreateSnapshotById/%s-intermediate-CAs failed: %Rrc%#RTeim",
+ m_fMachineStore ? "user" : "machine", rc, &ErrInfo.Core);
+ return NULL;
+ }
+ }
+
+ /*
+ * Open the search handle for the parent of the previous/end certificate.
+ *
+ * We don't need to consider RTCRCERTCTX::pTaInfo here as we're not
+ * after trust anchors, only intermediate certificates.
+ */
+#ifdef RT_OS_WINDOWS
+ PCRTCRX509CERTIFICATE pChildCert = pPrev ? pPrev->pCert : pCertificateReal ? pCertificateReal : pCertificate;
+#else
+ PCRTCRX509CERTIFICATE pChildCert = pPrev ? pPrev->pCert : pCertificate;
+#endif
+ AssertReturnStmt(pChildCert, RTCrCertCtxRelease(pPrev), NULL);
+
+ RTCRSTORECERTSEARCH Search;
+ int rc = RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280(s_hStoreIntermediate, &pChildCert->TbsCertificate.Issuer,
+ &Search);
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280 failed: %Rrc", rc);
+ return NULL;
+ }
+
+ /*
+ * We only gave the subject so, we have to check the serial number our selves.
+ */
+ PCRTCRCERTCTX pCertCtx;
+ while ((pCertCtx = RTCrStoreCertSearchNext(s_hStoreIntermediate, &Search)) != NULL)
+ {
+ if ( pCertCtx->pCert
+ && RTAsn1BitString_Compare(&pCertCtx->pCert->TbsCertificate.T1.IssuerUniqueId,
+ &pChildCert->TbsCertificate.T1.IssuerUniqueId) == 0 /* compares presentness too */
+ && !RTCrX509Certificate_IsSelfSigned(pCertCtx->pCert))
+ {
+ break; /** @todo compare valid periode too and keep a best match when outside the desired period? */
+ }
+ RTCrCertCtxRelease(pCertCtx);
+ }
+
+ RTCrStoreCertSearchDestroy(s_hStoreIntermediate, & Search);
+ RTCrCertCtxRelease(pPrev);
+ return pCertCtx;
+ }
+
+ /**
+ * Merges the user specified certificates with the signing certificate and any
+ * intermediate CAs we can find in the system store.
+ *
+ * @returns Merged store, NIL_RTCRSTORE on failure (messaged).
+ * @param hUserSpecifiedCertificates The user certificate store.
+ */
+ RTCRSTORE assembleAllAdditionalCertificates(RTCRSTORE hUserSpecifiedCertificates)
+ {
+ RTCRSTORE hRetStore;
+ int rc = RTCrStoreCreateInMemEx(&hRetStore, 0, hUserSpecifiedCertificates);
+ if (RT_SUCCESS(rc))
+ {
+ /* Add the signing certificate: */
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTCrStoreCertAddX509(hRetStore, RTCRCERTCTX_F_ENC_X509_DER | RTCRCERTCTX_F_ADD_IF_NOT_FOUND,
+#ifdef RT_OS_WINDOWS
+ (PRTCRX509CERTIFICATE)(pCertificateReal ? pCertificateReal : pCertificate),
+#else
+ (PRTCRX509CERTIFICATE)pCertificate,
+#endif
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ /* Add all intermediate CAs certificates we can find. */
+ PCRTCRCERTCTX pInterCaCert = NULL;
+ while ((pInterCaCert = findNextIntermediateCert(pInterCaCert)) != NULL)
+ {
+ rc = RTCrStoreCertAddEncoded(hRetStore, RTCRCERTCTX_F_ENC_X509_DER | RTCRCERTCTX_F_ADD_IF_NOT_FOUND,
+ pInterCaCert->pabEncoded, pInterCaCert->cbEncoded,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("RTCrStoreCertAddEncoded/InterCA failed: %Rrc%#RTeim", rc, &ErrInfo.Core);
+ RTCrCertCtxRelease(pInterCaCert);
+ break;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ return hRetStore;
+ }
+ else
+ RTMsgError("RTCrStoreCertAddX509/signer failed: %Rrc%#RTeim", rc, &ErrInfo.Core);
+ RTCrStoreRelease(hRetStore);
+ }
+ else
+ RTMsgError("RTCrStoreCreateInMemEx failed: %Rrc", rc);
+ return NIL_RTCRSTORE;
+ }
+
+};
+
+/*static*/ RTCRSTORE SignToolKeyPair::s_hStoreIntermediate = NIL_RTCRSTORE;
+/*static*/ uint32_t SignToolKeyPair::s_cInstances = 0;
+
+
+/*********************************************************************************************************************************
+*
+*********************************************************************************************************************************/
+/** Timestamp type. */
+typedef enum
+{
+ /** Old timestamp style.
+ * This is just a counter signature with a trustworthy SigningTime attribute.
+ * Specificially it's the SignerInfo part of a detached PKCS#7 covering the
+ * SignerInfo.EncryptedDigest. */
+ kTimestampType_Old = 1,
+ /** This is a whole PKCS#7 signature of an TSTInfo from RFC-3161 (see page 7).
+ * Currently not supported. */
+ kTimestampType_New
+} TIMESTAMPTYPE;
+
+/**
+ * Timestamping options.
+ *
+ * Certificate w/ public key + private key pair for signing and signature type.
+ */
+class SignToolTimestampOpts : public SignToolKeyPair
+{
+public:
+ /** Type timestamp type. */
+ TIMESTAMPTYPE m_enmType;
+
+ SignToolTimestampOpts(const char *a_pszWhat, TIMESTAMPTYPE a_enmType = kTimestampType_Old)
+ : SignToolKeyPair(a_pszWhat)
+ , m_enmType(a_enmType)
+ {
+ }
+
+ bool isOldType() const { return m_enmType == kTimestampType_Old; }
+ bool isNewType() const { return m_enmType == kTimestampType_New; }
+};
+
+
+
+/*********************************************************************************************************************************
+* Crypto Store Auto Cleanup Wrapper. *
+*********************************************************************************************************************************/
+class CryptoStore
+{
+public:
+ RTCRSTORE m_hStore;
+
+ CryptoStore()
+ : m_hStore(NIL_RTCRSTORE)
+ {
+ }
+
+ ~CryptoStore()
+ {
+ if (m_hStore != NIL_RTCRSTORE)
+ {
+ uint32_t cRefs = RTCrStoreRelease(m_hStore);
+ Assert(cRefs == 0); RT_NOREF(cRefs);
+ m_hStore = NIL_RTCRSTORE;
+ }
+ }
+
+ /**
+ * Adds one or more certificates from the given file.
+ *
+ * @returns boolean success indicator.
+ */
+ bool addFromFile(const char *pszFilename, PRTERRINFOSTATIC pStaticErrInfo)
+ {
+ int rc = RTCrStoreCertAddFromFile(this->m_hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
+ pszFilename, RTErrInfoInitStatic(pStaticErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ if (RTErrInfoIsSet(&pStaticErrInfo->Core))
+ RTMsgWarning("Warnings loading certificate '%s': %s", pszFilename, pStaticErrInfo->Core.pszMsg);
+ return true;
+ }
+ RTMsgError("Error loading certificate '%s': %Rrc%#RTeim", pszFilename, rc, &pStaticErrInfo->Core);
+ return false;
+ }
+
+ /**
+ * Adds trusted self-signed certificates from the system.
+ *
+ * @returns boolean success indicator.
+ * @note The selection is self-signed rather than CAs here so that test signing
+ * certificates will be included.
+ */
+ bool addSelfSignedRootsFromSystem(PRTERRINFOSTATIC pStaticErrInfo)
+ {
+ CryptoStore Tmp;
+ int rc = RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts(&Tmp.m_hStore, RTErrInfoInitStatic(pStaticErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ RTCRSTORECERTSEARCH Search;
+ rc = RTCrStoreCertFindAll(Tmp.m_hStore, &Search);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTCRCERTCTX pCertCtx;
+ while ((pCertCtx = RTCrStoreCertSearchNext(Tmp.m_hStore, &Search)) != NULL)
+ {
+ /* Add it if it's a full fledged self-signed certificate, otherwise just skip: */
+ if ( pCertCtx->pCert
+ && RTCrX509Certificate_IsSelfSigned(pCertCtx->pCert))
+ {
+ int rc2 = RTCrStoreCertAddEncoded(this->m_hStore,
+ pCertCtx->fFlags | RTCRCERTCTX_F_ADD_IF_NOT_FOUND,
+ pCertCtx->pabEncoded, pCertCtx->cbEncoded, NULL);
+ if (RT_FAILURE(rc2))
+ RTMsgWarning("RTCrStoreCertAddEncoded failed for a certificate: %Rrc", rc2);
+ }
+ RTCrCertCtxRelease(pCertCtx);
+ }
+
+ int rc2 = RTCrStoreCertSearchDestroy(Tmp.m_hStore, &Search);
+ AssertRC(rc2);
+ return true;
+ }
+ RTMsgError("RTCrStoreCertFindAll failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts failed: %Rrc%#RTeim", rc, &pStaticErrInfo->Core);
+ return false;
+ }
+
+};
+
+
+
+/*********************************************************************************************************************************
+* Workers. *
+*********************************************************************************************************************************/
+
+
+/**
+ * Deletes the structure.
+ *
+ * @param pThis The structure to initialize.
+ */
+static void SignToolPkcs7_Delete(PSIGNTOOLPKCS7 pThis)
+{
+ RTCrPkcs7ContentInfo_Delete(&pThis->ContentInfo);
+ pThis->pSignedData = NULL;
+ RTMemFree(pThis->pbBuf);
+ pThis->pbBuf = NULL;
+ pThis->cbBuf = 0;
+ RTMemFree(pThis->pbNewBuf);
+ pThis->pbNewBuf = NULL;
+ pThis->cbNewBuf = 0;
+}
+
+
+/**
+ * Deletes the structure.
+ *
+ * @param pThis The structure to initialize.
+ */
+static void SignToolPkcs7Exe_Delete(PSIGNTOOLPKCS7EXE pThis)
+{
+ if (pThis->hLdrMod != NIL_RTLDRMOD)
+ {
+ int rc2 = RTLdrClose(pThis->hLdrMod);
+ if (RT_FAILURE(rc2))
+ RTMsgError("RTLdrClose failed: %Rrc\n", rc2);
+ pThis->hLdrMod = NIL_RTLDRMOD;
+ }
+ SignToolPkcs7_Delete(pThis);
+}
+
+
+/**
+ * Decodes the PKCS #7 blob pointed to by pThis->pbBuf.
+ *
+ * @returns IPRT status code (error message already shown on failure).
+ * @param pThis The PKCS\#7 signature to decode.
+ * @param fCatalog Set if catalog file, clear if executable.
+ */
+static int SignToolPkcs7_Decode(PSIGNTOOLPKCS7 pThis, bool fCatalog)
+{
+ RTERRINFOSTATIC ErrInfo;
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pThis->pbBuf, (uint32_t)pThis->cbBuf, RTErrInfoInitStatic(&ErrInfo),
+ &g_RTAsn1DefaultAllocator, 0, "WinCert");
+
+ int rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, &pThis->ContentInfo, "CI");
+ if (RT_SUCCESS(rc))
+ {
+ if (RTCrPkcs7ContentInfo_IsSignedData(&pThis->ContentInfo))
+ {
+ pThis->pSignedData = pThis->ContentInfo.u.pSignedData;
+
+ /*
+ * Decode the authenticode bits.
+ */
+ if (!strcmp(pThis->pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID))
+ {
+ PRTCRSPCINDIRECTDATACONTENT pIndData = pThis->pSignedData->ContentInfo.u.pIndirectDataContent;
+ Assert(pIndData);
+
+ /*
+ * Check that things add up.
+ */
+ rc = RTCrPkcs7SignedData_CheckSanity(pThis->pSignedData,
+ RTCRPKCS7SIGNEDDATA_SANITY_F_AUTHENTICODE
+ | RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH
+ | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT,
+ RTErrInfoInitStatic(&ErrInfo), "SD");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrSpcIndirectDataContent_CheckSanityEx(pIndData,
+ pThis->pSignedData,
+ RTCRSPCINDIRECTDATACONTENT_SANITY_F_ONLY_KNOWN_HASH,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ RTMsgError("SPC indirect data content sanity check failed for '%s': %Rrc - %s\n",
+ pThis->pszFilename, rc, ErrInfo.szMsg);
+ }
+ else
+ RTMsgError("PKCS#7 sanity check failed for '%s': %Rrc - %s\n", pThis->pszFilename, rc, ErrInfo.szMsg);
+ }
+ else if (!strcmp(pThis->pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID))
+ { /* apple code signing */ }
+ else if (!fCatalog)
+ RTMsgError("Unexpected the signed content in '%s': %s (expected %s)", pThis->pszFilename,
+ pThis->pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID);
+ }
+ else
+ rc = RTMsgErrorRc(VERR_CR_PKCS7_NOT_SIGNED_DATA,
+ "PKCS#7 content is inside '%s' is not 'signedData': %s\n",
+ pThis->pszFilename, pThis->ContentInfo.ContentType.szObjId);
+ }
+ else
+ RTMsgError("RTCrPkcs7ContentInfo_DecodeAsn1 failed on '%s': %Rrc - %s\n", pThis->pszFilename, rc, ErrInfo.szMsg);
+ return rc;
+}
+
+
+/**
+ * Reads and decodes PKCS\#7 signature from the given cat file.
+ *
+ * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error message
+ * on failure.
+ * @param pThis The structure to initialize.
+ * @param pszFilename The catalog (or any other DER PKCS\#7) filename.
+ * @param cVerbosity The verbosity.
+ */
+static RTEXITCODE SignToolPkcs7_InitFromFile(PSIGNTOOLPKCS7 pThis, const char *pszFilename, unsigned cVerbosity)
+{
+ /*
+ * Init the return structure.
+ */
+ RT_ZERO(*pThis);
+ pThis->pszFilename = pszFilename;
+ pThis->enmType = RTSIGNTOOLFILETYPE_CAT;
+
+ /*
+ * Lazy bird uses RTFileReadAll and duplicates the allocation.
+ */
+ void *pvFile;
+ int rc = RTFileReadAll(pszFilename, &pvFile, &pThis->cbBuf);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pbBuf = (uint8_t *)RTMemDup(pvFile, pThis->cbBuf);
+ RTFileReadAllFree(pvFile, pThis->cbBuf);
+ if (pThis->pbBuf)
+ {
+ if (cVerbosity > 2)
+ RTPrintf("PKCS#7 signature: %u bytes\n", pThis->cbBuf);
+
+ /*
+ * Decode it.
+ */
+ rc = SignToolPkcs7_Decode(pThis, true /*fCatalog*/);
+ if (RT_SUCCESS(rc))
+ return RTEXITCODE_SUCCESS;
+ }
+ else
+ RTMsgError("Out of memory!");
+ }
+ else
+ RTMsgError("Error reading '%s' into memory: %Rrc", pszFilename, rc);
+
+ SignToolPkcs7_Delete(pThis);
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Encodes the signature into the SIGNTOOLPKCS7::pbNewBuf and
+ * SIGNTOOLPKCS7::cbNewBuf members.
+ *
+ * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error message
+ * on failure.
+ * @param pThis The signature to encode.
+ * @param cVerbosity The verbosity.
+ */
+static RTEXITCODE SignToolPkcs7_Encode(PSIGNTOOLPKCS7 pThis, unsigned cVerbosity)
+{
+ RTERRINFOSTATIC StaticErrInfo;
+ PRTASN1CORE pRoot = RTCrPkcs7ContentInfo_GetAsn1Core(&pThis->ContentInfo);
+ uint32_t cbEncoded;
+ int rc = RTAsn1EncodePrepare(pRoot, RTASN1ENCODE_F_DER, &cbEncoded, RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ if (cVerbosity >= 4)
+ RTAsn1Dump(pRoot, 0, 0, RTStrmDumpPrintfV, g_pStdOut);
+
+ RTMemFree(pThis->pbNewBuf);
+ pThis->cbNewBuf = cbEncoded;
+ pThis->pbNewBuf = (uint8_t *)RTMemAllocZ(cbEncoded);
+ if (pThis->pbNewBuf)
+ {
+ rc = RTAsn1EncodeToBuffer(pRoot, RTASN1ENCODE_F_DER, pThis->pbNewBuf, pThis->cbNewBuf,
+ RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ if (cVerbosity > 1)
+ RTMsgInfo("Encoded signature to %u bytes", cbEncoded);
+ return RTEXITCODE_SUCCESS;
+ }
+ RTMsgError("RTAsn1EncodeToBuffer failed: %Rrc", rc);
+
+ RTMemFree(pThis->pbNewBuf);
+ pThis->pbNewBuf = NULL;
+ }
+ else
+ RTMsgError("Failed to allocate %u bytes!", cbEncoded);
+ }
+ else
+ RTMsgError("RTAsn1EncodePrepare failed: %Rrc - %s", rc, StaticErrInfo.szMsg);
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Helper that makes sure the UnauthenticatedAttributes are present in the given
+ * SignerInfo structure.
+ *
+ * Call this before trying to modify the array.
+ *
+ * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error already
+ * displayed on failure.
+ * @param pSignerInfo The SignerInfo structure in question.
+ */
+static RTEXITCODE SignToolPkcs7_EnsureUnauthenticatedAttributesPresent(PRTCRPKCS7SIGNERINFO pSignerInfo)
+{
+ if (pSignerInfo->UnauthenticatedAttributes.cItems == 0)
+ {
+ /* HACK ALERT! Invent ASN.1 setters/whatever for members to replace this mess. */
+
+ if (pSignerInfo->AuthenticatedAttributes.cItems == 0)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No authenticated or unauthenticated attributes! Sorry, no can do.");
+
+ Assert(pSignerInfo->UnauthenticatedAttributes.SetCore.Asn1Core.uTag == 0);
+ int rc = RTAsn1SetCore_Init(&pSignerInfo->UnauthenticatedAttributes.SetCore,
+ pSignerInfo->AuthenticatedAttributes.SetCore.Asn1Core.pOps);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTAsn1SetCore_Init failed: %Rrc", rc);
+ pSignerInfo->UnauthenticatedAttributes.SetCore.Asn1Core.uTag = 1;
+ pSignerInfo->UnauthenticatedAttributes.SetCore.Asn1Core.fClass = ASN1_TAGCLASS_CONTEXT | ASN1_TAGFLAG_CONSTRUCTED;
+ RTAsn1MemInitArrayAllocation(&pSignerInfo->UnauthenticatedAttributes.Allocation,
+ pSignerInfo->AuthenticatedAttributes.Allocation.pAllocator,
+ sizeof(**pSignerInfo->UnauthenticatedAttributes.papItems));
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Adds the @a pSrc signature as a nested signature.
+ *
+ * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error message
+ * on failure.
+ * @param pThis The signature to modify.
+ * @param pSrc The signature to add as nested.
+ * @param cVerbosity The verbosity.
+ * @param fPrepend Whether to prepend (true) or append (false) the
+ * source signature to the nested attribute.
+ */
+static RTEXITCODE SignToolPkcs7_AddNestedSignature(PSIGNTOOLPKCS7 pThis, PSIGNTOOLPKCS7 pSrc,
+ unsigned cVerbosity, bool fPrepend)
+{
+ PRTCRPKCS7SIGNERINFO pSignerInfo = pThis->pSignedData->SignerInfos.papItems[0];
+
+ /*
+ * Deal with UnauthenticatedAttributes being absent before trying to append to the array.
+ */
+ RTEXITCODE rcExit = SignToolPkcs7_EnsureUnauthenticatedAttributesPresent(pSignerInfo);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /*
+ * Find or add an unauthenticated attribute for nested signatures.
+ */
+ int rc = VERR_NOT_FOUND;
+ PRTCRPKCS7ATTRIBUTE pAttr = NULL;
+ int32_t iPos = pSignerInfo->UnauthenticatedAttributes.cItems;
+ while (iPos-- > 0)
+ if (pSignerInfo->UnauthenticatedAttributes.papItems[iPos]->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE)
+ {
+ pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iPos];
+ rc = VINF_SUCCESS;
+ break;
+ }
+ if (iPos < 0)
+ {
+ iPos = RTCrPkcs7Attributes_Append(&pSignerInfo->UnauthenticatedAttributes);
+ if (iPos >= 0)
+ {
+ if (cVerbosity >= 3)
+ RTMsgInfo("Adding UnauthenticatedAttribute #%u...", iPos);
+ Assert((uint32_t)iPos < pSignerInfo->UnauthenticatedAttributes.cItems);
+
+ pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iPos];
+ rc = RTAsn1ObjId_InitFromString(&pAttr->Type, RTCR_PKCS9_ID_MS_NESTED_SIGNATURE, pAttr->Allocation.pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Generalize the Type + enmType DYN stuff and generate setters. */
+ Assert(pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_NOT_PRESENT);
+ Assert(pAttr->uValues.pContentInfos == NULL);
+ pAttr->enmType = RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE;
+ rc = RTAsn1MemAllocZ(&pAttr->Allocation, (void **)&pAttr->uValues.pContentInfos,
+ sizeof(*pAttr->uValues.pContentInfos));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrPkcs7SetOfContentInfos_Init(pAttr->uValues.pContentInfos, pAttr->Allocation.pAllocator);
+ if (!RT_SUCCESS(rc))
+ RTMsgError("RTCrPkcs7ContentInfos_Init failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTAsn1MemAllocZ failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTAsn1ObjId_InitFromString failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTCrPkcs7Attributes_Append failed: %Rrc", iPos);
+ }
+ else if (cVerbosity >= 2)
+ RTMsgInfo("Found UnauthenticatedAttribute #%u...", iPos);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Append/prepend the signature.
+ */
+ uint32_t iActualPos = UINT32_MAX;
+ iPos = fPrepend ? 0 : pAttr->uValues.pContentInfos->cItems;
+ rc = RTCrPkcs7SetOfContentInfos_InsertEx(pAttr->uValues.pContentInfos, iPos, &pSrc->ContentInfo,
+ pAttr->Allocation.pAllocator, &iActualPos);
+ if (RT_SUCCESS(rc))
+ {
+ if (cVerbosity > 0)
+ RTMsgInfo("Added nested signature (#%u)", iActualPos);
+ if (cVerbosity >= 3)
+ {
+ RTMsgInfo("SingerInfo dump after change:");
+ RTAsn1Dump(RTCrPkcs7SignerInfo_GetAsn1Core(pSignerInfo), 0, 2, RTStrmDumpPrintfV, g_pStdOut);
+ }
+ return RTEXITCODE_SUCCESS;
+ }
+
+ RTMsgError("RTCrPkcs7ContentInfos_InsertEx failed: %Rrc", rc);
+ }
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Writes the signature to the file.
+ *
+ * Caller must have called SignToolPkcs7_Encode() prior to this function.
+ *
+ * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error
+ * message on failure.
+ * @param pThis The file which to write.
+ * @param cVerbosity The verbosity.
+ */
+static RTEXITCODE SignToolPkcs7_WriteSignatureToFile(PSIGNTOOLPKCS7 pThis, const char *pszFilename, unsigned cVerbosity)
+{
+ AssertReturn(pThis->cbNewBuf && pThis->pbNewBuf, RTEXITCODE_FAILURE);
+
+ /*
+ * Open+truncate file, write new signature, close. Simple.
+ */
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(hFile, pThis->pbNewBuf, pThis->cbNewBuf, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileClose(hFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (cVerbosity > 0)
+ RTMsgInfo("Wrote %u bytes to %s", pThis->cbNewBuf, pszFilename);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ RTMsgError("RTFileClose failed on %s: %Rrc", pszFilename, rc);
+ }
+ else
+ RTMsgError("Write error on %s: %Rrc", pszFilename, rc);
+ }
+ else
+ RTMsgError("Failed to open %s for writing: %Rrc", pszFilename, rc);
+ return RTEXITCODE_FAILURE;
+}
+
+
+
+/**
+ * Worker for recursively searching for MS nested signatures and signer infos.
+ *
+ * @returns Pointer to the signer info corresponding to @a iReqSignature. NULL
+ * if not found.
+ * @param pSignedData The signature to search.
+ * @param piNextSignature Pointer to the variable keeping track of the next
+ * signature number.
+ * @param iReqSignature The request signature number.
+ * @param ppSignedData Where to return the signature data structure.
+ * Optional.
+ */
+static PRTCRPKCS7SIGNERINFO SignToolPkcs7_FindNestedSignatureByIndexWorker(PRTCRPKCS7SIGNEDDATA pSignedData,
+ uint32_t *piNextSignature,
+ uint32_t iReqSignature,
+ PRTCRPKCS7SIGNEDDATA *ppSignedData)
+{
+ for (uint32_t iSignerInfo = 0; iSignerInfo < pSignedData->SignerInfos.cItems; iSignerInfo++)
+ {
+ /* Match?*/
+ PRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[iSignerInfo];
+ if (*piNextSignature == iReqSignature)
+ {
+ if (ppSignedData)
+ *ppSignedData = pSignedData;
+ return pSignerInfo;
+ }
+ *piNextSignature += 1;
+
+ /* Look for nested signatures. */
+ for (uint32_t iAttrib = 0; iAttrib < pSignerInfo->UnauthenticatedAttributes.cItems; iAttrib++)
+ if (pSignerInfo->UnauthenticatedAttributes.papItems[iAttrib]->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE)
+ {
+ PRTCRPKCS7SETOFCONTENTINFOS pCntInfos;
+ pCntInfos = pSignerInfo->UnauthenticatedAttributes.papItems[iAttrib]->uValues.pContentInfos;
+ for (uint32_t iCntInfo = 0; iCntInfo < pCntInfos->cItems; iCntInfo++)
+ {
+ PRTCRPKCS7CONTENTINFO pCntInfo = pCntInfos->papItems[iCntInfo];
+ if (RTCrPkcs7ContentInfo_IsSignedData(pCntInfo))
+ {
+ PRTCRPKCS7SIGNERINFO pRet;
+ pRet = SignToolPkcs7_FindNestedSignatureByIndexWorker(pCntInfo->u.pSignedData, piNextSignature,
+ iReqSignature, ppSignedData);
+ if (pRet)
+ return pRet;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Locates the given nested signature.
+ *
+ * @returns Pointer to the signer info corresponding to @a iReqSignature. NULL
+ * if not found.
+ * @param pThis The PKCS\#7 structure to search.
+ * @param iReqSignature The requested signature number.
+ * @param ppSignedData Where to return the pointer to the signed data that
+ * the returned signer info belongs to.
+ *
+ * @todo Move into SPC or PKCS\#7.
+ */
+static PRTCRPKCS7SIGNERINFO SignToolPkcs7_FindNestedSignatureByIndex(PSIGNTOOLPKCS7 pThis, uint32_t iReqSignature,
+ PRTCRPKCS7SIGNEDDATA *ppSignedData)
+{
+ uint32_t iNextSignature = 0;
+ return SignToolPkcs7_FindNestedSignatureByIndexWorker(pThis->pSignedData, &iNextSignature, iReqSignature, ppSignedData);
+}
+
+
+
+/**
+ * Reads and decodes PKCS\#7 signature from the given executable, if it has one.
+ *
+ * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error message
+ * on failure.
+ * @param pThis The structure to initialize.
+ * @param pszFilename The executable filename.
+ * @param cVerbosity The verbosity.
+ * @param enmLdrArch For FAT binaries.
+ * @param fAllowUnsigned Whether to allow unsigned binaries.
+ */
+static RTEXITCODE SignToolPkcs7Exe_InitFromFile(PSIGNTOOLPKCS7EXE pThis, const char *pszFilename, unsigned cVerbosity,
+ RTLDRARCH enmLdrArch = RTLDRARCH_WHATEVER, bool fAllowUnsigned = false)
+{
+ /*
+ * Init the return structure.
+ */
+ RT_ZERO(*pThis);
+ pThis->hLdrMod = NIL_RTLDRMOD;
+ pThis->pszFilename = pszFilename;
+ pThis->enmType = RTSIGNTOOLFILETYPE_EXE;
+
+ /*
+ * Open the image and check if it's signed.
+ */
+ int rc = RTLdrOpen(pszFilename, RTLDR_O_FOR_VALIDATION, enmLdrArch, &pThis->hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ bool fIsSigned = false;
+ rc = RTLdrQueryProp(pThis->hLdrMod, RTLDRPROP_IS_SIGNED, &fIsSigned, sizeof(fIsSigned));
+ if (RT_SUCCESS(rc) && fIsSigned)
+ {
+ /*
+ * Query the PKCS#7 data (assuming M$ style signing) and hand it to a worker.
+ */
+ size_t cbActual = 0;
+#ifdef DEBUG
+ size_t cbBuf = 64;
+#else
+ size_t cbBuf = _512K;
+#endif
+ void *pvBuf = RTMemAllocZ(cbBuf);
+ if (pvBuf)
+ {
+ rc = RTLdrQueryPropEx(pThis->hLdrMod, RTLDRPROP_PKCS7_SIGNED_DATA, NULL /*pvBits*/, pvBuf, cbBuf, &cbActual);
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ RTMemFree(pvBuf);
+ cbBuf = cbActual;
+ pvBuf = RTMemAllocZ(cbActual);
+ if (pvBuf)
+ rc = RTLdrQueryPropEx(pThis->hLdrMod, RTLDRPROP_PKCS7_SIGNED_DATA, NULL /*pvBits*/,
+ pvBuf, cbBuf, &cbActual);
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ pThis->pbBuf = (uint8_t *)pvBuf;
+ pThis->cbBuf = cbActual;
+ if (RT_SUCCESS(rc))
+ {
+ if (cVerbosity > 2)
+ RTPrintf("PKCS#7 signature: %u bytes\n", cbActual);
+ if (cVerbosity > 3)
+ RTPrintf("%.*Rhxd\n", cbActual, pvBuf);
+
+ /*
+ * Decode it.
+ */
+ rc = SignToolPkcs7_Decode(pThis, false /*fCatalog*/);
+ if (RT_SUCCESS(rc))
+ return RTEXITCODE_SUCCESS;
+ }
+ else
+ RTMsgError("RTLdrQueryPropEx/RTLDRPROP_PKCS7_SIGNED_DATA failed on '%s': %Rrc\n", pszFilename, rc);
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ if (!fAllowUnsigned || cVerbosity >= 2)
+ RTMsgInfo("'%s': not signed\n", pszFilename);
+ if (fAllowUnsigned)
+ return RTEXITCODE_SUCCESS;
+ }
+ else
+ RTMsgError("RTLdrQueryProp/RTLDRPROP_IS_SIGNED failed on '%s': %Rrc\n", pszFilename, rc);
+ }
+ else
+ RTMsgError("Error opening executable image '%s': %Rrc", pszFilename, rc);
+
+ SignToolPkcs7Exe_Delete(pThis);
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Calculates the checksum of an executable.
+ *
+ * @returns Success indicator (errors are reported)
+ * @param pThis The exe file to checksum.
+ * @param hFile The file handle.
+ * @param puCheckSum Where to return the checksum.
+ */
+static bool SignToolPkcs7Exe_CalcPeCheckSum(PSIGNTOOLPKCS7EXE pThis, RTFILE hFile, uint32_t *puCheckSum)
+{
+#ifdef RT_OS_WINDOWS
+ /*
+ * Try use IMAGEHLP!MapFileAndCheckSumW first.
+ */
+ PRTUTF16 pwszPath;
+ int rc = RTStrToUtf16(pThis->pszFilename, &pwszPath);
+ if (RT_SUCCESS(rc))
+ {
+ decltype(MapFileAndCheckSumW) *pfnMapFileAndCheckSumW;
+ pfnMapFileAndCheckSumW = (decltype(MapFileAndCheckSumW) *)RTLdrGetSystemSymbol("IMAGEHLP.DLL", "MapFileAndCheckSumW");
+ if (pfnMapFileAndCheckSumW)
+ {
+ DWORD uOldSum = UINT32_MAX;
+ DWORD uCheckSum = UINT32_MAX;
+ DWORD dwRc = pfnMapFileAndCheckSumW(pwszPath, &uOldSum, &uCheckSum);
+ if (dwRc == CHECKSUM_SUCCESS)
+ {
+ *puCheckSum = uCheckSum;
+ return true;
+ }
+ }
+ }
+#endif
+
+ RT_NOREF(pThis, hFile, puCheckSum);
+ RTMsgError("Implement check sum calcuation fallback!");
+ return false;
+}
+
+
+/**
+ * Writes the signature to the file.
+ *
+ * This has the side-effect of closing the hLdrMod member. So, it can only be
+ * called once!
+ *
+ * Caller must have called SignToolPkcs7_Encode() prior to this function.
+ *
+ * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error
+ * message on failure.
+ * @param pThis The file which to write.
+ * @param cVerbosity The verbosity.
+ */
+static RTEXITCODE SignToolPkcs7Exe_WriteSignatureToFile(PSIGNTOOLPKCS7EXE pThis, unsigned cVerbosity)
+{
+ AssertReturn(pThis->cbNewBuf && pThis->pbNewBuf, RTEXITCODE_FAILURE);
+
+ /*
+ * Get the file header offset and arch before closing the destination handle.
+ */
+ uint32_t offNtHdrs;
+ int rc = RTLdrQueryProp(pThis->hLdrMod, RTLDRPROP_FILE_OFF_HEADER, &offNtHdrs, sizeof(offNtHdrs));
+ if (RT_SUCCESS(rc))
+ {
+ RTLDRARCH enmLdrArch = RTLdrGetArch(pThis->hLdrMod);
+ if (enmLdrArch != RTLDRARCH_INVALID)
+ {
+ RTLdrClose(pThis->hLdrMod);
+ pThis->hLdrMod = NIL_RTLDRMOD;
+ unsigned cbNtHdrs = 0;
+ switch (enmLdrArch)
+ {
+ case RTLDRARCH_AMD64:
+ cbNtHdrs = sizeof(IMAGE_NT_HEADERS64);
+ break;
+ case RTLDRARCH_X86_32:
+ cbNtHdrs = sizeof(IMAGE_NT_HEADERS32);
+ break;
+ default:
+ RTMsgError("Unknown image arch: %d", enmLdrArch);
+ }
+ if (cbNtHdrs > 0)
+ {
+ if (cVerbosity > 0)
+ RTMsgInfo("offNtHdrs=%#x cbNtHdrs=%u\n", offNtHdrs, cbNtHdrs);
+
+ /*
+ * Open the executable file for writing.
+ */
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pThis->pszFilename, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ /* Read the file header and locate the security directory entry. */
+ union
+ {
+ IMAGE_NT_HEADERS32 NtHdrs32;
+ IMAGE_NT_HEADERS64 NtHdrs64;
+ } uBuf;
+ PIMAGE_DATA_DIRECTORY pSecDir = cbNtHdrs == sizeof(IMAGE_NT_HEADERS64)
+ ? &uBuf.NtHdrs64.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]
+ : &uBuf.NtHdrs32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
+
+ rc = RTFileReadAt(hFile, offNtHdrs, &uBuf, cbNtHdrs, NULL);
+ if ( RT_SUCCESS(rc)
+ && uBuf.NtHdrs32.Signature == IMAGE_NT_SIGNATURE)
+ {
+ /*
+ * Drop any old signature by truncating the file.
+ */
+ if ( pSecDir->Size > 8
+ && pSecDir->VirtualAddress > offNtHdrs + sizeof(IMAGE_NT_HEADERS32))
+ {
+ rc = RTFileSetSize(hFile, pSecDir->VirtualAddress);
+ if (RT_FAILURE(rc))
+ RTMsgError("Error truncating file to %#x bytes: %Rrc", pSecDir->VirtualAddress, rc);
+ }
+ else if (pSecDir->Size != 0 && pSecDir->VirtualAddress == 0)
+ rc = RTMsgErrorRc(VERR_BAD_EXE_FORMAT, "Bad security directory entry: VA=%#x Size=%#x",
+ pSecDir->VirtualAddress, pSecDir->Size);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Pad the file with zero up to a WIN_CERTIFICATE_ALIGNMENT boundary.
+ *
+ * Since the hash algorithm hashes everything up to the signature data,
+ * zero padding included, the alignment we do here must match the alignment
+ * padding that done while calculating the hash.
+ */
+ uint32_t const cbWinCert = RT_UOFFSETOF(WIN_CERTIFICATE, bCertificate);
+ uint64_t offCur = 0;
+ rc = RTFileQuerySize(hFile, &offCur);
+ if ( RT_SUCCESS(rc)
+ && offCur < _2G)
+ {
+ if (offCur != RT_ALIGN_64(offCur, WIN_CERTIFICATE_ALIGNMENT))
+ {
+ uint32_t const cbNeeded = (uint32_t)(RT_ALIGN_64(offCur, WIN_CERTIFICATE_ALIGNMENT) - offCur);
+ rc = RTFileWriteAt(hFile, offCur, g_abRTZero4K, cbNeeded, NULL);
+ if (RT_SUCCESS(rc))
+ offCur += cbNeeded;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Write the header followed by the signature data.
+ */
+ uint32_t const cbZeroPad = (uint32_t)(RT_ALIGN_Z(pThis->cbNewBuf, 8) - pThis->cbNewBuf);
+ pSecDir->VirtualAddress = (uint32_t)offCur;
+ pSecDir->Size = cbWinCert + (uint32_t)pThis->cbNewBuf + cbZeroPad;
+ if (cVerbosity >= 2)
+ RTMsgInfo("Writing %u (%#x) bytes of signature at %#x (%u).\n",
+ pSecDir->Size, pSecDir->Size, pSecDir->VirtualAddress, pSecDir->VirtualAddress);
+
+ WIN_CERTIFICATE WinCert;
+ WinCert.dwLength = pSecDir->Size;
+ WinCert.wRevision = WIN_CERT_REVISION_2_0;
+ WinCert.wCertificateType = WIN_CERT_TYPE_PKCS_SIGNED_DATA;
+
+ rc = RTFileWriteAt(hFile, offCur, &WinCert, cbWinCert, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ offCur += cbWinCert;
+ rc = RTFileWriteAt(hFile, offCur, pThis->pbNewBuf, pThis->cbNewBuf, NULL);
+ }
+ if (RT_SUCCESS(rc) && cbZeroPad)
+ {
+ offCur += pThis->cbNewBuf;
+ rc = RTFileWriteAt(hFile, offCur, g_abRTZero4K, cbZeroPad, NULL);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Reset the checksum (sec dir updated already) and rewrite the header.
+ */
+ uBuf.NtHdrs32.OptionalHeader.CheckSum = 0;
+ offCur = offNtHdrs;
+ rc = RTFileWriteAt(hFile, offNtHdrs, &uBuf, cbNtHdrs, NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTFileFlush(hFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Calc checksum and write out the header again.
+ */
+ uint32_t uCheckSum = UINT32_MAX;
+ if (SignToolPkcs7Exe_CalcPeCheckSum(pThis, hFile, &uCheckSum))
+ {
+ uBuf.NtHdrs32.OptionalHeader.CheckSum = uCheckSum;
+ rc = RTFileWriteAt(hFile, offNtHdrs, &uBuf, cbNtHdrs, NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTFileFlush(hFile);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileClose(hFile);
+ if (RT_SUCCESS(rc))
+ return RTEXITCODE_SUCCESS;
+ RTMsgError("RTFileClose failed: %Rrc\n", rc);
+ return RTEXITCODE_FAILURE;
+ }
+ }
+ }
+ }
+ }
+ if (RT_FAILURE(rc))
+ RTMsgError("Write error at %#RX64: %Rrc", offCur, rc);
+ }
+ else if (RT_SUCCESS(rc))
+ RTMsgError("File to big: %'RU64 bytes", offCur);
+ else
+ RTMsgError("RTFileQuerySize failed: %Rrc", rc);
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ RTMsgError("Not NT executable header!");
+ else
+ RTMsgError("Error reading NT headers (%#x bytes) at %#x: %Rrc", cbNtHdrs, offNtHdrs, rc);
+ RTFileClose(hFile);
+ }
+ else
+ RTMsgError("Failed to open '%s' for writing: %Rrc", pThis->pszFilename, rc);
+ }
+ }
+ else
+ RTMsgError("RTLdrGetArch failed!");
+ }
+ else
+ RTMsgError("RTLdrQueryProp/RTLDRPROP_FILE_OFF_HEADER failed: %Rrc", rc);
+ return RTEXITCODE_FAILURE;
+}
+
+#ifndef IPRT_SIGNTOOL_NO_SIGNING
+
+static PRTCRPKCS7ATTRIBUTE SignToolPkcs7_AuthAttribAppend(PRTCRPKCS7ATTRIBUTES pAuthAttribs)
+{
+ int32_t iPos = RTCrPkcs7Attributes_Append(pAuthAttribs);
+ if (iPos >= 0)
+ return pAuthAttribs->papItems[iPos];
+ RTMsgError("RTCrPkcs7Attributes_Append failed: %Rrc", iPos);
+ return NULL;
+}
+
+
+static RTEXITCODE SignToolPkcs7_AuthAttribsAddSigningTime(PRTCRPKCS7ATTRIBUTES pAuthAttribs, RTTIMESPEC SigningTime)
+{
+ /*
+ * Signing time. For the old-style timestamps, Symantec used ASN.1 UTC TIME.
+ * start -vv vv=ASN1_TAG_UTC_TIME
+ * 00000187d6a65fd0/23b0: 0d 01 09 05 31 0f 17 0d-31 36 31 30 30 35 30 37 ....1...16100507
+ * 00000187d6a65fe0/23c0: 35 30 33 30 5a 30 23 06-09 2a 86 48 86 f7 0d 01 5030Z0#..*.H....
+ * ^^- end 2016-10-05T07:50:30.000000000Z (161005075030Z)
+ */
+ PRTCRPKCS7ATTRIBUTE pAttr = SignToolPkcs7_AuthAttribAppend(pAuthAttribs);
+ if (!pAttr)
+ return RTEXITCODE_FAILURE;
+
+ int rc = RTCrPkcs7Attribute_SetSigningTime(pAttr, NULL, pAuthAttribs->Allocation.pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrPkcs7Attribute_SetSigningTime failed: %Rrc", rc);
+
+ /* Create the timestamp. */
+ int32_t iPos = RTAsn1SetOfTimes_Append(pAttr->uValues.pSigningTime);
+ if (iPos < 0)
+ return RTMsgErrorExitFailure("RTAsn1SetOfTimes_Append failed: %Rrc", iPos);
+
+ PRTASN1TIME pTime = pAttr->uValues.pSigningTime->papItems[iPos];
+ rc = RTAsn1Time_SetTimeSpec(pTime, pAttr->Allocation.pAllocator, &SigningTime);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTAsn1Time_SetTimeSpec failed: %Rrc", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE SignToolPkcs7_AuthAttribsAddSpcOpusInfo(PRTCRPKCS7ATTRIBUTES pAuthAttribs, void *pvInfo)
+{
+ /** @todo The OpusInfo is a structure with an optional SpcString and an
+ * optional SpcLink (url). The two attributes can be set using the /d and /du
+ * options of MS signtool.exe, I think. We shouldn't be using them atm. */
+
+ PRTCRPKCS7ATTRIBUTE pAttr = SignToolPkcs7_AuthAttribAppend(pAuthAttribs);
+ if (!pAttr)
+ return RTEXITCODE_FAILURE;
+
+ int rc = RTCrPkcs7Attribute_SetMsStatementType(pAttr, NULL, pAuthAttribs->Allocation.pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrPkcs7Attribute_SetMsStatementType failed: %Rrc", rc);
+
+ /* Override the ID. */
+ rc = RTAsn1ObjId_SetFromString(&pAttr->Type, RTCR_PKCS9_ID_MS_SP_OPUS_INFO, pAuthAttribs->Allocation.pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTAsn1ObjId_SetFromString failed: %Rrc", rc);
+
+ /* Add attribute value entry. */
+ int32_t iPos = RTAsn1SetOfObjIdSeqs_Append(pAttr->uValues.pObjIdSeqs);
+ if (iPos < 0)
+ return RTMsgErrorExitFailure("RTAsn1SetOfObjIdSeqs_Append failed: %Rrc", iPos);
+
+ RT_NOREF(pvInfo); Assert(!pvInfo);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE SignToolPkcs7_AuthAttribsAddMsStatementType(PRTCRPKCS7ATTRIBUTES pAuthAttribs, const char *pszTypeId)
+{
+ PRTCRPKCS7ATTRIBUTE pAttr = SignToolPkcs7_AuthAttribAppend(pAuthAttribs);
+ if (!pAttr)
+ return RTEXITCODE_FAILURE;
+
+ int rc = RTCrPkcs7Attribute_SetMsStatementType(pAttr, NULL, pAuthAttribs->Allocation.pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrPkcs7Attribute_SetMsStatementType failed: %Rrc", rc);
+
+ /* Add attribute value entry. */
+ int32_t iPos = RTAsn1SetOfObjIdSeqs_Append(pAttr->uValues.pObjIdSeqs);
+ if (iPos < 0)
+ return RTMsgErrorExitFailure("RTAsn1SetOfObjIdSeqs_Append failed: %Rrc", iPos);
+ PRTASN1SEQOFOBJIDS pSeqObjIds = pAttr->uValues.pObjIdSeqs->papItems[iPos];
+
+ /* Add a object id to the value. */
+ RTASN1OBJID ObjIdValue;
+ rc = RTAsn1ObjId_InitFromString(&ObjIdValue, pszTypeId, &g_RTAsn1DefaultAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTAsn1ObjId_InitFromString/%s failed: %Rrc", pszTypeId, rc);
+
+ rc = RTAsn1SeqOfObjIds_InsertEx(pSeqObjIds, 0 /*iPos*/, &ObjIdValue, &g_RTAsn1DefaultAllocator, NULL);
+ RTAsn1ObjId_Delete(&ObjIdValue);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTAsn1SeqOfObjIds_InsertEx failed: %Rrc", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE SignToolPkcs7_AuthAttribsAddContentType(PRTCRPKCS7ATTRIBUTES pAuthAttribs, const char *pszContentTypeId)
+{
+ PRTCRPKCS7ATTRIBUTE pAttr = SignToolPkcs7_AuthAttribAppend(pAuthAttribs);
+ if (!pAttr)
+ return RTEXITCODE_FAILURE;
+
+ int rc = RTCrPkcs7Attribute_SetContentType(pAttr, NULL, pAuthAttribs->Allocation.pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrPkcs7Attribute_SetContentType failed: %Rrc", rc);
+
+ /* Add a object id to the value. */
+ RTASN1OBJID ObjIdValue;
+ rc = RTAsn1ObjId_InitFromString(&ObjIdValue, pszContentTypeId, pAuthAttribs->Allocation.pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTAsn1ObjId_InitFromString/%s failed: %Rrc", pszContentTypeId, rc);
+
+ rc = RTAsn1SetOfObjIds_InsertEx(pAttr->uValues.pObjIds, 0 /*iPos*/, &ObjIdValue, pAuthAttribs->Allocation.pAllocator, NULL);
+ RTAsn1ObjId_Delete(&ObjIdValue);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTAsn1SetOfObjIds_InsertEx failed: %Rrc", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE SignToolPkcs7_AddAuthAttribsForTimestamp(PRTCRPKCS7ATTRIBUTES pAuthAttribs, TIMESTAMPTYPE enmTimestampType,
+ RTTIMESPEC SigningTime, PCRTCRX509CERTIFICATE pTimestampCert)
+{
+ /*
+ * Add content type.
+ */
+ RTEXITCODE rcExit = SignToolPkcs7_AuthAttribsAddContentType(pAuthAttribs,
+ enmTimestampType == kTimestampType_Old
+ ? RTCR_PKCS7_DATA_OID : RTCRTSPTSTINFO_OID);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /*
+ * Add signing time.
+ */
+ rcExit = SignToolPkcs7_AuthAttribsAddSigningTime(pAuthAttribs, SigningTime);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /*
+ * More later if we want to support fTimestampTypeOld = false perhaps?
+ */
+ Assert(enmTimestampType == kTimestampType_Old);
+ RT_NOREF(pTimestampCert);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE SignToolPkcs7_AddAuthAttribsForImageOrCatSignature(PRTCRPKCS7ATTRIBUTES pAuthAttribs, RTTIMESPEC SigningTime,
+ bool fNoSigningTime, const char *pszContentTypeId)
+{
+ /*
+ * Add SpcOpusInfo. No attribute values.
+ * SEQ start -vv vv- Type ObjId
+ * 1c60: 0e 03 02 1a 05 00 a0 70-30 10 06 0a 2b 06 01 04 .......p0...+...
+ * 1c70: 01 82 37 02 01 0c 31 02-30 00 30 19 06 09 2a 86 ..7...1.0.0...*.
+ * Set Of -^^ ^^- Empty Sequence.
+ */
+ RTEXITCODE rcExit = SignToolPkcs7_AuthAttribsAddSpcOpusInfo(pAuthAttribs, NULL /*pvInfo - none*/);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /*
+ * Add ContentType = Ms-SpcIndirectDataContext?
+ * SEQ start -vv vv- Type ObjId
+ * 1c70: 01 82 37 02 01 0c 31 02-30 00 30 19 06 09 2a 86 ..7...1.0.0...*.
+ * 1c80: 48 86 f7 0d 01 09 03 31-0c 06 0a 2b 06 01 04 01 H......1...+....
+ * 1c90: 82 37 02 01 04 ^^- ^^- ObjId
+ * ^- Set Of
+ */
+ rcExit = SignToolPkcs7_AuthAttribsAddContentType(pAuthAttribs, pszContentTypeId);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /*
+ * Add Ms-SpcStatementType = Ms-SpcIndividualCodeSigning.
+ * SEQ start -vv vv- Type ObjId
+ * 1c90: 82 37 02 01 04 30 1c 06-0a 2b 06 01 04 01 82 37 .7...0...+.....7
+ * 1ca0: 02 01 0b 31 0e 30 0c 06-0a 2b 06 01 04 01 82 37 ...1.0...+.....7
+ * 1cb0: 02 01 15 ^^ ^^ ^^- ObjId
+ * Set Of -^^ ^^- Sequence Of
+ */
+ rcExit = SignToolPkcs7_AuthAttribsAddMsStatementType(pAuthAttribs, RTCRSPC_STMT_TYPE_INDIVIDUAL_CODE_SIGNING);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /*
+ * Add signing time. We add this, even if signtool.exe, since OpenSSL will always do it otherwise.
+ */
+ if (!fNoSigningTime) /** @todo requires disabling the code in do_pkcs7_signed_attrib that adds it when absent */
+ {
+ rcExit = SignToolPkcs7_AuthAttribsAddSigningTime(pAuthAttribs, SigningTime);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ }
+
+ /** @todo more? Some certificate stuff? */
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE SignToolPkcs7_AppendCounterSignature(PRTCRPKCS7SIGNERINFO pSignerInfo,
+ PCRTCRPKCS7SIGNERINFO pCounterSignerInfo, unsigned cVerbosity)
+{
+ /* Make sure the UnauthenticatedAttributes member is there. */
+ RTEXITCODE rcExit = SignToolPkcs7_EnsureUnauthenticatedAttributesPresent(pSignerInfo);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+#if 0 /* Windows won't accept multiple timestamps either way. Doing the latter as it makes more sense to me... */
+ /* Append an entry to UnauthenticatedAttributes. */
+ uint32_t iPos;
+ int rc = RTCrPkcs7Attributes_InsertEx(&pSignerInfo->UnauthenticatedAttributes, 0 /*iPosition*/, NULL /*pToClone*/,
+ &g_RTAsn1DefaultAllocator, &iPos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrPkcs7Attributes_Append failed: %Rrc", rc);
+ Assert(iPos < pSignerInfo->UnauthenticatedAttributes.cItems); Assert(iPos == 0);
+ PRTCRPKCS7ATTRIBUTE pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iPos];
+
+ if (cVerbosity >= 2)
+ RTMsgInfo("Adding UnauthenticatedAttribute #%u...", iPos);
+#else
+ /* Look up the counter signature attribute, create one if needed. */
+ int rc;
+ uint32_t iPos = 0;
+ PRTCRPKCS7ATTRIBUTE pAttr = NULL;
+ for (; iPos < pSignerInfo->UnauthenticatedAttributes.cItems; iPos++)
+ {
+ pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iPos];
+ if (pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES)
+ break;
+ }
+ if (iPos >= pSignerInfo->UnauthenticatedAttributes.cItems)
+ {
+ /* Append a new entry to UnauthenticatedAttributes. */
+ rc = RTCrPkcs7Attributes_InsertEx(&pSignerInfo->UnauthenticatedAttributes, 0 /*iPosition*/, NULL /*pToClone*/,
+ &g_RTAsn1DefaultAllocator, &iPos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrPkcs7Attributes_Append failed: %Rrc", rc);
+ Assert(iPos < pSignerInfo->UnauthenticatedAttributes.cItems); Assert(iPos == 0);
+ pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iPos];
+
+ /* Create the attrib and its sub-set of counter signatures. */
+ rc = RTCrPkcs7Attribute_SetCounterSignatures(pAttr, NULL, pAttr->Allocation.pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrPkcs7Attribute_SetCounterSignatures failed: %Rrc", rc);
+ }
+
+ if (cVerbosity >= 2)
+ RTMsgInfo("Adding UnauthenticatedAttribute #%u.%u...", iPos, pAttr->uValues.pCounterSignatures->cItems);
+
+#endif
+
+ /* Insert the counter signature. */
+ rc = RTCrPkcs7SignerInfos_InsertEx(pAttr->uValues.pCounterSignatures, pAttr->uValues.pCounterSignatures->cItems /*iPosition*/,
+ pCounterSignerInfo, pAttr->Allocation.pAllocator, NULL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrPkcs7SignerInfos_InsertEx failed: %Rrc", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE SignToolPkcs7_AppendCertificate(PRTCRPKCS7SIGNEDDATA pSignedData, PCRTCRX509CERTIFICATE pCertToAppend)
+{
+ if (pSignedData->Certificates.cItems == 0 && !RTCrPkcs7SetOfCerts_IsPresent(&pSignedData->Certificates))
+ return RTMsgErrorExitFailure("PKCS#7 signature includes no certificates! Didn't expect that");
+
+ /* Already there? */
+ PCRTCRX509CERTIFICATE pExisting
+ = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(&pSignedData->Certificates, &pCertToAppend->TbsCertificate.Issuer,
+ &pCertToAppend->TbsCertificate.SerialNumber);
+ if (!pExisting || RTCrX509Certificate_Compare(pExisting, pCertToAppend) != 0)
+ {
+ /* Prepend a RTCRPKCS7CERT entry. */
+ uint32_t iPos;
+ int rc = RTCrPkcs7SetOfCerts_InsertEx(&pSignedData->Certificates, 0 /*iPosition*/, NULL /*pToClone*/,
+ &g_RTAsn1DefaultAllocator, &iPos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrPkcs7SetOfCerts_Append failed: %Rrc", rc);
+ PRTCRPKCS7CERT pCertEntry = pSignedData->Certificates.papItems[iPos];
+
+ /* Set (clone) the certificate. */
+ rc = RTCrPkcs7Cert_SetX509Cert(pCertEntry, pCertToAppend, pCertEntry->Allocation.pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrPkcs7Cert_X509Cert failed: %Rrc", rc);
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+#ifdef RT_OS_WINDOWS
+
+static PCRTUTF16 GetBCryptNameFromCrDigest(RTCRDIGEST hDigest)
+{
+ switch (RTCrDigestGetType(hDigest))
+ {
+ case RTDIGESTTYPE_MD2: return BCRYPT_MD2_ALGORITHM;
+ case RTDIGESTTYPE_MD4: return BCRYPT_MD4_ALGORITHM;
+ case RTDIGESTTYPE_SHA1: return BCRYPT_SHA1_ALGORITHM;
+ case RTDIGESTTYPE_SHA256: return BCRYPT_SHA256_ALGORITHM;
+ case RTDIGESTTYPE_SHA384: return BCRYPT_SHA384_ALGORITHM;
+ case RTDIGESTTYPE_SHA512: return BCRYPT_SHA512_ALGORITHM;
+ default:
+ RTMsgError("No BCrypt translation for %s/%d!", RTCrDigestGetAlgorithmOid(hDigest), RTCrDigestGetType(hDigest));
+ return L"No BCrypt translation";
+ }
+}
+
+static RTEXITCODE
+SignToolPkcs7_Pkcs7SignStuffAgainWithReal(const char *pszWhat, SignToolKeyPair *pCertKeyPair, unsigned cVerbosity,
+ PRTCRPKCS7CONTENTINFO pContentInfo, void **ppvSigned, size_t *pcbSigned)
+
+{
+ RT_NOREF(cVerbosity);
+
+ /*
+ * First remove the fake certificate from the PKCS7 structure and insert the real one.
+ */
+ PRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData;
+ unsigned iCert = pSignedData->Certificates.cItems;
+ unsigned cErased = 0;
+ while (iCert-- > 0)
+ {
+ PCRTCRPKCS7CERT pCert = pSignedData->Certificates.papItems[iCert];
+ if ( pCert->enmChoice == RTCRPKCS7CERTCHOICE_X509
+ && RTCrX509Certificate_MatchIssuerAndSerialNumber(pCert->u.pX509Cert,
+ &pCertKeyPair->pCertificate->TbsCertificate.Issuer,
+ &pCertKeyPair->pCertificate->TbsCertificate.SerialNumber))
+ {
+ RTCrPkcs7SetOfCerts_Erase(&pSignedData->Certificates, iCert);
+ cErased++;
+ }
+ }
+ if (cErased == 0)
+ return RTMsgErrorExitFailure("(%s) Failed to find temporary signing certificate in PKCS#7 from OpenSSL: %u certs",
+ pszWhat, pSignedData->Certificates.cItems);
+
+ /* Then insert the real signing certificate. */
+ PCRTCRX509CERTIFICATE const pRealCertificate = pCertKeyPair->getRealCertificate();
+ RTEXITCODE rcExit = SignToolPkcs7_AppendCertificate(pSignedData, pRealCertificate);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /*
+ * Modify the signer info to reflect the real certificate.
+ */
+ PRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[0];
+ RTCrX509Name_Delete(&pSignerInfo->IssuerAndSerialNumber.Name);
+ int rc = RTCrX509Name_Clone(&pSignerInfo->IssuerAndSerialNumber.Name,
+ &pRealCertificate->TbsCertificate.Issuer, &g_RTAsn1DefaultAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("(%s) RTCrX509Name_Clone failed: %Rrc", pszWhat, rc);
+
+ RTAsn1Integer_Delete(&pSignerInfo->IssuerAndSerialNumber.SerialNumber);
+ rc = RTAsn1Integer_Clone(&pSignerInfo->IssuerAndSerialNumber.SerialNumber,
+ &pRealCertificate->TbsCertificate.SerialNumber, &g_RTAsn1DefaultAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("(%s) RTAsn1Integer_Clone failed: %Rrc", pszWhat, rc);
+
+ /* There shouldn't be anything in the authenticated attributes that
+ we need to modify... */
+
+ /*
+ * Now a create a new signature using the real key. Since we haven't modified
+ * the authenticated attributes, we can just hash them as-is.
+ */
+ /* Create the hash to sign. */
+ RTCRDIGEST hDigest;
+ rc = RTCrDigestCreateByObjId(&hDigest, &pSignerInfo->DigestAlgorithm.Algorithm);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("(%s) RTCrDigestCreateByObjId failed on '%s': %Rrc",
+ pszWhat, pSignerInfo->DigestAlgorithm.Algorithm.szObjId, rc);
+
+ rcExit = RTEXITCODE_FAILURE;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTCrPkcs7Attributes_HashAttributes(&pSignerInfo->AuthenticatedAttributes, hDigest, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ BCRYPT_PKCS1_PADDING_INFO PaddingInfo = { GetBCryptNameFromCrDigest(hDigest) };
+ DWORD cbSignature = 0;
+ SECURITY_STATUS rcNCrypt = NCryptSignHash(pCertKeyPair->hNCryptPrivateKey, &PaddingInfo,
+ (PBYTE)RTCrDigestGetHash(hDigest), RTCrDigestGetHashSize(hDigest),
+ NULL, 0, &cbSignature, NCRYPT_SILENT_FLAG | BCRYPT_PAD_PKCS1);
+ if (rcNCrypt == ERROR_SUCCESS)
+ {
+ if (cVerbosity)
+ RTMsgInfo("PaddingInfo: '%ls' cb=%#x, was %#zx\n",
+ PaddingInfo.pszAlgId, cbSignature, pSignerInfo->EncryptedDigest.Asn1Core.cb);
+
+ rc = RTAsn1OctetString_AllocContent(&pSignerInfo->EncryptedDigest, NULL /*pvSrc*/, cbSignature,
+ &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(pSignerInfo->EncryptedDigest.Asn1Core.uData.pv);
+ rcNCrypt = NCryptSignHash(pCertKeyPair->hNCryptPrivateKey, &PaddingInfo,
+ (PBYTE)RTCrDigestGetHash(hDigest), RTCrDigestGetHashSize(hDigest),
+ (PBYTE)pSignerInfo->EncryptedDigest.Asn1Core.uData.pv, cbSignature, &cbSignature,
+ /*NCRYPT_SILENT_FLAG |*/ BCRYPT_PAD_PKCS1);
+ if (rcNCrypt == ERROR_SUCCESS)
+ {
+ /*
+ * Now we need to re-encode the whole thing and decode it again.
+ */
+ PRTASN1CORE pRoot = RTCrPkcs7ContentInfo_GetAsn1Core(pContentInfo);
+ uint32_t cbRealSigned;
+ rc = RTAsn1EncodePrepare(pRoot, RTASN1ENCODE_F_DER, &cbRealSigned, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ void *pvRealSigned = RTMemAllocZ(cbRealSigned);
+ if (pvRealSigned)
+ {
+ rc = RTAsn1EncodeToBuffer(pRoot, RTASN1ENCODE_F_DER, pvRealSigned, cbRealSigned,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ /* Decode it */
+ RTCrPkcs7ContentInfo_Delete(pContentInfo);
+
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pvRealSigned, cbRealSigned, RTErrInfoInitStatic(&ErrInfo),
+ &g_RTAsn1DefaultAllocator, 0, pszWhat);
+ rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, pContentInfo, "CI");
+ if (RT_SUCCESS(rc))
+ {
+ Assert(RTCrPkcs7ContentInfo_IsSignedData(pContentInfo));
+
+ /* Almost done! Just replace output buffer. */
+ RTMemFree(*ppvSigned);
+ *ppvSigned = pvRealSigned;
+ *pcbSigned = cbRealSigned;
+ pvRealSigned = NULL;
+ rcExit = RTEXITCODE_SUCCESS;
+ }
+ else
+ RTMsgError("(%s) RTCrPkcs7ContentInfo_DecodeAsn1 failed: %Rrc%#RTeim",
+ pszWhat, rc, &ErrInfo.Core);
+ }
+ else
+ RTMsgError("(%s) RTAsn1EncodeToBuffer failed: %Rrc%#RTeim", pszWhat, rc, &ErrInfo.Core);
+
+ RTMemFree(pvRealSigned);
+ }
+ else
+ RTMsgError("(%s) Failed to allocate %u bytes!", pszWhat, cbRealSigned);
+ }
+ else
+ RTMsgError("(%s) RTAsn1EncodePrepare failed: %Rrc%#RTeim", pszWhat, rc, &ErrInfo.Core);
+ }
+ else
+ RTMsgError("(%s) NCryptSignHash/2 failed: %Rwc %#x (%u)", pszWhat, rcNCrypt, rcNCrypt, rcNCrypt);
+ }
+ else
+ RTMsgError("(%s) RTAsn1OctetString_AllocContent(,,%#x) failed: %Rrc", pszWhat, cbSignature, rc);
+ }
+ else
+ RTMsgError("(%s) NCryptSignHash/1 failed: %Rwc %#x (%u)", pszWhat, rcNCrypt, rcNCrypt, rcNCrypt);
+ }
+ else
+ RTMsgError("(%s) RTCrPkcs7Attributes_HashAttributes failed: %Rrc%#RTeim", pszWhat, rc, &ErrInfo.Core);
+ RTCrDigestRelease(hDigest);
+ return rcExit;
+}
+
+#endif /* RT_OS_WINDOWS */
+
+static RTEXITCODE SignToolPkcs7_Pkcs7SignStuffInner(const char *pszWhat, const void *pvToDataToSign, size_t cbToDataToSign,
+ PCRTCRPKCS7ATTRIBUTES pAuthAttribs, RTCRSTORE hAdditionalCerts,
+ uint32_t fExtraFlags, RTDIGESTTYPE enmDigestType,
+ SignToolKeyPair *pCertKeyPair, unsigned cVerbosity,
+ void **ppvSigned, size_t *pcbSigned, PRTCRPKCS7CONTENTINFO pContentInfo,
+ PRTCRPKCS7SIGNEDDATA *ppSignedData)
+{
+ *ppvSigned = NULL;
+ if (pcbSigned)
+ *pcbSigned = 0;
+ if (ppSignedData)
+ *ppSignedData = NULL;
+
+ /* Figure out how large the signature will be. */
+ uint32_t const fSignFlags = RTCRPKCS7SIGN_SD_F_USE_V1 | RTCRPKCS7SIGN_SD_F_NO_SMIME_CAP | fExtraFlags;
+ size_t cbSigned = 1024;
+ RTERRINFOSTATIC ErrInfo;
+ int rc = RTCrPkcs7SimpleSignSignedData(fSignFlags, pCertKeyPair->pCertificate, pCertKeyPair->hPrivateKey,
+ pvToDataToSign, cbToDataToSign,enmDigestType, hAdditionalCerts, pAuthAttribs,
+ NULL, &cbSigned, RTErrInfoInitStatic(&ErrInfo));
+ if (rc != VERR_BUFFER_OVERFLOW)
+ return RTMsgErrorExitFailure("(%s) RTCrPkcs7SimpleSignSignedData failed: %Rrc%#RTeim", pszWhat, rc, &ErrInfo.Core);
+
+ /* Allocate memory for it and do the actual signing. */
+ void *pvSigned = RTMemAllocZ(cbSigned);
+ if (!pvSigned)
+ return RTMsgErrorExitFailure("(%s) Failed to allocate %#zx bytes for %s signature", pszWhat, cbSigned, pszWhat);
+ rc = RTCrPkcs7SimpleSignSignedData(fSignFlags, pCertKeyPair->pCertificate, pCertKeyPair->hPrivateKey,
+ pvToDataToSign, cbToDataToSign, enmDigestType, hAdditionalCerts, pAuthAttribs,
+ pvSigned, &cbSigned, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ if (cVerbosity > 2)
+ RTMsgInfo("%s signature: %#zx bytes\n%.*Rhxd\n", pszWhat, cbSigned, cbSigned, pvSigned);
+
+ /*
+ * Decode the signature and check that it is SignedData.
+ */
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pvSigned, (uint32_t)cbSigned, RTErrInfoInitStatic(&ErrInfo),
+ &g_RTAsn1DefaultAllocator, 0, pszWhat);
+ rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, pContentInfo, "CI");
+ if (RT_SUCCESS(rc))
+ {
+ if (RTCrPkcs7ContentInfo_IsSignedData(pContentInfo))
+ {
+#ifdef RT_OS_WINDOWS
+ /*
+ * If we're using a fake key+cert, we now have to re-do the signing using the real
+ * key+cert and the windows crypto API. This kludge is necessary because we can't
+ * typically get that the encoded private key, so it isn't possible to feed it to
+ * openssl.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ if (pCertKeyPair->pCertificateReal)
+ rcExit = SignToolPkcs7_Pkcs7SignStuffAgainWithReal(pszWhat, pCertKeyPair, cVerbosity, pContentInfo,
+ &pvSigned, &cbSigned);
+ if (rcExit == RTEXITCODE_SUCCESS)
+#endif
+ {
+ /*
+ * Set returns and maybe display the result before returning.
+ */
+ *ppvSigned = pvSigned;
+ if (pcbSigned)
+ *pcbSigned = cbSigned;
+ if (ppSignedData)
+ *ppSignedData = pContentInfo->u.pSignedData;
+
+ if (cVerbosity)
+ {
+ SHOWEXEPKCS7 ShowExe;
+ RT_ZERO(ShowExe);
+ ShowExe.cVerbosity = cVerbosity;
+ HandleShowExeWorkerPkcs7Display(&ShowExe, pContentInfo->u.pSignedData, 0, pContentInfo);
+ }
+ return RTEXITCODE_SUCCESS;
+ }
+ }
+
+ RTMsgError("(%s) RTCrPkcs7SimpleSignSignedData did not create SignedData: %s",
+ pszWhat, pContentInfo->ContentType.szObjId);
+ }
+ else
+ RTMsgError("(%s) RTCrPkcs7ContentInfo_DecodeAsn1 failed: %Rrc%#RTeim", pszWhat, rc, &ErrInfo.Core);
+ RTCrPkcs7ContentInfo_Delete(pContentInfo);
+ }
+ RTMemFree(pvSigned);
+ return RTEXITCODE_FAILURE;
+}
+
+
+static RTEXITCODE SignToolPkcs7_Pkcs7SignStuff(const char *pszWhat, const void *pvToDataToSign, size_t cbToDataToSign,
+ PCRTCRPKCS7ATTRIBUTES pAuthAttribs, RTCRSTORE hAdditionalCerts,
+ uint32_t fExtraFlags, RTDIGESTTYPE enmDigestType, SignToolKeyPair *pCertKeyPair,
+ unsigned cVerbosity, void **ppvSigned, size_t *pcbSigned,
+ PRTCRPKCS7CONTENTINFO pContentInfo, PRTCRPKCS7SIGNEDDATA *ppSignedData)
+{
+ /*
+ * Gather all additional certificates before doing the actual work.
+ */
+ RTCRSTORE hAllAdditionalCerts = pCertKeyPair->assembleAllAdditionalCertificates(hAdditionalCerts);
+ if (hAllAdditionalCerts == NIL_RTCRSTORE)
+ return RTEXITCODE_FAILURE;
+ RTEXITCODE rcExit = SignToolPkcs7_Pkcs7SignStuffInner(pszWhat, pvToDataToSign, cbToDataToSign, pAuthAttribs,
+ hAllAdditionalCerts, fExtraFlags, enmDigestType, pCertKeyPair,
+ cVerbosity, ppvSigned, pcbSigned, pContentInfo, ppSignedData);
+ RTCrStoreRelease(hAllAdditionalCerts);
+ return rcExit;
+}
+
+
+static RTEXITCODE SignToolPkcs7_AddTimestampSignatureEx(PRTCRPKCS7SIGNERINFO pSignerInfo, PRTCRPKCS7SIGNEDDATA pSignedData,
+ unsigned cVerbosity, bool fReplaceExisting,
+ RTTIMESPEC SigningTime, SignToolTimestampOpts *pTimestampOpts)
+{
+ AssertReturn(!pTimestampOpts->isNewType(), RTMsgErrorExitFailure("New style signatures not supported yet"));
+
+ /*
+ * Create a set of attributes we need to include in the AuthenticatedAttributes
+ * of the timestamp signature.
+ */
+ RTCRPKCS7ATTRIBUTES AuthAttribs;
+ int rc = RTCrPkcs7Attributes_Init(&AuthAttribs, &g_RTAsn1DefaultAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrPkcs7SetOfAttributes_Init failed: %Rrc", rc);
+
+ RTEXITCODE rcExit = SignToolPkcs7_AddAuthAttribsForTimestamp(&AuthAttribs, pTimestampOpts->m_enmType, SigningTime,
+ pTimestampOpts->getRealCertificate());
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Now create a PKCS#7 signature of the encrypted signature from the selected signer info.
+ */
+ void *pvSigned = NULL;
+ PRTCRPKCS7SIGNEDDATA pTsSignedData = NULL;
+ RTCRPKCS7CONTENTINFO TsContentInfo;
+ rcExit = SignToolPkcs7_Pkcs7SignStuffInner("timestamp", pSignerInfo->EncryptedDigest.Asn1Core.uData.pv,
+ pSignerInfo->EncryptedDigest.Asn1Core.cb, &AuthAttribs,
+ NIL_RTCRSTORE /*hAdditionalCerts*/, RTCRPKCS7SIGN_SD_F_DEATCHED,
+ RTDIGESTTYPE_SHA1, pTimestampOpts, cVerbosity,
+ &pvSigned, NULL /*pcbSigned*/, &TsContentInfo, &pTsSignedData);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+
+ /*
+ * If we're replacing existing timestamp signatures, remove old ones now.
+ */
+ if ( fReplaceExisting
+ && RTCrPkcs7Attributes_IsPresent(&pSignerInfo->UnauthenticatedAttributes))
+ {
+ uint32_t iItem = pSignerInfo->UnauthenticatedAttributes.cItems;
+ while (iItem-- > 0)
+ {
+ PRTCRPKCS7ATTRIBUTE pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iItem];
+ if (pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES) /* ASSUMES all counter sigs are timstamps */
+ {
+ if (cVerbosity > 1)
+ RTMsgInfo("Removing counter signature in attribute #%u\n", iItem);
+ rc = RTCrPkcs7Attributes_Erase(&pSignerInfo->UnauthenticatedAttributes, iItem);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("RTCrPkcs7Attributes_Erase failed on #%u: %Rrc", iItem, rc);
+ }
+ }
+ }
+
+ /*
+ * Add the new one.
+ */
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = SignToolPkcs7_AppendCounterSignature(pSignerInfo, pTsSignedData->SignerInfos.papItems[0], cVerbosity);
+
+ /*
+ * Make sure the signing certificate is included.
+ */
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ rcExit = SignToolPkcs7_AppendCertificate(pSignedData, pTimestampOpts->getRealCertificate());
+
+ PCRTCRCERTCTX pInterCaCtx = NULL;
+ while ((pInterCaCtx = pTimestampOpts->findNextIntermediateCert(pInterCaCtx)) != NULL)
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = SignToolPkcs7_AppendCertificate(pSignedData, pInterCaCtx->pCert);
+ }
+
+ /*
+ * Clean up.
+ */
+ RTCrPkcs7ContentInfo_Delete(&TsContentInfo);
+ RTMemFree(pvSigned);
+ }
+ }
+ RTCrPkcs7Attributes_Delete(&AuthAttribs);
+ return rcExit;
+}
+
+
+static RTEXITCODE SignToolPkcs7_AddTimestampSignature(SIGNTOOLPKCS7EXE *pThis, unsigned cVerbosity, unsigned iSignature,
+ bool fReplaceExisting, RTTIMESPEC SigningTime,
+ SignToolTimestampOpts *pTimestampOpts)
+{
+ /*
+ * Locate the signature specified by iSignature and add a timestamp to it.
+ */
+ PRTCRPKCS7SIGNEDDATA pSignedData = NULL;
+ PRTCRPKCS7SIGNERINFO pSignerInfo = SignToolPkcs7_FindNestedSignatureByIndex(pThis, iSignature, &pSignedData);
+ if (!pSignerInfo)
+ return RTMsgErrorExitFailure("No signature #%u in %s", iSignature, pThis->pszFilename);
+
+ return SignToolPkcs7_AddTimestampSignatureEx(pSignerInfo, pSignedData, cVerbosity, fReplaceExisting,
+ SigningTime, pTimestampOpts);
+}
+
+
+typedef enum SIGNDATATWEAK
+{
+ kSignDataTweak_NoTweak = 1,
+ kSignDataTweak_RootIsParent
+} SIGNDATATWEAK;
+
+static RTEXITCODE SignToolPkcs7_SignData(SIGNTOOLPKCS7 *pThis, PRTASN1CORE pToSignRoot, SIGNDATATWEAK enmTweak,
+ const char *pszContentTypeId, unsigned cVerbosity, uint32_t fExtraFlags,
+ RTDIGESTTYPE enmSigType, bool fReplaceExisting, bool fNoSigningTime,
+ SignToolKeyPair *pSigningCertKey, RTCRSTORE hAddCerts,
+ RTTIMESPEC SigningTime, size_t cTimestampOpts, SignToolTimestampOpts *paTimestampOpts)
+{
+ /*
+ * Encode it.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t cbEncoded = 0;
+ int rc = RTAsn1EncodePrepare(pToSignRoot, RTASN1ENCODE_F_DER, &cbEncoded, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTAsn1EncodePrepare failed: %Rrc%RTeim", rc, &ErrInfo.Core);
+
+ if (cVerbosity >= 4)
+ RTAsn1Dump(pToSignRoot, 0, 0, RTStrmDumpPrintfV, g_pStdOut);
+
+ uint8_t *pbEncoded = (uint8_t *)RTMemTmpAllocZ(cbEncoded );
+ if (!pbEncoded)
+ return RTMsgErrorExitFailure("Failed to allocate %#z bytes for encoding data we're signing (%s)",
+ cbEncoded, pszContentTypeId);
+
+ RTEXITCODE rcExit = RTEXITCODE_FAILURE;
+ rc = RTAsn1EncodeToBuffer(pToSignRoot, RTASN1ENCODE_F_DER, pbEncoded, cbEncoded, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ size_t const cbToSign = cbEncoded - (enmTweak == kSignDataTweak_RootIsParent ? pToSignRoot->cbHdr : 0);
+ void const *pvToSign = pbEncoded + (enmTweak == kSignDataTweak_RootIsParent ? pToSignRoot->cbHdr : 0);
+
+ /*
+ * Create additional authenticated attributes.
+ */
+ RTCRPKCS7ATTRIBUTES AuthAttribs;
+ rc = RTCrPkcs7Attributes_Init(&AuthAttribs, &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ rcExit = SignToolPkcs7_AddAuthAttribsForImageOrCatSignature(&AuthAttribs, SigningTime, fNoSigningTime,
+ pszContentTypeId);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Ditch the old signature if so desired.
+ * (It is okay to do this in the CAT case too, as we've already
+ * encoded the data and won't touch pToSignRoot any more.)
+ */
+ pToSignRoot = NULL; /* (may become invalid if replacing) */
+ if (fReplaceExisting && pThis->pSignedData)
+ {
+ RTCrPkcs7ContentInfo_Delete(&pThis->ContentInfo);
+ pThis->pSignedData = NULL;
+ RTMemFree(pThis->pbBuf);
+ pThis->pbBuf = NULL;
+ pThis->cbBuf = 0;
+ }
+
+ /*
+ * Do the actual signing.
+ */
+ SIGNTOOLPKCS7 Src = { RTSIGNTOOLFILETYPE_DETECT, NULL, 0, NULL };
+ PSIGNTOOLPKCS7 pSigDst = !pThis->pSignedData ? pThis : &Src;
+ rcExit = SignToolPkcs7_Pkcs7SignStuff("image", pvToSign, cbToSign, &AuthAttribs, hAddCerts,
+ fExtraFlags | RTCRPKCS7SIGN_SD_F_NO_DATA_ENCAP, enmSigType /** @todo ?? */,
+ pSigningCertKey, cVerbosity,
+ (void **)&pSigDst->pbBuf, &pSigDst->cbBuf,
+ &pSigDst->ContentInfo, &pSigDst->pSignedData);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Add the requested timestamp signatures if requested.
+ */
+ for (size_t i = 0; rcExit == RTEXITCODE_SUCCESS &&i < cTimestampOpts; i++)
+ if (paTimestampOpts[i].isComplete())
+ rcExit = SignToolPkcs7_AddTimestampSignatureEx(pSigDst->pSignedData->SignerInfos.papItems[0],
+ pSigDst->pSignedData,
+ cVerbosity, false /*fReplaceExisting*/,
+ SigningTime, &paTimestampOpts[i]);
+
+ /*
+ * Append the signature to the existing one, if that's what we're doing.
+ */
+ if (rcExit == RTEXITCODE_SUCCESS && pSigDst == &Src)
+ rcExit = SignToolPkcs7_AddNestedSignature(pThis, &Src, cVerbosity, true /*fPrepend*/); /** @todo prepend/append option */
+
+ /* cleanup */
+ if (pSigDst == &Src)
+ SignToolPkcs7_Delete(&Src);
+ }
+
+ }
+ RTCrPkcs7Attributes_Delete(&AuthAttribs);
+ }
+ else
+ RTMsgError("RTCrPkcs7SetOfAttributes_Init failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTAsn1EncodeToBuffer failed: %Rrc", rc);
+ RTMemTmpFree(pbEncoded);
+ return rcExit;
+}
+
+
+static RTEXITCODE SignToolPkcs7_SpcCompleteWithoutPageHashes(RTCRSPCINDIRECTDATACONTENT *pSpcIndData)
+{
+ PCRTASN1ALLOCATORVTABLE const pAllocator = &g_RTAsn1DefaultAllocator;
+ PRTCRSPCPEIMAGEDATA const pPeImage = pSpcIndData->Data.uValue.pPeImage;
+ Assert(pPeImage);
+
+ /*
+ * Set it to File with an empty name.
+ * RTCRSPCPEIMAGEDATA::Flags -vv
+ * RTCRSPCPEIMAGEDATA::SeqCore -vv T0 -vv vv- pT2/CtxTag2
+ * 0040: 04 01 82 37 02 01 0f 30-09 03 01 00 a0 04 a2 02 ...7...0........
+ * 0050: 80 00 30 21 30 09 06 05-2b 0e 03 02 1a 05 00 04 ..0!0...+.......
+ * ^^- pUcs2 / empty string
+ */
+
+ /* Create an empty BMP string. */
+ RTASN1STRING EmptyStr;
+ int rc = RTAsn1BmpString_Init(&EmptyStr, pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTAsn1BmpString_Init/Ucs2 failed: %Rrc", rc);
+
+ /* Create an SPC string and use the above empty string with the Ucs2 setter. */
+ RTEXITCODE rcExit = RTEXITCODE_FAILURE;
+ RTCRSPCSTRING SpcString;
+ rc = RTCrSpcString_Init(&SpcString, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrSpcString_SetUcs2(&SpcString, &EmptyStr, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /* Create a temporary SpcLink with the empty SpcString. */
+ RTCRSPCLINK SpcLink;
+ rc = RTCrSpcLink_Init(&SpcLink, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /* Use the setter on the SpcLink object to copy the SpcString to it. */
+ rc = RTCrSpcLink_SetFile(&SpcLink, &SpcString, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /* Use the setter to copy SpcLink to the PeImage structure. */
+ rc = RTCrSpcPeImageData_SetFile(pPeImage, &SpcLink, pAllocator);
+ if (RT_SUCCESS(rc))
+ rcExit = RTEXITCODE_SUCCESS;
+ else
+ RTMsgError("RTCrSpcPeImageData_SetFile failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTCrSpcLink_SetFile failed: %Rrc", rc);
+ RTCrSpcLink_Delete(&SpcLink);
+ }
+ else
+ RTMsgError("RTCrSpcLink_Init failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTCrSpcString_SetUcs2 failed: %Rrc", rc);
+ RTCrSpcString_Delete(&SpcString);
+ }
+ else
+ RTMsgError("RTCrSpcString_Init failed: %Rrc", rc);
+ RTAsn1BmpString_Delete(&EmptyStr);
+ return rcExit;
+}
+
+
+static RTEXITCODE SignToolPkcs7_SpcAddImagePageHashes(SIGNTOOLPKCS7EXE *pThis, RTCRSPCINDIRECTDATACONTENT *pSpcIndData,
+ RTDIGESTTYPE enmSigType)
+{
+ PCRTASN1ALLOCATORVTABLE const pAllocator = &g_RTAsn1DefaultAllocator;
+ PRTCRSPCPEIMAGEDATA const pPeImage = pSpcIndData->Data.uValue.pPeImage;
+ Assert(pPeImage);
+
+ /*
+ * The hashes are stored in the 'Moniker' attribute.
+ */
+ /* Create a temporary SpcLink with a default moniker. */
+ RTCRSPCLINK SpcLink;
+ int rc = RTCrSpcLink_Init(&SpcLink, pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrSpcLink_Init failed: %Rrc", rc);
+ rc = RTCrSpcLink_SetMoniker(&SpcLink, NULL, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /* Use the setter to copy SpcLink to the PeImage structure. */
+ rc = RTCrSpcPeImageData_SetFile(pPeImage, &SpcLink, pAllocator);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTCrSpcLink_SetFile failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTCrSpcLink_SetMoniker failed: %Rrc", rc);
+ RTCrSpcLink_Delete(&SpcLink);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+
+ /*
+ * Now go to work on the moniker. It doesn't have any autogenerated
+ * setters, so we must do stuff manually.
+ */
+ PRTCRSPCSERIALIZEDOBJECT pMoniker = pPeImage->T0.File.u.pMoniker;
+ RTUUID Uuid;
+ rc = RTUuidFromStr(&Uuid, RTCRSPCSERIALIZEDOBJECT_UUID_STR);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTUuidFromStr failed: %Rrc", rc);
+
+ rc = RTAsn1OctetString_AllocContent(&pMoniker->Uuid, &Uuid, sizeof(Uuid), pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTAsn1String_InitWithValue/UUID failed: %Rrc", rc);
+
+ /* Create a new set of attributes and associate this with the SerializedData member. */
+ PRTCRSPCSERIALIZEDOBJECTATTRIBUTES pSpcAttribs;
+ rc = RTAsn1MemAllocZ(&pMoniker->SerializedData.EncapsulatedAllocation,
+ (void **)&pSpcAttribs, sizeof(*pSpcAttribs));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTAsn1MemAllocZ/pSpcAttribs failed: %Rrc", rc);
+ pMoniker->SerializedData.pEncapsulated = RTCrSpcSerializedObjectAttributes_GetAsn1Core(pSpcAttribs);
+ pMoniker->enmType = RTCRSPCSERIALIZEDOBJECTTYPE_ATTRIBUTES;
+ pMoniker->u.pData = pSpcAttribs;
+
+ rc = RTCrSpcSerializedObjectAttributes_Init(pSpcAttribs, pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrSpcSerializedObjectAttributes_Init failed: %Rrc", rc);
+
+ /*
+ * Add a single attribute to the set that we'll use for page hashes.
+ */
+ int32_t iPos = RTCrSpcSerializedObjectAttributes_Append(pSpcAttribs);
+ if (iPos < 0)
+ return RTMsgErrorExitFailure("RTCrSpcSerializedObjectAttributes_Append failed: %Rrc", iPos);
+ PRTCRSPCSERIALIZEDOBJECTATTRIBUTE pSpcObjAttr = pSpcAttribs->papItems[iPos];
+
+ if (enmSigType == RTDIGESTTYPE_SHA1)
+ rc = RTCrSpcSerializedObjectAttribute_SetV1Hashes(pSpcObjAttr, NULL, pAllocator);
+ else if (enmSigType == RTDIGESTTYPE_SHA256)
+ rc = RTCrSpcSerializedObjectAttribute_SetV2Hashes(pSpcObjAttr, NULL, pAllocator);
+ else
+ rc = VERR_CR_DIGEST_NOT_SUPPORTED;
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrSpcSerializedObjectAttribute_SetV1Hashes/SetV2Hashes failed: %Rrc", rc);
+ PRTCRSPCSERIALIZEDPAGEHASHES pSpcPageHashes = pSpcObjAttr->u.pPageHashes;
+ Assert(pSpcPageHashes);
+
+ /*
+ * Now ask the loader for the number of pages in the page hash table
+ * and calculate its size.
+ */
+ uint32_t cPages = 0;
+ rc = RTLdrQueryPropEx(pThis->hLdrMod, RTLDRPROP_HASHABLE_PAGES, NULL, &cPages, sizeof(cPages), NULL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTLdrQueryPropEx/RTLDRPROP_HASHABLE_PAGES failed: %Rrc", rc);
+
+ uint32_t const cbHash = RTCrDigestTypeToHashSize(enmSigType);
+ AssertReturn(cbHash > 0, RTMsgErrorExitFailure("Invalid value: enmSigType=%d", enmSigType));
+ uint32_t const cbTable = (sizeof(uint32_t) + cbHash) * cPages;
+
+ /*
+ * Allocate memory in the octect string.
+ */
+ rc = RTAsn1ContentAllocZ(&pSpcPageHashes->RawData.Asn1Core, cbTable, pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTAsn1ContentAllocZ failed to allocate %#x bytes for page hashes: %Rrc", cbTable, rc);
+ pSpcPageHashes->pData = (PCRTCRSPCPEIMAGEPAGEHASHES)pSpcPageHashes->RawData.Asn1Core.uData.pu8;
+
+ RTLDRPROP enmLdrProp;
+ switch (enmSigType)
+ {
+ case RTDIGESTTYPE_SHA1: enmLdrProp = RTLDRPROP_SHA1_PAGE_HASHES; break;
+ case RTDIGESTTYPE_SHA256: enmLdrProp = RTLDRPROP_SHA256_PAGE_HASHES; break;
+ default: AssertFailedReturn(RTMsgErrorExitFailure("Invalid value: enmSigType=%d", enmSigType));
+
+ }
+ rc = RTLdrQueryPropEx(pThis->hLdrMod, enmLdrProp, NULL, (void *)pSpcPageHashes->RawData.Asn1Core.uData.pv, cbTable, NULL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTLdrQueryPropEx/RTLDRPROP_SHA?_PAGE_HASHES/%#x failed: %Rrc", cbTable, rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE SignToolPkcs7_SpcAddImageHash(SIGNTOOLPKCS7EXE *pThis, RTCRSPCINDIRECTDATACONTENT *pSpcIndData,
+ RTDIGESTTYPE enmSigType)
+{
+ uint32_t const cbHash = RTCrDigestTypeToHashSize(enmSigType);
+ const char * const pszAlgId = RTCrDigestTypeToAlgorithmOid(enmSigType);
+
+ /*
+ * Ask the loader for the hash.
+ */
+ uint8_t abHash[RTSHA512_HASH_SIZE];
+ int rc = RTLdrHashImage(pThis->hLdrMod, enmSigType, abHash, sizeof(abHash));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTLdrHashImage/%s failed: %Rrc", RTCrDigestTypeToName(enmSigType), rc);
+
+ /*
+ * Set it.
+ */
+ /** @todo no setter, this should be okay, though... */
+ rc = RTAsn1ObjId_InitFromString(&pSpcIndData->DigestInfo.DigestAlgorithm.Algorithm, pszAlgId, &g_RTAsn1DefaultAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTAsn1ObjId_InitFromString/%s failed: %Rrc", pszAlgId, rc);
+ RTAsn1DynType_SetToNull(&pSpcIndData->DigestInfo.DigestAlgorithm.Parameters); /* ASSUMES RSA or similar */
+
+ rc = RTAsn1ContentDup(&pSpcIndData->DigestInfo.Digest.Asn1Core, abHash, cbHash, &g_RTAsn1DefaultAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTAsn1ContentDup/%#x failed: %Rrc", cbHash, rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE SignToolPkcs7_AddOrReplaceSignature(SIGNTOOLPKCS7EXE *pThis, unsigned cVerbosity, RTDIGESTTYPE enmSigType,
+ bool fReplaceExisting, bool fHashPages, bool fNoSigningTime,
+ SignToolKeyPair *pSigningCertKey, RTCRSTORE hAddCerts,
+ RTTIMESPEC SigningTime,
+ size_t cTimestampOpts, SignToolTimestampOpts *paTimestampOpts)
+{
+ /*
+ * We must construct the data to be packed into the PKCS#7 signature
+ * and signed.
+ */
+ PCRTASN1ALLOCATORVTABLE const pAllocator = &g_RTAsn1DefaultAllocator;
+ RTCRSPCINDIRECTDATACONTENT SpcIndData;
+ int rc = RTCrSpcIndirectDataContent_Init(&SpcIndData, pAllocator);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrSpcIndirectDataContent_Init failed: %Rrc", rc);
+
+ /* Set the data to PE image. */
+ /** @todo Generalize the Type + enmType DYN stuff and generate setters. */
+ Assert(SpcIndData.Data.enmType == RTCRSPCAAOVTYPE_NOT_PRESENT);
+ Assert(SpcIndData.Data.uValue.pPeImage == NULL);
+ RTEXITCODE rcExit;
+ rc = RTAsn1ObjId_SetFromString(&SpcIndData.Data.Type, RTCRSPCPEIMAGEDATA_OID, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ SpcIndData.Data.enmType = RTCRSPCAAOVTYPE_PE_IMAGE_DATA;
+ rc = RTAsn1MemAllocZ(&SpcIndData.Data.Allocation, (void **)&SpcIndData.Data.uValue.pPeImage,
+ sizeof(*SpcIndData.Data.uValue.pPeImage));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrSpcPeImageData_Init(SpcIndData.Data.uValue.pPeImage, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /* Old (SHA1) signatures has a Flags member, it's zero bits, though. */
+ if (enmSigType == RTDIGESTTYPE_SHA1)
+ {
+ uint8_t bFlags = 0;
+ RTASN1BITSTRING Flags;
+ rc = RTAsn1BitString_InitWithData(&Flags, &bFlags, 0, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrSpcPeImageData_SetFlags(SpcIndData.Data.uValue.pPeImage, &Flags, pAllocator);
+ RTAsn1BitString_Delete(&Flags);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("RTCrSpcPeImageData_SetFlags failed: %Rrc", rc);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("RTAsn1BitString_InitWithData failed: %Rrc", rc);
+ }
+
+ /*
+ * Add the hashes.
+ */
+ rcExit = SignToolPkcs7_SpcAddImageHash(pThis, &SpcIndData, enmSigType);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (fHashPages)
+ rcExit = SignToolPkcs7_SpcAddImagePageHashes(pThis, &SpcIndData, enmSigType);
+ else
+ rcExit = SignToolPkcs7_SpcCompleteWithoutPageHashes(&SpcIndData);
+
+ /*
+ * Encode and sign the SPC data, timestamp it, and line it up for adding to the executable.
+ */
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = SignToolPkcs7_SignData(pThis, RTCrSpcIndirectDataContent_GetAsn1Core(&SpcIndData),
+ kSignDataTweak_NoTweak, RTCRSPCINDIRECTDATACONTENT_OID, cVerbosity, 0,
+ enmSigType, fReplaceExisting, fNoSigningTime, pSigningCertKey, hAddCerts,
+ SigningTime, cTimestampOpts, paTimestampOpts);
+ }
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("RTCrPkcs7SignerInfos_Init failed: %Rrc", rc);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("RTAsn1MemAllocZ failed for RTCRSPCPEIMAGEDATA: %Rrc", rc);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("RTAsn1ObjId_SetWithString/SpcPeImageData failed: %Rrc", rc);
+
+ RTCrSpcIndirectDataContent_Delete(&SpcIndData);
+ return rcExit;
+}
+
+
+static RTEXITCODE SignToolPkcs7_AddOrReplaceCatSignature(SIGNTOOLPKCS7 *pThis, unsigned cVerbosity, RTDIGESTTYPE enmSigType,
+ bool fReplaceExisting, bool fNoSigningTime,
+ SignToolKeyPair *pSigningCertKey, RTCRSTORE hAddCerts,
+ RTTIMESPEC SigningTime,
+ size_t cTimestampOpts, SignToolTimestampOpts *paTimestampOpts)
+{
+ AssertReturn(pThis->pSignedData, RTMsgErrorExitFailure("pSignedData is NULL!"));
+
+ /*
+ * Figure out what to sign first.
+ */
+ uint32_t fExtraFlags = 0;
+ PRTASN1CORE pToSign = &pThis->pSignedData->ContentInfo.Content.Asn1Core;
+ const char *pszType = pThis->pSignedData->ContentInfo.ContentType.szObjId;
+
+ if (!fReplaceExisting && pThis->pSignedData->SignerInfos.cItems == 0)
+ fReplaceExisting = true;
+ if (!fReplaceExisting)
+ {
+ pszType = RTCR_PKCS7_DATA_OID;
+ fExtraFlags |= RTCRPKCS7SIGN_SD_F_DEATCHED;
+ }
+
+ /*
+ * Do the signing.
+ */
+ RTEXITCODE rcExit = SignToolPkcs7_SignData(pThis, pToSign, kSignDataTweak_RootIsParent,
+ pszType, cVerbosity, fExtraFlags, enmSigType, fReplaceExisting,
+ fNoSigningTime, pSigningCertKey, hAddCerts,
+ SigningTime, cTimestampOpts, paTimestampOpts);
+
+ /* probably need to clean up stuff related to nested signatures here later... */
+ return rcExit;
+}
+
+#endif /* !IPRT_SIGNTOOL_NO_SIGNING */
+
+
+/*********************************************************************************************************************************
+* Option handlers shared by 'sign-exe', 'sign-cat', 'add-timestamp-exe-signature' and others. *
+*********************************************************************************************************************************/
+#ifndef IPRT_SIGNTOOL_NO_SIGNING
+
+static RTEXITCODE HandleOptAddCert(PRTCRSTORE phStore, const char *pszFile)
+{
+ if (*phStore == NIL_RTCRSTORE)
+ {
+ int rc = RTCrStoreCreateInMem(phStore, 2);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrStoreCreateInMem(,2) failed: %Rrc", rc);
+ }
+ RTERRINFOSTATIC ErrInfo;
+ int rc = RTCrStoreCertAddFromFile(*phStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND, pszFile, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Error reading certificate from '%s': %Rrc%#RTeim", pszFile, rc, &ErrInfo.Core);
+ return RTEXITCODE_SUCCESS;
+}
+
+static RTEXITCODE HandleOptSignatureType(RTDIGESTTYPE *penmSigType, const char *pszType)
+{
+ if ( RTStrICmpAscii(pszType, "sha1") == 0
+ || RTStrICmpAscii(pszType, "sha-1") == 0)
+ *penmSigType = RTDIGESTTYPE_SHA1;
+ else if ( RTStrICmpAscii(pszType, "sha256") == 0
+ || RTStrICmpAscii(pszType, "sha-256") == 0)
+ *penmSigType = RTDIGESTTYPE_SHA256;
+ else
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown signature type: %s (expected sha1 or sha256)", pszType);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE HandleOptTimestampType(SignToolTimestampOpts *pTimestampOpts, const char *pszType)
+{
+ if (strcmp(pszType, "old") == 0)
+ pTimestampOpts->m_enmType = kTimestampType_Old;
+ else if (strcmp(pszType, "new") == 0)
+ pTimestampOpts->m_enmType = kTimestampType_New;
+ else
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown timestamp type: %s", pszType);
+ return RTEXITCODE_SUCCESS;
+}
+
+static RTEXITCODE HandleOptTimestampOverride(PRTTIMESPEC pSigningTime, const char *pszPartialTs)
+{
+ /*
+ * First try use it as-is.
+ */
+ if (RTTimeSpecFromString(pSigningTime, pszPartialTs) != NULL)
+ return RTEXITCODE_SUCCESS;
+
+ /* Check the input against a pattern, making sure we've got something that
+ makes sense before trying to merge. */
+ size_t const cchPartialTs = strlen(pszPartialTs);
+ static char s_szPattern[] = "0000-00-00T00:00:";
+ if (cchPartialTs > sizeof(s_szPattern) - 1) /* It is not a partial timestamp if we've got the seconds component. */
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid timestamp: %s", pszPartialTs);
+
+ for (size_t off = 0; off < cchPartialTs; off++)
+ switch (s_szPattern[off])
+ {
+ case '0':
+ if (!RT_C_IS_DIGIT(pszPartialTs[off]))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid timestamp, expected digit at position %u: %s",
+ off + 1, pszPartialTs);
+ break;
+ case '-':
+ case ':':
+ if (pszPartialTs[off] != s_szPattern[off])
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid timestamp, expected '%c' at position %u: %s",
+ s_szPattern[off], off + 1, pszPartialTs);
+ break;
+ case 'T':
+ if ( pszPartialTs[off] != 'T'
+ && pszPartialTs[off] != 't'
+ && pszPartialTs[off] != ' ')
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid timestamp, expected 'T' or space at position %u: %s",
+ off + 1, pszPartialTs);
+ break;
+ default:
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Internal error");
+ }
+
+ if (RT_C_IS_DIGIT(s_szPattern[cchPartialTs]) && RT_C_IS_DIGIT(s_szPattern[cchPartialTs - 1]))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Incomplete timstamp component: %s", pszPartialTs);
+
+ /*
+ * Take the current time and merge in the components from pszPartialTs.
+ */
+ char szSigningTime[RTTIME_STR_LEN];
+ RTTIMESPEC Now;
+ RTTimeSpecToString(RTTimeNow(&Now), szSigningTime, sizeof(szSigningTime));
+ memcpy(szSigningTime, pszPartialTs, cchPartialTs);
+ szSigningTime[4+1+2+1+2] = 'T';
+
+ /* Fix 29th for non-leap override: */
+ if (memcmp(&szSigningTime[5], RT_STR_TUPLE("02-29")) == 0)
+ {
+ if (!RTTimeIsLeapYear(RTStrToUInt32(szSigningTime)))
+ szSigningTime[9] = '8';
+ }
+ if (RTTimeSpecFromString(pSigningTime, szSigningTime) == NULL)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid timestamp: %s (%s)", pszPartialTs, szSigningTime);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+static RTEXITCODE HandleOptFileType(RTSIGNTOOLFILETYPE *penmFileType, const char *pszType)
+{
+ if (strcmp(pszType, "detect") == 0 || strcmp(pszType, "auto") == 0)
+ *penmFileType = RTSIGNTOOLFILETYPE_DETECT;
+ else if (strcmp(pszType, "exe") == 0)
+ *penmFileType = RTSIGNTOOLFILETYPE_EXE;
+ else if (strcmp(pszType, "cat") == 0)
+ *penmFileType = RTSIGNTOOLFILETYPE_CAT;
+ else
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown forced file type: %s", pszType);
+ return RTEXITCODE_SUCCESS;
+}
+
+#endif /* !IPRT_SIGNTOOL_NO_SIGNING */
+
+/**
+ * Detects the type of files @a pszFile is (by reading from it).
+ *
+ * @returns The file type, or RTSIGNTOOLFILETYPE_UNKNOWN (error displayed).
+ * @param enmForceFileType Usually set to RTSIGNTOOLFILETYPE_DETECT, but if
+ * not we'll return this without probing the file.
+ * @param pszFile The name of the file to detect the type of.
+ */
+static RTSIGNTOOLFILETYPE DetectFileType(RTSIGNTOOLFILETYPE enmForceFileType, const char *pszFile)
+{
+ /*
+ * Forced?
+ */
+ if (enmForceFileType != RTSIGNTOOLFILETYPE_DETECT)
+ return enmForceFileType;
+
+ /*
+ * Read the start of the file.
+ */
+ RTFILE hFile = NIL_RTFILE;
+ int rc = RTFileOpen(&hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("Error opening '%s' for reading: %Rrc", pszFile, rc);
+ return RTSIGNTOOLFILETYPE_UNKNOWN;
+ }
+
+ union
+ {
+ uint8_t ab[256];
+ uint16_t au16[256/2];
+ uint32_t au32[256/4];
+ } uBuf;
+ RT_ZERO(uBuf);
+
+ size_t cbRead = 0;
+ rc = RTFileRead(hFile, &uBuf, sizeof(uBuf), &cbRead);
+ if (RT_FAILURE(rc))
+ RTMsgError("Error reading from '%s': %Rrc", pszFile, rc);
+
+ uint64_t cbFile;
+ int rcSize = RTFileQuerySize(hFile, &cbFile);
+ if (RT_FAILURE(rcSize))
+ RTMsgError("Error querying size of '%s': %Rrc", pszFile, rc);
+
+ RTFileClose(hFile);
+ if (RT_FAILURE(rc) || RT_FAILURE(rcSize))
+ return RTSIGNTOOLFILETYPE_UNKNOWN;
+
+ /*
+ * Try guess the kind of file.
+ */
+ /* All the executable magics we know: */
+ if ( uBuf.au16[0] == RT_H2LE_U16_C(IMAGE_DOS_SIGNATURE)
+ || uBuf.au16[0] == RT_H2LE_U16_C(IMAGE_NE_SIGNATURE)
+ || uBuf.au16[0] == RT_H2LE_U16_C(IMAGE_LX_SIGNATURE)
+ || uBuf.au16[0] == RT_H2LE_U16_C(IMAGE_LE_SIGNATURE)
+ || uBuf.au32[0] == RT_H2LE_U32_C(IMAGE_NT_SIGNATURE)
+ || uBuf.au32[0] == RT_H2LE_U32_C(IMAGE_ELF_SIGNATURE)
+ || uBuf.au32[0] == IMAGE_FAT_SIGNATURE
+ || uBuf.au32[0] == IMAGE_FAT_SIGNATURE_OE
+ || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE
+ || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE_OE
+ || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE
+ || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE_OE)
+ return RTSIGNTOOLFILETYPE_EXE;
+
+ /*
+ * Catalog files are PKCS#7 SignedData and starts with a ContentInfo, i.e.:
+ * SEQUENCE {
+ * contentType OBJECT IDENTIFIER,
+ * content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
+ * }
+ *
+ * We ASSUME that it's DER encoded and doesn't use an indefinite length form
+ * at the start and that contentType is signedData (1.2.840.113549.1.7.2).
+ *
+ * Example of a 10353 (0x2871) byte long file:
+ * vv-------- contentType -------vv
+ * 00000000 30 82 28 6D 06 09 2A 86 48 86 F7 0D 01 07 02 A0
+ * 00000010 82 28 5E 30 82 28 5A 02 01 01 31 0B 30 09 06 05
+ */
+ if ( uBuf.ab[0] == (ASN1_TAG_SEQUENCE | ASN1_TAGFLAG_CONSTRUCTED)
+ && uBuf.ab[1] != 0x80 /* not indefinite form */
+ && uBuf.ab[1] > 0x30)
+ {
+ size_t off = 1;
+ uint32_t cbRec = uBuf.ab[1];
+ if (cbRec & 0x80)
+ {
+ cbRec &= 0x7f;
+ off += cbRec;
+ switch (cbRec)
+ {
+ case 1: cbRec = uBuf.ab[2]; break;
+ case 2: cbRec = RT_MAKE_U16( uBuf.ab[3], uBuf.ab[2]); break;
+ case 3: cbRec = RT_MAKE_U32_FROM_U8(uBuf.ab[4], uBuf.ab[3], uBuf.ab[2], 0); break;
+ case 4: cbRec = RT_MAKE_U32_FROM_U8(uBuf.ab[5], uBuf.ab[4], uBuf.ab[3], uBuf.ab[2]); break;
+ default: cbRec = UINT32_MAX; break;
+ }
+ }
+ if (off <= 5)
+ {
+ off++;
+ if (off + cbRec == cbFile)
+ {
+ /* If the contentType is signedData we're going to treat it as a catalog file,
+ we don't currently much care about the signed content of a cat file. */
+ static const uint8_t s_abSignedDataOid[] =
+ { ASN1_TAG_OID, 9 /*length*/, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02 };
+ if (memcmp(&uBuf.ab[off], s_abSignedDataOid, sizeof(s_abSignedDataOid)) == 0)
+ return RTSIGNTOOLFILETYPE_CAT;
+ }
+ }
+ }
+
+ RTMsgError("Unable to detect type of '%s'", pszFile);
+ return RTSIGNTOOLFILETYPE_UNKNOWN;
+}
+
+
+/*********************************************************************************************************************************
+* The 'extract-exe-signer-cert' command. *
+*********************************************************************************************************************************/
+
+static RTEXITCODE HelpExtractExeSignerCert(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT,
+ "extract-exe-signer-cert [--ber|--cer|--der] [--signature-index|-i <num>] [--input|--exe|-e] <exe> [--output|-o] <outfile.cer>\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+static RTEXITCODE WriteCertToFile(PCRTCRX509CERTIFICATE pCert, const char *pszFilename, bool fForce)
+{
+ RTEXITCODE rcExit = RTEXITCODE_FAILURE;
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszFilename,
+ RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | (fForce ? RTFILE_O_CREATE_REPLACE : RTFILE_O_CREATE));
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbCert = pCert->SeqCore.Asn1Core.cbHdr + pCert->SeqCore.Asn1Core.cb;
+ rc = RTFileWrite(hFile, pCert->SeqCore.Asn1Core.uData.pu8 - pCert->SeqCore.Asn1Core.cbHdr,
+ cbCert, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileClose(hFile);
+ if (RT_SUCCESS(rc))
+ {
+ hFile = NIL_RTFILE;
+ rcExit = RTEXITCODE_SUCCESS;
+ RTMsgInfo("Successfully wrote %u bytes to '%s'", cbCert, pszFilename);
+ }
+ else
+ RTMsgError("RTFileClose failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTFileWrite failed: %Rrc", rc);
+ RTFileClose(hFile);
+ }
+ else
+ RTMsgError("Error opening '%s' for writing: %Rrc", pszFilename, rc);
+ return rcExit;
+}
+
+
+static RTEXITCODE HandleExtractExeSignerCert(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--ber", 'b', RTGETOPT_REQ_NOTHING },
+ { "--cer", 'c', RTGETOPT_REQ_NOTHING },
+ { "--der", 'd', RTGETOPT_REQ_NOTHING },
+ { "--exe", 'e', RTGETOPT_REQ_STRING },
+ { "--input", 'e', RTGETOPT_REQ_STRING },
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "--signature-index", 'i', RTGETOPT_REQ_UINT32 },
+ { "--force", 'f', RTGETOPT_REQ_NOTHING },
+ };
+
+ const char *pszExe = NULL;
+ const char *pszOut = NULL;
+ RTLDRARCH enmLdrArch = RTLDRARCH_WHATEVER;
+ unsigned cVerbosity = 0;
+ uint32_t fCursorFlags = RTASN1CURSOR_FLAGS_DER;
+ uint32_t iSignature = 0;
+ bool fForce = false;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'e': pszExe = ValueUnion.psz; break;
+ case 'o': pszOut = ValueUnion.psz; break;
+ case 'b': fCursorFlags = 0; break;
+ case 'c': fCursorFlags = RTASN1CURSOR_FLAGS_CER; break;
+ case 'd': fCursorFlags = RTASN1CURSOR_FLAGS_DER; break;
+ case 'f': fForce = true; break;
+ case 'i': iSignature = ValueUnion.u32; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpExtractExeSignerCert(g_pStdOut, RTSIGNTOOLHELP_FULL);
+
+ case VINF_GETOPT_NOT_OPTION:
+ if (!pszExe)
+ pszExe = ValueUnion.psz;
+ else if (!pszOut)
+ pszOut = ValueUnion.psz;
+ else
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many file arguments: %s", ValueUnion.psz);
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (!pszExe)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given.");
+ if (!pszOut)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No output file given.");
+ if (!fForce && RTPathExists(pszOut))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "The output file '%s' exists.", pszOut);
+
+ /*
+ * Do it.
+ */
+ /* Read & decode the PKCS#7 signature. */
+ SIGNTOOLPKCS7EXE This;
+ RTEXITCODE rcExit = SignToolPkcs7Exe_InitFromFile(&This, pszExe, cVerbosity, enmLdrArch);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /* Find the signing certificate (ASSUMING that the certificate used is shipped in the set of certificates). */
+ PRTCRPKCS7SIGNEDDATA pSignedData;
+ PCRTCRPKCS7SIGNERINFO pSignerInfo = SignToolPkcs7_FindNestedSignatureByIndex(&This, iSignature, &pSignedData);
+ rcExit = RTEXITCODE_FAILURE;
+ if (pSignerInfo)
+ {
+ PCRTCRPKCS7ISSUERANDSERIALNUMBER pISN = &pSignedData->SignerInfos.papItems[0]->IssuerAndSerialNumber;
+ PCRTCRX509CERTIFICATE pCert;
+ pCert = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(&pSignedData->Certificates,
+ &pISN->Name, &pISN->SerialNumber);
+ if (pCert)
+ {
+ /*
+ * Write it out.
+ */
+ rcExit = WriteCertToFile(pCert, pszOut, fForce);
+ }
+ else
+ RTMsgError("Certificate not found.");
+ }
+ else
+ RTMsgError("Could not locate signature #%u!", iSignature);
+
+ /* Delete the signature data. */
+ SignToolPkcs7Exe_Delete(&This);
+ }
+ return rcExit;
+}
+
+
+/*********************************************************************************************************************************
+* The 'extract-signer-root' & 'extract-timestamp-root' commands. *
+*********************************************************************************************************************************/
+class BaseExtractState
+{
+public:
+ const char *pszFile;
+ const char *pszOut;
+ RTLDRARCH enmLdrArch;
+ unsigned cVerbosity;
+ uint32_t iSignature;
+ bool fForce;
+ /** Timestamp or main signature. */
+ bool const fTimestamp;
+
+ BaseExtractState(bool a_fTimestamp)
+ : pszFile(NULL)
+ , pszOut(NULL)
+ , enmLdrArch(RTLDRARCH_WHATEVER)
+ , cVerbosity(0)
+ , iSignature(0)
+ , fForce(false)
+ , fTimestamp(a_fTimestamp)
+ {
+ }
+};
+
+class RootExtractState : public BaseExtractState
+{
+public:
+ CryptoStore RootStore;
+ CryptoStore AdditionalStore;
+
+ RootExtractState(bool a_fTimestamp)
+ : BaseExtractState(a_fTimestamp)
+ , RootStore()
+ , AdditionalStore()
+ { }
+
+ /**
+ * Creates the two stores, filling the root one with trusted CAs and
+ * certificates found on the system or in the user's account.
+ */
+ bool init(void)
+ {
+ int rc = RTCrStoreCreateInMem(&this->RootStore.m_hStore, 0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrStoreCreateInMem(&this->AdditionalStore.m_hStore, 0);
+ if (RT_SUCCESS(rc))
+ return true;
+ }
+ RTMsgError("RTCrStoreCreateInMem failed: %Rrc", rc);
+ return false;
+ }
+};
+
+
+/**
+ * Locates the target signature and certificate collection.
+ */
+static PRTCRPKCS7SIGNERINFO BaseExtractFindSignerInfo(SIGNTOOLPKCS7 *pThis, BaseExtractState *pState,
+ PRTCRPKCS7SIGNEDDATA *ppSignedData, PCRTCRPKCS7SETOFCERTS *ppCerts)
+{
+ *ppSignedData = NULL;
+ *ppCerts = NULL;
+
+ /*
+ * Locate the target signature.
+ */
+ PRTCRPKCS7SIGNEDDATA pSignedData = NULL;
+ PRTCRPKCS7SIGNERINFO pSignerInfo = SignToolPkcs7_FindNestedSignatureByIndex(pThis, pState->iSignature, &pSignedData);
+ if (pSignerInfo)
+ {
+ /*
+ * If the target is the timestamp we have to locate the relevant
+ * timestamp signature and adjust the return values.
+ */
+ if (pState->fTimestamp)
+ {
+ for (uint32_t iItem = 0; iItem < pSignerInfo->UnauthenticatedAttributes.cItems; iItem++)
+ {
+ PCRTCRPKCS7ATTRIBUTE pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iItem];
+ if (pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES)
+ {
+ /* ASSUME that all counter signatures are timestamping. */
+ if (pAttr->uValues.pCounterSignatures->cItems > 0)
+ {
+ *ppSignedData = pSignedData;
+ *ppCerts = &pSignedData->Certificates;
+ return pAttr->uValues.pCounterSignatures->papItems[0];
+ }
+ RTMsgWarning("Timestamp signature attribute is empty!");
+ }
+ else if (pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_TIMESTAMP)
+ {
+ /* ASSUME that all valid timestamp signatures for now, pick the first. */
+ if (pAttr->uValues.pContentInfos->cItems > 0)
+ {
+ PCRTCRPKCS7CONTENTINFO pContentInfo = pAttr->uValues.pContentInfos->papItems[0];
+ if (RTAsn1ObjId_CompareWithString(&pContentInfo->ContentType, RTCR_PKCS7_SIGNED_DATA_OID) == 0)
+ {
+ pSignedData = pContentInfo->u.pSignedData;
+ if (RTAsn1ObjId_CompareWithString(&pSignedData->ContentInfo.ContentType, RTCRTSPTSTINFO_OID) == 0)
+ {
+ if (pSignedData->SignerInfos.cItems > 0)
+ {
+ *ppSignedData = pSignedData;
+ *ppCerts = &pSignedData->Certificates;
+ return pSignedData->SignerInfos.papItems[0];
+ }
+ RTMsgWarning("Timestamp signature has no signers!");
+ }
+ else
+ RTMsgWarning("Timestamp signature contains wrong content (%s)!",
+ pSignedData->ContentInfo.ContentType.szObjId);
+ }
+ else
+ RTMsgWarning("Timestamp signature is not SignedData but %s!", pContentInfo->ContentType.szObjId);
+ }
+ else
+ RTMsgWarning("Timestamp signature attribute is empty!");
+ }
+ }
+ RTMsgError("Cound not find a timestamp signature associated with signature #%u!", pState->iSignature);
+ pSignerInfo = NULL;
+ }
+ else
+ {
+ *ppSignedData = pSignedData;
+ *ppCerts = &pSignedData->Certificates;
+ }
+ }
+ else
+ RTMsgError("Could not locate signature #%u!", pState->iSignature);
+ return pSignerInfo;
+}
+
+
+/** @callback_method_impl{FNRTDUMPPRINTFV} */
+static DECLCALLBACK(void) DumpToStdOutPrintfV(void *pvUser, const char *pszFormat, va_list va)
+{
+ RT_NOREF(pvUser);
+ RTPrintfV(pszFormat, va);
+}
+
+
+static RTEXITCODE RootExtractWorker2(SIGNTOOLPKCS7 *pThis, RootExtractState *pState, PRTERRINFOSTATIC pStaticErrInfo)
+{
+ /*
+ * Locate the target signature.
+ */
+ PRTCRPKCS7SIGNEDDATA pSignedData;
+ PCRTCRPKCS7SETOFCERTS pCerts;
+ PCRTCRPKCS7SIGNERINFO pSignerInfo = BaseExtractFindSignerInfo(pThis,pState, &pSignedData, &pCerts);
+ if (!pSignerInfo)
+ return RTMsgErrorExitFailure("Could not locate signature #%u!", pState->iSignature);
+
+ /* The next bit is modelled on first half of rtCrPkcs7VerifySignerInfo. */
+
+ /*
+ * Locate the signing certificate.
+ */
+ PCRTCRCERTCTX pSignerCertCtx = RTCrStoreCertByIssuerAndSerialNo(pState->RootStore.m_hStore,
+ &pSignerInfo->IssuerAndSerialNumber.Name,
+ &pSignerInfo->IssuerAndSerialNumber.SerialNumber);
+ if (!pSignerCertCtx)
+ pSignerCertCtx = RTCrStoreCertByIssuerAndSerialNo(pState->AdditionalStore.m_hStore,
+ &pSignerInfo->IssuerAndSerialNumber.Name,
+ &pSignerInfo->IssuerAndSerialNumber.SerialNumber);
+
+ PCRTCRX509CERTIFICATE pSignerCert;
+ if (pSignerCertCtx)
+ pSignerCert = pSignerCertCtx->pCert;
+ else
+ {
+ pSignerCert = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(pCerts,
+ &pSignerInfo->IssuerAndSerialNumber.Name,
+ &pSignerInfo->IssuerAndSerialNumber.SerialNumber);
+ if (!pSignerCert)
+ return RTMsgErrorExitFailure("Certificate not found: serial=%.*Rhxs",
+ pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.cb,
+ pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.uData.pv);
+ }
+
+ /*
+ * Now we build paths so we can get to the root certificate.
+ */
+ RTCRX509CERTPATHS hCertPaths;
+ int rc = RTCrX509CertPathsCreate(&hCertPaths, pSignerCert);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTCrX509CertPathsCreate failed: %Rrc", rc);
+
+ /* Configure: */
+ RTEXITCODE rcExit = RTEXITCODE_FAILURE;
+ rc = RTCrX509CertPathsSetTrustedStore(hCertPaths, pState->RootStore.m_hStore);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrX509CertPathsSetUntrustedStore(hCertPaths, pState->AdditionalStore.m_hStore);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrX509CertPathsSetUntrustedSet(hCertPaths, pCerts);
+ if (RT_SUCCESS(rc))
+ {
+ /* We don't technically need this, I think. */
+ rc = RTCrX509CertPathsSetTrustAnchorChecks(hCertPaths, true /*fEnable*/);
+ if (RT_SUCCESS(rc))
+ {
+ /* Build the paths: */
+ rc = RTCrX509CertPathsBuild(hCertPaths, RTErrInfoInitStatic(pStaticErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t const cPaths = RTCrX509CertPathsGetPathCount(hCertPaths);
+
+ /* Validate the paths: */
+ uint32_t cValidPaths = 0;
+ rc = RTCrX509CertPathsValidateAll(hCertPaths, &cValidPaths, RTErrInfoInitStatic(pStaticErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ if (pState->cVerbosity > 0)
+ RTMsgInfo("%u of %u paths are valid", cValidPaths, cPaths);
+ if (pState->cVerbosity > 1)
+ RTCrX509CertPathsDumpAll(hCertPaths, pState->cVerbosity, DumpToStdOutPrintfV, NULL);
+
+ /*
+ * Now, pick the first valid path with a real certificate at the end.
+ */
+ for (uint32_t iPath = 0; iPath < cPaths; iPath++)
+ {
+ PCRTCRX509CERTIFICATE pRootCert = NULL;
+ PCRTCRX509NAME pSubject = NULL;
+ bool fTrusted = false;
+ int rcVerify = -1;
+ rc = RTCrX509CertPathsQueryPathInfo(hCertPaths, iPath, &fTrusted, NULL /*pcNodes*/,
+ &pSubject, NULL, &pRootCert, NULL /*ppCertCtx*/, &rcVerify);
+ if (RT_SUCCESS(rc))
+ {
+ if (fTrusted && RT_SUCCESS(rcVerify) && pRootCert)
+ {
+ /*
+ * Now copy out the certificate.
+ */
+ rcExit = WriteCertToFile(pRootCert, pState->pszOut, pState->fForce);
+ break;
+ }
+ }
+ else
+ {
+ RTMsgError("RTCrX509CertPathsQueryPathInfo failed: %Rrc", rc);
+ break;
+ }
+ }
+ }
+ else
+ {
+ RTMsgError("RTCrX509CertPathsValidateAll failed: %Rrc%#RTeim", rc, &pStaticErrInfo->Core);
+ RTCrX509CertPathsDumpAll(hCertPaths, pState->cVerbosity, DumpToStdOutPrintfV, NULL);
+ }
+ }
+ else
+ RTMsgError("RTCrX509CertPathsBuild failed: %Rrc%#RTeim", rc, &pStaticErrInfo->Core);
+ }
+ else
+ RTMsgError("RTCrX509CertPathsSetTrustAnchorChecks failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTCrX509CertPathsSetUntrustedSet failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTCrX509CertPathsSetUntrustedStore failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTCrX509CertPathsSetTrustedStore failed: %Rrc", rc);
+
+ uint32_t cRefs = RTCrX509CertPathsRelease(hCertPaths);
+ Assert(cRefs == 0); RT_NOREF(cRefs);
+
+ return rcExit;
+}
+
+
+static RTEXITCODE RootExtractWorker(RootExtractState *pState, PRTERRINFOSTATIC pStaticErrInfo)
+{
+ /*
+ * Check that all we need is there and whether the output file exists.
+ */
+ if (!pState->pszFile)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given.");
+ if (!pState->pszOut)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No output file given.");
+ if (!pState->fForce && RTPathExists(pState->pszOut))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "The output file '%s' exists.", pState->pszOut);
+
+ /*
+ * Detect the type of file we're dealing with, do type specific setup and
+ * call common worker to do the rest.
+ */
+ RTEXITCODE rcExit;
+ RTSIGNTOOLFILETYPE enmFileType = DetectFileType(RTSIGNTOOLFILETYPE_DETECT, pState->pszFile);
+ if (enmFileType == RTSIGNTOOLFILETYPE_EXE)
+ {
+ SIGNTOOLPKCS7EXE Exe;
+ rcExit = SignToolPkcs7Exe_InitFromFile(&Exe, pState->pszFile, pState->cVerbosity, pState->enmLdrArch);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ rcExit = RootExtractWorker2(&Exe, pState, pStaticErrInfo);
+ SignToolPkcs7Exe_Delete(&Exe);
+ }
+ }
+ else if (enmFileType == RTSIGNTOOLFILETYPE_CAT)
+ {
+ SIGNTOOLPKCS7 Cat;
+ rcExit = SignToolPkcs7_InitFromFile(&Cat, pState->pszFile, pState->cVerbosity);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ rcExit = RootExtractWorker2(&Cat, pState, pStaticErrInfo);
+ SignToolPkcs7_Delete(&Cat);
+ }
+ }
+ else
+ rcExit = RTEXITCODE_FAILURE;
+ return rcExit;
+}
+
+
+static RTEXITCODE HelpExtractRootCommon(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel, bool fTimestamp)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT,
+ "extract-%s-root [-v|--verbose] [-q|--quiet] [--signature-index|-i <num>] [--root <root-cert.der>] "
+ "[--self-signed-roots-from-system] [--additional <supp-cert.der>] "
+ "[--input] <signed-file> [-f|--force] [--output|-o] <outfile.cer>\n",
+ fTimestamp ? "timestamp" : "signer");
+ if (enmLevel == RTSIGNTOOLHELP_FULL)
+ {
+ RTStrmWrappedPrintf(pStrm, 0,
+ "\n"
+ "Extracts the root certificate of the %sgiven "
+ "signature. If there are more than one valid certificate path, the first one with "
+ "a full certificate will be picked.\n",
+ fTimestamp ? "first timestamp associated with the " : "");
+ RTStrmWrappedPrintf(pStrm, 0,
+ "\n"
+ "Options:\n"
+ " -v, --verbose, -q, --quite\n"
+ " Controls the noise level. The '-v' options are accumlative while '-q' is absolute.\n"
+ " Default: -q\n"
+ " -i <num>, --signature-index <num>\n"
+ " Zero-based index of the signature to extract the root for.\n"
+ " Default: -i 0\n"
+ " -r <root-cert.file>, --root <root-cert.file>\n"
+ " Use the certificate(s) in the specified file as a trusted root(s). "
+ "The file format can be PEM or DER.\n"
+ " -R, --self-signed-roots-from-system\n"
+ " Use all self-signed trusted root certificates found in the system and associated with the "
+ "current user as trusted roots. This is limited to self-signed certificates, so that we get "
+ "a full chain even if a non-end-entity certificate is present in any of those system stores for "
+ "some reason.\n"
+ " -a <supp-cert.file>, --additional <supp-cert.file>\n"
+ " Use the certificate(s) in the specified file as a untrusted intermediate certificates. "
+ "The file format can be PEM or DER.\n"
+ " --input <signed-file>\n"
+ " Signed executable or security cabinet file to examine. The '--input' option bit is optional "
+ "and there to allow more flexible parameter ordering.\n"
+ " -f, --force\n"
+ " Overwrite existing output file. The default is not to overwriting any existing file.\n"
+ " -o <outfile.cer> --output <outfile.cer>\n"
+ " The name of the output file. Again the '-o|--output' bit is optional and only for flexibility.\n"
+ );
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE HandleExtractRootCommon(int cArgs, char **papszArgs, bool fTimestamp)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--root", 'r', RTGETOPT_REQ_STRING },
+ { "--self-signed-roots-from-system", 'R', RTGETOPT_REQ_NOTHING },
+ { "--additional", 'a', RTGETOPT_REQ_STRING },
+ { "--add", 'a', RTGETOPT_REQ_STRING },
+ { "--input", 'I', RTGETOPT_REQ_STRING },
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "--signature-index", 'i', RTGETOPT_REQ_UINT32 },
+ { "--force", 'f', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ };
+ RTERRINFOSTATIC StaticErrInfo;
+ RootExtractState State(fTimestamp);
+ if (!State.init())
+ return RTEXITCODE_FAILURE;
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'a':
+ if (!State.AdditionalStore.addFromFile(ValueUnion.psz, &StaticErrInfo))
+ return RTEXITCODE_FAILURE;
+ break;
+
+ case 'r':
+ if (!State.RootStore.addFromFile(ValueUnion.psz, &StaticErrInfo))
+ return RTEXITCODE_FAILURE;
+ break;
+
+ case 'R':
+ if (!State.RootStore.addSelfSignedRootsFromSystem(&StaticErrInfo))
+ return RTEXITCODE_FAILURE;
+ break;
+
+ case 'I': State.pszFile = ValueUnion.psz; break;
+ case 'o': State.pszOut = ValueUnion.psz; break;
+ case 'f': State.fForce = true; break;
+ case 'i': State.iSignature = ValueUnion.u32; break;
+ case 'v': State.cVerbosity++; break;
+ case 'q': State.cVerbosity = 0; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpExtractRootCommon(g_pStdOut, RTSIGNTOOLHELP_FULL, fTimestamp);
+
+ case VINF_GETOPT_NOT_OPTION:
+ if (!State.pszFile)
+ State.pszFile = ValueUnion.psz;
+ else if (!State.pszOut)
+ State.pszOut = ValueUnion.psz;
+ else
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many file arguments: %s", ValueUnion.psz);
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ return RootExtractWorker(&State, &StaticErrInfo);
+}
+
+
+static RTEXITCODE HelpExtractSignerRoot(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ return HelpExtractRootCommon(pStrm, enmLevel, false /*fTimestamp*/);
+}
+
+
+static RTEXITCODE HandleExtractSignerRoot(int cArgs, char **papszArgs)
+{
+ return HandleExtractRootCommon(cArgs, papszArgs, false /*fTimestamp*/ );
+}
+
+
+static RTEXITCODE HelpExtractTimestampRoot(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ return HelpExtractRootCommon(pStrm, enmLevel, true /*fTimestamp*/);
+}
+
+
+static RTEXITCODE HandleExtractTimestampRoot(int cArgs, char **papszArgs)
+{
+ return HandleExtractRootCommon(cArgs, papszArgs, true /*fTimestamp*/ );
+}
+
+
+/*********************************************************************************************************************************
+* The 'extract-exe-signature' command. *
+*********************************************************************************************************************************/
+
+static RTEXITCODE HelpExtractExeSignature(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT,
+ "extract-exe-signerature [--input|--exe|-e] <exe> [--output|-o] <outfile.pkcs7>\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+static RTEXITCODE HandleExtractExeSignature(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--exe", 'e', RTGETOPT_REQ_STRING },
+ { "--input", 'e', RTGETOPT_REQ_STRING },
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "--force", 'f', RTGETOPT_REQ_NOTHING },
+ };
+
+ const char *pszExe = NULL;
+ const char *pszOut = NULL;
+ RTLDRARCH enmLdrArch = RTLDRARCH_WHATEVER;
+ unsigned cVerbosity = 0;
+ bool fForce = false;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'e': pszExe = ValueUnion.psz; break;
+ case 'o': pszOut = ValueUnion.psz; break;
+ case 'f': fForce = true; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpExtractExeSignerCert(g_pStdOut, RTSIGNTOOLHELP_FULL);
+
+ case VINF_GETOPT_NOT_OPTION:
+ if (!pszExe)
+ pszExe = ValueUnion.psz;
+ else if (!pszOut)
+ pszOut = ValueUnion.psz;
+ else
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many file arguments: %s", ValueUnion.psz);
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (!pszExe)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given.");
+ if (!pszOut)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No output file given.");
+ if (!fForce && RTPathExists(pszOut))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "The output file '%s' exists.", pszOut);
+
+ /*
+ * Do it.
+ */
+ /* Read & decode the PKCS#7 signature. */
+ SIGNTOOLPKCS7EXE This;
+ RTEXITCODE rcExit = SignToolPkcs7Exe_InitFromFile(&This, pszExe, cVerbosity, enmLdrArch);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Write out the PKCS#7 signature.
+ */
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pszOut,
+ RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | (fForce ? RTFILE_O_CREATE_REPLACE : RTFILE_O_CREATE));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(hFile, This.pbBuf, This.cbBuf, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileClose(hFile);
+ if (RT_SUCCESS(rc))
+ {
+ hFile = NIL_RTFILE;
+ RTMsgInfo("Successfully wrote %u bytes to '%s'", This.cbBuf, pszOut);
+ rcExit = RTEXITCODE_SUCCESS;
+ }
+ else
+ RTMsgError("RTFileClose failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTFileWrite failed: %Rrc", rc);
+ RTFileClose(hFile);
+ }
+ else
+ RTMsgError("Error opening '%s' for writing: %Rrc", pszOut, rc);
+
+ /* Delete the signature data. */
+ SignToolPkcs7Exe_Delete(&This);
+ }
+ return rcExit;
+}
+
+
+/*********************************************************************************************************************************
+* The 'add-nested-exe-signature' command. *
+*********************************************************************************************************************************/
+
+static RTEXITCODE HelpAddNestedExeSignature(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT,
+ "add-nested-exe-signature [-v|--verbose] [-d|--debug] [-p|--prepend] <destination-exe> <source-exe>\n");
+ if (enmLevel == RTSIGNTOOLHELP_FULL)
+ RTStrmWrappedPrintf(pStrm, 0,
+ "\n"
+ "The --debug option allows the source-exe to be omitted in order to test the "
+ "encoding and PE file modification.\n"
+ "\n"
+ "The --prepend option puts the nested signature first rather than appending it "
+ "to the end of of the nested signature set. Windows reads nested signatures in "
+ "reverse order, so --prepend will logically putting it last.\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE HandleAddNestedExeSignature(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--prepend", 'p', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--debug", 'd', RTGETOPT_REQ_NOTHING },
+ };
+
+ const char *pszDst = NULL;
+ const char *pszSrc = NULL;
+ unsigned cVerbosity = 0;
+ bool fDebug = false;
+ bool fPrepend = false;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'v': cVerbosity++; break;
+ case 'd': fDebug = pszSrc == NULL; break;
+ case 'p': fPrepend = true; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpAddNestedExeSignature(g_pStdOut, RTSIGNTOOLHELP_FULL);
+
+ case VINF_GETOPT_NOT_OPTION:
+ if (!pszDst)
+ pszDst = ValueUnion.psz;
+ else if (!pszSrc)
+ {
+ pszSrc = ValueUnion.psz;
+ fDebug = false;
+ }
+ else
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many file arguments: %s", ValueUnion.psz);
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (!pszDst)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No destination executable given.");
+ if (!pszSrc && !fDebug)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No source executable file given.");
+
+ /*
+ * Do it.
+ */
+ /* Read & decode the source PKCS#7 signature. */
+ SIGNTOOLPKCS7EXE Src;
+ RTEXITCODE rcExit = pszSrc ? SignToolPkcs7Exe_InitFromFile(&Src, pszSrc, cVerbosity) : RTEXITCODE_SUCCESS;
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /* Ditto for the destination PKCS#7 signature. */
+ SIGNTOOLPKCS7EXE Dst;
+ rcExit = SignToolPkcs7Exe_InitFromFile(&Dst, pszDst, cVerbosity);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /* Do the signature manipulation. */
+ if (pszSrc)
+ rcExit = SignToolPkcs7_AddNestedSignature(&Dst, &Src, cVerbosity, fPrepend);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = SignToolPkcs7_Encode(&Dst, cVerbosity);
+
+ /* Update the destination executable file. */
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = SignToolPkcs7Exe_WriteSignatureToFile(&Dst, cVerbosity);
+
+ SignToolPkcs7Exe_Delete(&Dst);
+ }
+ if (pszSrc)
+ SignToolPkcs7Exe_Delete(&Src);
+ }
+
+ return rcExit;
+}
+
+
+/*********************************************************************************************************************************
+* The 'add-nested-cat-signature' command. *
+*********************************************************************************************************************************/
+
+static RTEXITCODE HelpAddNestedCatSignature(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT,
+ "add-nested-cat-signature [-v|--verbose] [-d|--debug] [-p|--prepend] <destination-cat> <source-cat>\n");
+ if (enmLevel == RTSIGNTOOLHELP_FULL)
+ RTStrmWrappedPrintf(pStrm, 0,
+ "\n"
+ "The --debug option allows the source-cat to be omitted in order to test the "
+ "ASN.1 re-encoding of the destination catalog file.\n"
+ "\n"
+ "The --prepend option puts the nested signature first rather than appending it "
+ "to the end of of the nested signature set. Windows reads nested signatures in "
+ "reverse order, so --prepend will logically putting it last.\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE HandleAddNestedCatSignature(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--prepend", 'p', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--debug", 'd', RTGETOPT_REQ_NOTHING },
+ };
+
+ const char *pszDst = NULL;
+ const char *pszSrc = NULL;
+ unsigned cVerbosity = 0;
+ bool fDebug = false;
+ bool fPrepend = false;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'v': cVerbosity++; break;
+ case 'd': fDebug = pszSrc == NULL; break;
+ case 'p': fPrepend = true; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpAddNestedCatSignature(g_pStdOut, RTSIGNTOOLHELP_FULL);
+
+ case VINF_GETOPT_NOT_OPTION:
+ if (!pszDst)
+ pszDst = ValueUnion.psz;
+ else if (!pszSrc)
+ {
+ pszSrc = ValueUnion.psz;
+ fDebug = false;
+ }
+ else
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many file arguments: %s", ValueUnion.psz);
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (!pszDst)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No destination catalog file given.");
+ if (!pszSrc && !fDebug)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No source catalog file given.");
+
+ /*
+ * Do it.
+ */
+ /* Read & decode the source PKCS#7 signature. */
+ SIGNTOOLPKCS7 Src;
+ RTEXITCODE rcExit = pszSrc ? SignToolPkcs7_InitFromFile(&Src, pszSrc, cVerbosity) : RTEXITCODE_SUCCESS;
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /* Ditto for the destination PKCS#7 signature. */
+ SIGNTOOLPKCS7EXE Dst;
+ rcExit = SignToolPkcs7_InitFromFile(&Dst, pszDst, cVerbosity);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /* Do the signature manipulation. */
+ if (pszSrc)
+ rcExit = SignToolPkcs7_AddNestedSignature(&Dst, &Src, cVerbosity, fPrepend);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = SignToolPkcs7_Encode(&Dst, cVerbosity);
+
+ /* Update the destination executable file. */
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = SignToolPkcs7_WriteSignatureToFile(&Dst, pszDst, cVerbosity);
+
+ SignToolPkcs7_Delete(&Dst);
+ }
+ if (pszSrc)
+ SignToolPkcs7_Delete(&Src);
+ }
+
+ return rcExit;
+}
+
+
+/*********************************************************************************************************************************
+* The 'add-timestamp-exe-signature' command. *
+*********************************************************************************************************************************/
+#ifndef IPRT_SIGNTOOL_NO_SIGNING
+
+static RTEXITCODE HelpAddTimestampExeSignature(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+
+ RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT,
+ "add-timestamp-exe-signature [-v|--verbose] [--signature-index|-i <num>] "
+ OPT_CERT_KEY_SYNOPSIS("--timestamp-", "")
+ "[--timestamp-type old|new] "
+ "[--timestamp-override <partial-isots>] "
+ "[--replace-existing|-r] "
+ "<exe>\n");
+ if (enmLevel == RTSIGNTOOLHELP_FULL)
+ RTStrmWrappedPrintf(pStrm, 0,
+ "This is mainly to test timestamp code.\n"
+ "\n"
+ "The --timestamp-override option can take a partial or full ISO timestamp. It is merged "
+ "with the current time if partial.\n"
+ "\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+static RTEXITCODE HandleAddTimestampExeSignature(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--signature-index", 'i', RTGETOPT_REQ_UINT32 },
+ OPT_CERT_KEY_GETOPTDEF_ENTRIES("--timestamp-", "", 1000),
+ { "--timestamp-type", OPT_TIMESTAMP_TYPE, RTGETOPT_REQ_STRING },
+ { "--timestamp-override", OPT_TIMESTAMP_OVERRIDE, RTGETOPT_REQ_STRING },
+ { "--replace-existing", 'r', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ };
+
+ unsigned cVerbosity = 0;
+ unsigned iSignature = 0;
+ bool fReplaceExisting = false;
+ SignToolTimestampOpts TimestampOpts("timestamp");
+ RTTIMESPEC SigningTime;
+ RTTimeNow(&SigningTime);
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ RTEXITCODE rcExit2 = RTEXITCODE_SUCCESS;
+ switch (ch)
+ {
+ OPT_CERT_KEY_SWITCH_CASES(TimestampOpts, 1000, ch, ValueUnion, rcExit2);
+ case 'i': iSignature = ValueUnion.u32; break;
+ case OPT_TIMESTAMP_TYPE: rcExit2 = HandleOptTimestampType(&TimestampOpts, ValueUnion.psz); break;
+ case OPT_TIMESTAMP_OVERRIDE: rcExit2 = HandleOptTimestampOverride(&SigningTime, ValueUnion.psz); break;
+ case 'r': fReplaceExisting = true; break;
+ case 'v': cVerbosity++; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpAddTimestampExeSignature(g_pStdOut, RTSIGNTOOLHELP_FULL);
+
+ case VINF_GETOPT_NOT_OPTION:
+ /* Do final certificate and key option processing (first file only). */
+ rcExit2 = TimestampOpts.finalizeOptions(cVerbosity);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ {
+ /* Do the work: */
+ SIGNTOOLPKCS7EXE Exe;
+ rcExit2 = SignToolPkcs7Exe_InitFromFile(&Exe, ValueUnion.psz, cVerbosity);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ {
+ rcExit2 = SignToolPkcs7_AddTimestampSignature(&Exe, cVerbosity, iSignature, fReplaceExisting,
+ SigningTime, &TimestampOpts);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ rcExit2 = SignToolPkcs7_Encode(&Exe, cVerbosity);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ rcExit2 = SignToolPkcs7Exe_WriteSignatureToFile(&Exe, cVerbosity);
+ SignToolPkcs7Exe_Delete(&Exe);
+ }
+ if (rcExit2 != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ rcExit2 = RTEXITCODE_SUCCESS;
+ }
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ {
+ rcExit = rcExit2;
+ break;
+ }
+ }
+ return rcExit;
+}
+
+#endif /*!IPRT_SIGNTOOL_NO_SIGNING */
+
+
+/*********************************************************************************************************************************
+* The 'sign-exe' command. *
+*********************************************************************************************************************************/
+#ifndef IPRT_SIGNTOOL_NO_SIGNING
+
+static RTEXITCODE HelpSign(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+
+ RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT,
+ "sign [-v|--verbose] "
+ "[--file-type exe|cat] "
+ "[--type|/fd sha1|sha256] "
+ "[--hash-pages|/ph] "
+ "[--no-hash-pages|/nph] "
+ "[--append/as] "
+ "[--no-signing-time] "
+ "[--add-cert <file>] "
+ "[--timestamp-type old|new] "
+ "[--timestamp-override <partial-isots>] "
+ "[--verbose|/debug|-v] "
+ OPT_CERT_KEY_SYNOPSIS("--", "")
+ OPT_CERT_KEY_SYNOPSIS("--timestamp-", "")
+ //OPT_CERT_KEY_SYNOPSIS("--timestamp-", "-2") - doesn't work, windows only uses one. Check again with new-style signatures
+ "<exe>\n");
+ if (enmLevel == RTSIGNTOOLHELP_FULL)
+ RTStrmWrappedPrintf(pStrm, 0,
+ "\n"
+ "Create a new code signature for an executable or catalog.\n"
+ "\n"
+ "Options:\n"
+ " --append, /as\n"
+ " Append the signature if one already exists. The default is to replace any existing signature.\n"
+ " --type sha1|sha256, /fd sha1|sha256\n"
+ " Signature type, SHA-1 or SHA-256.\n"
+ " --hash-pages, /ph, --no-page-hashes, /nph\n"
+ " Enables or disables page hashing. Ignored for catalog files. Default: --no-page-hashes\n"
+ " --add-cert <file>, /ac <file>\n"
+ " Adds (first) certificate from the file to the signature. Both PEM and DER (binary) encodings "
+ "are accepted. Repeat to add more certiifcates.\n"
+ " --timestamp-override <partial-iso-timestamp>\n"
+ " This specifies the signing time as a ISO timestamp. Partial timestamps are merged with the "
+ "current time. This is applied to any timestamp signature as well as the signingTime attribute of "
+ "main signature. Higher resolution than seconds is not supported. Default: Current time.\n"
+ " --no-signing-time\n"
+ " Don't set the signing time on the main signature, only on the timestamp one. Unfortunately, "
+ "this doesn't work without modifying OpenSSL a little.\n"
+ " --timestamp-type old|new\n"
+ " Selects the timstamp type. 'old' is the old style /t <url> stuff from signtool.exe. "
+ "'new' means a RTC-3161 timstamp - currently not implemented. Default: old\n"
+ //" --timestamp-type-2 old|new\n"
+ //" Same as --timestamp-type but for the 2nd timstamp signature.\n"
+ "\n"
+ //"Certificate and Key Options (--timestamp-cert-name[-2] etc for timestamps):\n"
+ "Certificate and Key Options (--timestamp-cert-name etc for timestamps):\n"
+ " --cert-subject <partial name>, /n <partial name>\n"
+ " Locate the main signature signing certificate and key, unless anything else is given, "
+ "by the given name substring. Overrides any previous --cert-sha1 and --cert-file options.\n"
+ " --cert-sha1 <hex bytes>, /sha1 <hex bytes>\n"
+ " Locate the main signature signing certificate and key, unless anything else is given, "
+ "by the given thumbprint. The hex bytes can be space separated, colon separated, just "
+ "bunched together, or a mix of these. This overrids any previous --cert-name and --cert-file "
+ "options.\n"
+ " --cert-store <name>, /s <store>\n"
+ " Certificate store to search when using --cert-name or --cert-sha1. Default: MY\n"
+ " --cert-machine-store, /sm\n"
+ " Use the machine store rather the ones of the current user.\n"
+ " --cert-file <file>, /f <file>\n"
+ " Load the certificate and key, unless anything else is given, from given file. Both PEM and "
+ "DER (binary) encodings are supported. Keys file can be RSA or PKCS#12 formatted.\n"
+ " --key-file <file>\n"
+ " Load the private key from the given file. Support RSA and PKCS#12 formatted files.\n"
+ " --key-password <password>, /p <password>\n"
+ " Password to use to decrypt a PKCS#12 password file.\n"
+ " --key-password-file <file>|stdin\n"
+ " Load password to decrypt the password file from the given file or from stdin.\n"
+ " --key-name <name>, /kc <name>\n"
+ " The private key container name. Not implemented.\n"
+ " --key-provider <name>, /csp <name>\n"
+ " The name of the crypto provider where the private key conatiner specified via --key-name "
+ "can be found.\n"
+ );
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE HandleSign(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--append", 'A', RTGETOPT_REQ_NOTHING },
+ { "/as", 'A', RTGETOPT_REQ_NOTHING },
+ { "/a", OPT_IGNORED, RTGETOPT_REQ_NOTHING }, /* select best cert automatically */
+ { "--type", 't', RTGETOPT_REQ_STRING },
+ { "/fd", 't', RTGETOPT_REQ_STRING },
+ { "--hash-pages", OPT_HASH_PAGES, RTGETOPT_REQ_NOTHING },
+ { "/ph", OPT_HASH_PAGES, RTGETOPT_REQ_NOTHING },
+ { "--no-hash-pages", OPT_NO_HASH_PAGES, RTGETOPT_REQ_NOTHING },
+ { "/nph", OPT_NO_HASH_PAGES, RTGETOPT_REQ_NOTHING },
+ { "--add-cert", OPT_ADD_CERT, RTGETOPT_REQ_STRING },
+ { "/ac", OPT_ADD_CERT, RTGETOPT_REQ_STRING },
+ { "--description", 'd', RTGETOPT_REQ_STRING },
+ { "--desc", 'd', RTGETOPT_REQ_STRING },
+ { "/d", 'd', RTGETOPT_REQ_STRING },
+ { "--description-url", 'D', RTGETOPT_REQ_STRING },
+ { "--desc-url", 'D', RTGETOPT_REQ_STRING },
+ { "/du", 'D', RTGETOPT_REQ_STRING },
+ { "--no-signing-time", OPT_NO_SIGNING_TIME, RTGETOPT_REQ_NOTHING },
+ OPT_CERT_KEY_GETOPTDEF_ENTRIES("--", "", 1000),
+ OPT_CERT_KEY_GETOPTDEF_COMPAT_ENTRIES( 1000),
+ OPT_CERT_KEY_GETOPTDEF_ENTRIES("--timestamp-", "", 1020),
+ //OPT_CERT_KEY_GETOPTDEF_ENTRIES("--timestamp-", "-1", 1020),
+ //OPT_CERT_KEY_GETOPTDEF_ENTRIES("--timestamp-", "-2", 1040), - disabled as windows cannot make use of it. Try again when
+ // new-style timestamp signatures has been implemented. Otherwise, just add two primary signatures with the two
+ // different timestamps certificates / hashes / whatever.
+ { "--timestamp-type", OPT_TIMESTAMP_TYPE, RTGETOPT_REQ_STRING },
+ { "--timestamp-type-1", OPT_TIMESTAMP_TYPE, RTGETOPT_REQ_STRING },
+ { "--timestamp-type-2", OPT_TIMESTAMP_TYPE_2, RTGETOPT_REQ_STRING },
+ { "--timestamp-override", OPT_TIMESTAMP_OVERRIDE, RTGETOPT_REQ_STRING },
+ { "--file-type", OPT_FILE_TYPE, RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "/v", 'v', RTGETOPT_REQ_NOTHING },
+ { "/debug", 'v', RTGETOPT_REQ_NOTHING },
+ };
+
+ unsigned cVerbosity = 0;
+ RTDIGESTTYPE enmSigType = RTDIGESTTYPE_SHA1;
+ bool fReplaceExisting = true;
+ bool fHashPages = false;
+ bool fNoSigningTime = false;
+ RTSIGNTOOLFILETYPE enmForceFileType = RTSIGNTOOLFILETYPE_DETECT;
+ SignToolKeyPair SigningCertKey("signing", true);
+ CryptoStore AddCerts;
+ const char *pszDescription = NULL; /** @todo implement putting descriptions into the OpusInfo stuff. */
+ const char *pszDescriptionUrl = NULL;
+ SignToolTimestampOpts aTimestampOpts[2] = { SignToolTimestampOpts("timestamp"), SignToolTimestampOpts("timestamp#2") };
+ RTTIMESPEC SigningTime;
+ RTTimeNow(&SigningTime);
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ RTEXITCODE rcExit2 = RTEXITCODE_SUCCESS;
+ switch (ch)
+ {
+ OPT_CERT_KEY_SWITCH_CASES(SigningCertKey, 1000, ch, ValueUnion, rcExit2);
+ OPT_CERT_KEY_SWITCH_CASES(aTimestampOpts[0], 1020, ch, ValueUnion, rcExit2);
+ OPT_CERT_KEY_SWITCH_CASES(aTimestampOpts[1], 1040, ch, ValueUnion, rcExit2);
+ case 't': rcExit2 = HandleOptSignatureType(&enmSigType, ValueUnion.psz); break;
+ case 'A': fReplaceExisting = false; break;
+ case 'd': pszDescription = ValueUnion.psz; break;
+ case 'D': pszDescriptionUrl = ValueUnion.psz; break;
+ case OPT_HASH_PAGES: fHashPages = true; break;
+ case OPT_NO_HASH_PAGES: fHashPages = false; break;
+ case OPT_NO_SIGNING_TIME: fNoSigningTime = true; break;
+ case OPT_ADD_CERT: rcExit2 = HandleOptAddCert(&AddCerts.m_hStore, ValueUnion.psz); break;
+ case OPT_TIMESTAMP_TYPE: rcExit2 = HandleOptTimestampType(&aTimestampOpts[0], ValueUnion.psz); break;
+ case OPT_TIMESTAMP_TYPE_2: rcExit2 = HandleOptTimestampType(&aTimestampOpts[1], ValueUnion.psz); break;
+ case OPT_TIMESTAMP_OVERRIDE: rcExit2 = HandleOptTimestampOverride(&SigningTime, ValueUnion.psz); break;
+ case OPT_FILE_TYPE: rcExit2 = HandleOptFileType(&enmForceFileType, ValueUnion.psz); break;
+ case OPT_IGNORED: break;
+ case 'v': cVerbosity++; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpSign(g_pStdOut, RTSIGNTOOLHELP_FULL);
+
+ case VINF_GETOPT_NOT_OPTION:
+ /*
+ * Do final certificate and key option processing (first file only).
+ */
+ rcExit2 = SigningCertKey.finalizeOptions(cVerbosity);
+ for (unsigned i = 0; rcExit2 == RTEXITCODE_SUCCESS && i < RT_ELEMENTS(aTimestampOpts); i++)
+ rcExit2 = aTimestampOpts[i].finalizeOptions(cVerbosity);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Detect file type.
+ */
+ RTSIGNTOOLFILETYPE enmFileType = DetectFileType(enmForceFileType, ValueUnion.psz);
+ if (enmFileType == RTSIGNTOOLFILETYPE_EXE)
+ {
+ /*
+ * Sign executable image.
+ */
+ SIGNTOOLPKCS7EXE Exe;
+ rcExit2 = SignToolPkcs7Exe_InitFromFile(&Exe, ValueUnion.psz, cVerbosity,
+ RTLDRARCH_WHATEVER, true /*fAllowUnsigned*/);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ {
+ rcExit2 = SignToolPkcs7_AddOrReplaceSignature(&Exe, cVerbosity, enmSigType, fReplaceExisting,
+ fHashPages, fNoSigningTime, &SigningCertKey,
+ AddCerts.m_hStore, SigningTime,
+ RT_ELEMENTS(aTimestampOpts), aTimestampOpts);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ rcExit2 = SignToolPkcs7_Encode(&Exe, cVerbosity);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ rcExit2 = SignToolPkcs7Exe_WriteSignatureToFile(&Exe, cVerbosity);
+ SignToolPkcs7Exe_Delete(&Exe);
+ }
+ }
+ else if (enmFileType == RTSIGNTOOLFILETYPE_CAT)
+ {
+ /*
+ * Sign catalog file.
+ */
+ SIGNTOOLPKCS7 Cat;
+ rcExit2 = SignToolPkcs7_InitFromFile(&Cat, ValueUnion.psz, cVerbosity);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ {
+ rcExit2 = SignToolPkcs7_AddOrReplaceCatSignature(&Cat, cVerbosity, enmSigType, fReplaceExisting,
+ fNoSigningTime, &SigningCertKey,
+ AddCerts.m_hStore, SigningTime,
+ RT_ELEMENTS(aTimestampOpts), aTimestampOpts);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ rcExit2 = SignToolPkcs7_Encode(&Cat, cVerbosity);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ rcExit2 = SignToolPkcs7_WriteSignatureToFile(&Cat, ValueUnion.psz, cVerbosity);
+ SignToolPkcs7_Delete(&Cat);
+ }
+ }
+ else
+ rcExit2 = RTEXITCODE_FAILURE;
+ if (rcExit2 != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ rcExit2 = RTEXITCODE_SUCCESS;
+ }
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ {
+ rcExit = rcExit2;
+ break;
+ }
+ }
+
+ return rcExit;
+}
+
+#endif /*!IPRT_SIGNTOOL_NO_SIGNING */
+
+
+/*********************************************************************************************************************************
+* The 'verify-exe' command. *
+*********************************************************************************************************************************/
+#ifndef IPRT_IN_BUILD_TOOL
+
+static RTEXITCODE HelpVerifyExe(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT,
+ "verify-exe [--verbose|--quiet] [--kernel] [--root <root-cert.der>] [--self-signed-roots-from-system] "
+ "[--additional <supp-cert.der>] [--type <win|osx>] <exe1> [exe2 [..]]\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+typedef struct VERIFYEXESTATE
+{
+ CryptoStore RootStore;
+ CryptoStore KernelRootStore;
+ CryptoStore AdditionalStore;
+ bool fKernel;
+ int cVerbose;
+ enum { kSignType_Windows, kSignType_OSX } enmSignType;
+ RTLDRARCH enmLdrArch;
+ uint32_t cBad;
+ uint32_t cOkay;
+ const char *pszFilename;
+ RTTIMESPEC ValidationTime;
+
+ VERIFYEXESTATE()
+ : fKernel(false)
+ , cVerbose(0)
+ , enmSignType(kSignType_Windows)
+ , enmLdrArch(RTLDRARCH_WHATEVER)
+ , cBad(0)
+ , cOkay(0)
+ , pszFilename(NULL)
+ {
+ RTTimeSpecSetSeconds(&ValidationTime, 0);
+ }
+} VERIFYEXESTATE;
+
+# ifdef VBOX
+/** Certificate store load set.
+ * Declared outside HandleVerifyExe because of braindead gcc visibility crap. */
+struct STSTORESET
+{
+ RTCRSTORE hStore;
+ PCSUPTAENTRY paTAs;
+ unsigned cTAs;
+};
+# endif
+
+/**
+ * @callback_method_impl{FNRTCRPKCS7VERIFYCERTCALLBACK,
+ * Standard code signing. Use this for Microsoft SPC.}
+ */
+static DECLCALLBACK(int) VerifyExecCertVerifyCallback(PCRTCRX509CERTIFICATE pCert, RTCRX509CERTPATHS hCertPaths, uint32_t fFlags,
+ void *pvUser, PRTERRINFO pErrInfo)
+{
+ VERIFYEXESTATE *pState = (VERIFYEXESTATE *)pvUser;
+ uint32_t cPaths = RTCrX509CertPathsGetPathCount(hCertPaths);
+
+ /*
+ * Dump all the paths.
+ */
+ if (pState->cVerbose > 0)
+ {
+ RTPrintf(fFlags & RTCRPKCS7VCC_F_TIMESTAMP ? "Timestamp Path%s:\n" : "Signature Path%s:\n",
+ cPaths == 1 ? "" : "s");
+ for (uint32_t iPath = 0; iPath < cPaths; iPath++)
+ {
+ //if (iPath != 0)
+ // RTPrintf("---\n");
+ RTCrX509CertPathsDumpOne(hCertPaths, iPath, pState->cVerbose, RTStrmDumpPrintfV, g_pStdOut);
+ *pErrInfo->pszMsg = '\0';
+ }
+ //RTPrintf(fFlags & RTCRPKCS7VCC_F_TIMESTAMP ? "--- end timestamp ---\n" : "--- end signature ---\n");
+ }
+
+ /*
+ * Test signing certificates normally doesn't have all the necessary
+ * features required below. So, treat them as special cases.
+ */
+ if ( hCertPaths == NIL_RTCRX509CERTPATHS
+ && RTCrX509Name_Compare(&pCert->TbsCertificate.Issuer, &pCert->TbsCertificate.Subject) == 0)
+ {
+ RTMsgInfo("Test signed.\n");
+ return VINF_SUCCESS;
+ }
+
+ if (hCertPaths == NIL_RTCRX509CERTPATHS)
+ RTMsgInfo("Signed by trusted certificate.\n");
+
+ /*
+ * Standard code signing capabilites required.
+ */
+ int rc = RTCrPkcs7VerifyCertCallbackCodeSigning(pCert, hCertPaths, fFlags, NULL, pErrInfo);
+ if ( RT_SUCCESS(rc)
+ && (fFlags & RTCRPKCS7VCC_F_SIGNED_DATA))
+ {
+ /*
+ * If windows kernel signing, a valid certificate path must be anchored
+ * by the microsoft kernel signing root certificate. The only
+ * alternative is test signing.
+ */
+ if ( pState->fKernel
+ && hCertPaths != NIL_RTCRX509CERTPATHS
+ && pState->enmSignType == VERIFYEXESTATE::kSignType_Windows)
+ {
+ uint32_t cFound = 0;
+ uint32_t cValid = 0;
+ for (uint32_t iPath = 0; iPath < cPaths; iPath++)
+ {
+ bool fTrusted;
+ PCRTCRX509NAME pSubject;
+ PCRTCRX509SUBJECTPUBLICKEYINFO pPublicKeyInfo;
+ int rcVerify;
+ rc = RTCrX509CertPathsQueryPathInfo(hCertPaths, iPath, &fTrusted, NULL /*pcNodes*/, &pSubject, &pPublicKeyInfo,
+ NULL, NULL /*pCertCtx*/, &rcVerify);
+ AssertRCBreak(rc);
+
+ if (RT_SUCCESS(rcVerify))
+ {
+ Assert(fTrusted);
+ cValid++;
+
+ /* Search the kernel signing root store for a matching anchor. */
+ RTCRSTORECERTSEARCH Search;
+ rc = RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280(pState->KernelRootStore.m_hStore, pSubject, &Search);
+ AssertRCBreak(rc);
+ PCRTCRCERTCTX pCertCtx;
+ while ((pCertCtx = RTCrStoreCertSearchNext(pState->KernelRootStore.m_hStore, &Search)) != NULL)
+ {
+ PCRTCRX509SUBJECTPUBLICKEYINFO pPubKeyInfo;
+ if (pCertCtx->pCert)
+ pPubKeyInfo = &pCertCtx->pCert->TbsCertificate.SubjectPublicKeyInfo;
+ else if (pCertCtx->pTaInfo)
+ pPubKeyInfo = &pCertCtx->pTaInfo->PubKey;
+ else
+ pPubKeyInfo = NULL;
+ if (RTCrX509SubjectPublicKeyInfo_Compare(pPubKeyInfo, pPublicKeyInfo) == 0)
+ cFound++;
+ RTCrCertCtxRelease(pCertCtx);
+ }
+
+ int rc2 = RTCrStoreCertSearchDestroy(pState->KernelRootStore.m_hStore, &Search); AssertRC(rc2);
+ }
+ }
+ if (RT_SUCCESS(rc) && cFound == 0)
+ rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, "Not valid kernel code signature.");
+ if (RT_SUCCESS(rc) && cValid != 2)
+ RTMsgWarning("%u valid paths, expected 2", cValid);
+ }
+ /*
+ * For Mac OS X signing, check for special developer ID attributes.
+ */
+ else if (pState->enmSignType == VERIFYEXESTATE::kSignType_OSX)
+ {
+ uint32_t cDevIdApp = 0;
+ uint32_t cDevIdKext = 0;
+ uint32_t cDevIdMacDev = 0;
+ for (uint32_t i = 0; i < pCert->TbsCertificate.T3.Extensions.cItems; i++)
+ {
+ PCRTCRX509EXTENSION pExt = pCert->TbsCertificate.T3.Extensions.papItems[i];
+ if (RTAsn1ObjId_CompareWithString(&pExt->ExtnId, RTCR_APPLE_CS_DEVID_APPLICATION_OID) == 0)
+ {
+ cDevIdApp++;
+ if (!pExt->Critical.fValue)
+ rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
+ "Dev ID Application certificate extension is not flagged critical");
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pExt->ExtnId, RTCR_APPLE_CS_DEVID_KEXT_OID) == 0)
+ {
+ cDevIdKext++;
+ if (!pExt->Critical.fValue)
+ rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
+ "Dev ID kext certificate extension is not flagged critical");
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pExt->ExtnId, RTCR_APPLE_CS_DEVID_MAC_SW_DEV_OID) == 0)
+ {
+ cDevIdMacDev++;
+ if (!pExt->Critical.fValue)
+ rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
+ "Dev ID Mac SW dev certificate extension is not flagged critical");
+ }
+ }
+ if (cDevIdApp == 0)
+ {
+ if (cDevIdMacDev == 0)
+ rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
+ "Certificate is missing the 'Dev ID Application' extension");
+ else
+ RTMsgWarning("Mac SW dev certificate used to sign code.");
+ }
+ if (cDevIdKext == 0 && pState->fKernel)
+ {
+ if (cDevIdMacDev == 0)
+ rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
+ "Certificate is missing the 'Dev ID kext' extension");
+ else
+ RTMsgWarning("Mac SW dev certificate used to sign kernel code.");
+ }
+ }
+ }
+
+ return rc;
+}
+
+/** @callback_method_impl{FNRTLDRVALIDATESIGNEDDATA} */
+static DECLCALLBACK(int) VerifyExeCallback(RTLDRMOD hLdrMod, PCRTLDRSIGNATUREINFO pInfo, PRTERRINFO pErrInfo, void *pvUser)
+{
+ VERIFYEXESTATE *pState = (VERIFYEXESTATE *)pvUser;
+ RT_NOREF_PV(hLdrMod);
+
+ switch (pInfo->enmType)
+ {
+ case RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA:
+ {
+ PCRTCRPKCS7CONTENTINFO pContentInfo = (PCRTCRPKCS7CONTENTINFO)pInfo->pvSignature;
+
+ if (pState->cVerbose > 0)
+ RTMsgInfo("Verifying '%s' signature #%u ...\n", pState->pszFilename, pInfo->iSignature + 1);
+
+ /*
+ * Dump the signed data if so requested and it's the first one, assuming that
+ * additional signatures in contained wihtin the same ContentInfo structure.
+ */
+ if (pState->cVerbose > 1 && pInfo->iSignature == 0)
+ RTAsn1Dump(&pContentInfo->SeqCore.Asn1Core, 0, 0, RTStrmDumpPrintfV, g_pStdOut);
+
+ /*
+ * We'll try different alternative timestamps here.
+ */
+ struct { RTTIMESPEC TimeSpec; const char *pszDesc; } aTimes[3];
+ unsigned cTimes = 0;
+
+ /* The specified timestamp. */
+ if (RTTimeSpecGetSeconds(&pState->ValidationTime) != 0)
+ {
+ aTimes[cTimes].TimeSpec = pState->ValidationTime;
+ aTimes[cTimes].pszDesc = "validation time";
+ cTimes++;
+ }
+
+ /* Linking timestamp: */
+ uint64_t uLinkingTime = 0;
+ int rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uLinkingTime, sizeof(uLinkingTime));
+ if (RT_SUCCESS(rc))
+ {
+ RTTimeSpecSetSeconds(&aTimes[cTimes].TimeSpec, uLinkingTime);
+ aTimes[cTimes].pszDesc = "at link time";
+ cTimes++;
+ }
+ else if (rc != VERR_NOT_FOUND)
+ RTMsgError("RTLdrQueryProp/RTLDRPROP_TIMESTAMP_SECONDS failed on '%s': %Rrc\n", pState->pszFilename, rc);
+
+ /* Now: */
+ RTTimeNow(&aTimes[cTimes].TimeSpec);
+ aTimes[cTimes].pszDesc = "now";
+ cTimes++;
+
+ /*
+ * Do the actual verification.
+ */
+ for (unsigned iTime = 0; iTime < cTimes; iTime++)
+ {
+ if (pInfo->pvExternalData)
+ rc = RTCrPkcs7VerifySignedDataWithExternalData(pContentInfo,
+ RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
+ | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT
+ | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_MS_TIMESTAMP_IF_PRESENT
+ | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS,
+ pState->AdditionalStore.m_hStore, pState->RootStore.m_hStore,
+ &aTimes[iTime].TimeSpec,
+ VerifyExecCertVerifyCallback, pState,
+ pInfo->pvExternalData, pInfo->cbExternalData, pErrInfo);
+ else
+ rc = RTCrPkcs7VerifySignedData(pContentInfo,
+ RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
+ | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT
+ | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_MS_TIMESTAMP_IF_PRESENT
+ | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS,
+ pState->AdditionalStore.m_hStore, pState->RootStore.m_hStore,
+ &aTimes[iTime].TimeSpec,
+ VerifyExecCertVerifyCallback, pState, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(rc == VINF_SUCCESS || rc == VINF_CR_DIGEST_DEPRECATED);
+ const char *pszNote = rc == VINF_CR_DIGEST_DEPRECATED ? " (deprecated digest)" : "";
+ if (pInfo->cSignatures == 1)
+ RTMsgInfo("'%s' is valid %s%s.\n", pState->pszFilename, aTimes[iTime].pszDesc, pszNote);
+ else
+ RTMsgInfo("'%s' signature #%u is valid %s%s.\n",
+ pState->pszFilename, pInfo->iSignature + 1, aTimes[iTime].pszDesc, pszNote);
+ pState->cOkay++;
+ return VINF_SUCCESS;
+ }
+ if (rc != VERR_CR_X509_CPV_NOT_VALID_AT_TIME)
+ {
+ if (pInfo->cSignatures == 1)
+ RTMsgError("%s: Failed to verify signature: %Rrc%#RTeim\n", pState->pszFilename, rc, pErrInfo);
+ else
+ RTMsgError("%s: Failed to verify signature #%u: %Rrc%#RTeim\n",
+ pState->pszFilename, pInfo->iSignature + 1, rc, pErrInfo);
+ pState->cBad++;
+ return VINF_SUCCESS;
+ }
+ }
+
+ if (pInfo->cSignatures == 1)
+ RTMsgError("%s: Signature is not valid at present or link time.\n", pState->pszFilename);
+ else
+ RTMsgError("%s: Signature #%u is not valid at present or link time.\n",
+ pState->pszFilename, pInfo->iSignature + 1);
+ pState->cBad++;
+ return VINF_SUCCESS;
+ }
+
+ default:
+ return RTErrInfoSetF(pErrInfo, VERR_NOT_SUPPORTED, "Unsupported signature type: %d", pInfo->enmType);
+ }
+}
+
+/**
+ * Worker for HandleVerifyExe.
+ */
+static RTEXITCODE HandleVerifyExeWorker(VERIFYEXESTATE *pState, const char *pszFilename, PRTERRINFOSTATIC pStaticErrInfo)
+{
+ /*
+ * Open the executable image and verify it.
+ */
+ RTLDRMOD hLdrMod;
+ int rc = RTLdrOpen(pszFilename, RTLDR_O_FOR_VALIDATION, pState->enmLdrArch, &hLdrMod);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error opening executable image '%s': %Rrc", pszFilename, rc);
+
+ /* Reset the state. */
+ pState->cBad = 0;
+ pState->cOkay = 0;
+ pState->pszFilename = pszFilename;
+
+ rc = RTLdrVerifySignature(hLdrMod, VerifyExeCallback, pState, RTErrInfoInitStatic(pStaticErrInfo));
+ if (RT_FAILURE(rc))
+ RTMsgError("RTLdrVerifySignature failed on '%s': %Rrc - %s\n", pszFilename, rc, pStaticErrInfo->szMsg);
+
+ int rc2 = RTLdrClose(hLdrMod);
+ if (RT_FAILURE(rc2))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTLdrClose failed: %Rrc\n", rc2);
+ if (RT_FAILURE(rc))
+ return rc != VERR_LDRVI_NOT_SIGNED ? RTEXITCODE_FAILURE : RTEXITCODE_SKIPPED;
+
+ return pState->cOkay > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+static RTEXITCODE HandleVerifyExe(int cArgs, char **papszArgs)
+{
+ RTERRINFOSTATIC StaticErrInfo;
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--kernel", 'k', RTGETOPT_REQ_NOTHING },
+ { "--root", 'r', RTGETOPT_REQ_STRING },
+ { "--self-signed-roots-from-system", 'R', RTGETOPT_REQ_NOTHING },
+ { "--additional", 'a', RTGETOPT_REQ_STRING },
+ { "--add", 'a', RTGETOPT_REQ_STRING },
+ { "--type", 't', RTGETOPT_REQ_STRING },
+ { "--validation-time", 'T', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ };
+
+ VERIFYEXESTATE State;
+ int rc = RTCrStoreCreateInMem(&State.RootStore.m_hStore, 0);
+ if (RT_SUCCESS(rc))
+ rc = RTCrStoreCreateInMem(&State.KernelRootStore.m_hStore, 0);
+ if (RT_SUCCESS(rc))
+ rc = RTCrStoreCreateInMem(&State.AdditionalStore.m_hStore, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error creating in-memory certificate store: %Rrc", rc);
+
+ RTGETOPTSTATE GetState;
+ rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) && ch != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (ch)
+ {
+ case 'a':
+ if (!State.AdditionalStore.addFromFile(ValueUnion.psz, &StaticErrInfo))
+ return RTEXITCODE_FAILURE;
+ break;
+
+ case 'r':
+ if (!State.RootStore.addFromFile(ValueUnion.psz, &StaticErrInfo))
+ return RTEXITCODE_FAILURE;
+ break;
+
+ case 'R':
+ if (!State.RootStore.addSelfSignedRootsFromSystem(&StaticErrInfo))
+ return RTEXITCODE_FAILURE;
+ break;
+
+ case 't':
+ if (!strcmp(ValueUnion.psz, "win") || !strcmp(ValueUnion.psz, "windows"))
+ State.enmSignType = VERIFYEXESTATE::kSignType_Windows;
+ else if (!strcmp(ValueUnion.psz, "osx") || !strcmp(ValueUnion.psz, "apple"))
+ State.enmSignType = VERIFYEXESTATE::kSignType_OSX;
+ else
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown signing type: '%s'", ValueUnion.psz);
+ break;
+
+ case 'T':
+ if (!RTTimeSpecFromString(&State.ValidationTime, ValueUnion.psz))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid validation time (%s): %Rrc", ValueUnion.psz, rc);
+ break;
+
+ case 'k': State.fKernel = true; break;
+ case 'v': State.cVerbose++; break;
+ case 'q': State.cVerbose = 0; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpVerifyExe(g_pStdOut, RTSIGNTOOLHELP_FULL);
+ default: return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (ch != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given.");
+
+ /*
+ * Populate the certificate stores according to the signing type.
+ */
+# ifdef VBOX
+ unsigned cSets = 0;
+ struct STSTORESET aSets[6];
+ switch (State.enmSignType)
+ {
+ case VERIFYEXESTATE::kSignType_Windows:
+ aSets[cSets].hStore = State.RootStore.m_hStore;
+ aSets[cSets].paTAs = g_aSUPTimestampTAs;
+ aSets[cSets].cTAs = g_cSUPTimestampTAs;
+ cSets++;
+ aSets[cSets].hStore = State.RootStore.m_hStore;
+ aSets[cSets].paTAs = g_aSUPSpcRootTAs;
+ aSets[cSets].cTAs = g_cSUPSpcRootTAs;
+ cSets++;
+ aSets[cSets].hStore = State.RootStore.m_hStore;
+ aSets[cSets].paTAs = g_aSUPNtKernelRootTAs;
+ aSets[cSets].cTAs = g_cSUPNtKernelRootTAs;
+ cSets++;
+ aSets[cSets].hStore = State.KernelRootStore.m_hStore;
+ aSets[cSets].paTAs = g_aSUPNtKernelRootTAs;
+ aSets[cSets].cTAs = g_cSUPNtKernelRootTAs;
+ cSets++;
+ break;
+
+ case VERIFYEXESTATE::kSignType_OSX:
+ aSets[cSets].hStore = State.RootStore.m_hStore;
+ aSets[cSets].paTAs = g_aSUPAppleRootTAs;
+ aSets[cSets].cTAs = g_cSUPAppleRootTAs;
+ cSets++;
+ break;
+ }
+ for (unsigned i = 0; i < cSets; i++)
+ for (unsigned j = 0; j < aSets[i].cTAs; j++)
+ {
+ rc = RTCrStoreCertAddEncoded(aSets[i].hStore, RTCRCERTCTX_F_ENC_TAF_DER, aSets[i].paTAs[j].pch,
+ aSets[i].paTAs[j].cb, RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTCrStoreCertAddEncoded failed (%u/%u): %s",
+ i, j, StaticErrInfo.szMsg);
+ }
+# endif /* VBOX */
+
+ /*
+ * Do it.
+ */
+ RTEXITCODE rcExit;
+ for (;;)
+ {
+ rcExit = HandleVerifyExeWorker(&State, ValueUnion.psz, &StaticErrInfo);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+
+ /*
+ * Next file
+ */
+ ch = RTGetOpt(&GetState, &ValueUnion);
+ if (ch == 0)
+ break;
+ if (ch != VINF_GETOPT_NOT_OPTION)
+ {
+ rcExit = RTGetOptPrintError(ch, &ValueUnion);
+ break;
+ }
+ }
+
+ return rcExit;
+}
+
+#endif /* !IPRT_IN_BUILD_TOOL */
+
+/*
+ * common code for show-exe and show-cat:
+ */
+
+/**
+ * Display an object ID.
+ *
+ * @returns IPRT status code.
+ * @param pThis The show exe instance data.
+ * @param pObjId The object ID to display.
+ * @param pszLabel The field label (prefixed by szPrefix).
+ * @param pszPost What to print after the ID (typically newline).
+ */
+static void HandleShowExeWorkerDisplayObjId(PSHOWEXEPKCS7 pThis, PCRTASN1OBJID pObjId, const char *pszLabel, const char *pszPost)
+{
+ int rc = RTAsn1QueryObjIdName(pObjId, pThis->szTmp, sizeof(pThis->szTmp));
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->cVerbosity > 1)
+ RTPrintf("%s%s%s (%s)%s", pThis->szPrefix, pszLabel, pThis->szTmp, pObjId->szObjId, pszPost);
+ else
+ RTPrintf("%s%s%s%s", pThis->szPrefix, pszLabel, pThis->szTmp, pszPost);
+ }
+ else
+ RTPrintf("%s%s%s%s", pThis->szPrefix, pszLabel, pObjId->szObjId, pszPost);
+}
+
+
+/**
+ * Display an object ID, without prefix and label
+ *
+ * @returns IPRT status code.
+ * @param pThis The show exe instance data.
+ * @param pObjId The object ID to display.
+ * @param pszPost What to print after the ID (typically newline).
+ */
+static void HandleShowExeWorkerDisplayObjIdSimple(PSHOWEXEPKCS7 pThis, PCRTASN1OBJID pObjId, const char *pszPost)
+{
+ int rc = RTAsn1QueryObjIdName(pObjId, pThis->szTmp, sizeof(pThis->szTmp));
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->cVerbosity > 1)
+ RTPrintf("%s (%s)%s", pThis->szTmp, pObjId->szObjId, pszPost);
+ else
+ RTPrintf("%s%s", pThis->szTmp, pszPost);
+ }
+ else
+ RTPrintf("%s%s", pObjId->szObjId, pszPost);
+}
+
+
+/**
+ * Display a signer info attribute.
+ *
+ * @returns IPRT status code.
+ * @param pThis The show exe instance data.
+ * @param offPrefix The current prefix offset.
+ * @param pAttr The attribute to display.
+ */
+static int HandleShowExeWorkerPkcs7DisplayAttrib(PSHOWEXEPKCS7 pThis, size_t offPrefix, PCRTCRPKCS7ATTRIBUTE pAttr)
+{
+ HandleShowExeWorkerDisplayObjId(pThis, &pAttr->Type, "", ":\n");
+ if (pThis->cVerbosity > 4 && pAttr->SeqCore.Asn1Core.uData.pu8)
+ RTPrintf("%s uData.pu8=%p cb=%#x\n", pThis->szPrefix, pAttr->SeqCore.Asn1Core.uData.pu8, pAttr->SeqCore.Asn1Core.cb);
+
+ int rc = VINF_SUCCESS;
+ switch (pAttr->enmType)
+ {
+ case RTCRPKCS7ATTRIBUTETYPE_UNKNOWN:
+ if (pAttr->uValues.pCores->cItems <= 1)
+ RTPrintf("%s %u bytes\n", pThis->szPrefix,pAttr->uValues.pCores->SetCore.Asn1Core.cb);
+ else
+ RTPrintf("%s %u bytes divided by %u items\n", pThis->szPrefix, pAttr->uValues.pCores->SetCore.Asn1Core.cb, pAttr->uValues.pCores->cItems);
+ break;
+
+ /* Object IDs, use pObjIds. */
+ case RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS:
+ if (pAttr->uValues.pObjIds->cItems != 1)
+ RTPrintf("%s%u object IDs:", pThis->szPrefix, pAttr->uValues.pObjIds->cItems);
+ for (unsigned i = 0; i < pAttr->uValues.pObjIds->cItems; i++)
+ {
+ if (pAttr->uValues.pObjIds->cItems == 1)
+ RTPrintf("%s ", pThis->szPrefix);
+ else
+ RTPrintf("%s ObjId[%u]: ", pThis->szPrefix, i);
+ HandleShowExeWorkerDisplayObjIdSimple(pThis, pAttr->uValues.pObjIds->papItems[i], "\n");
+ }
+ break;
+
+ /* Sequence of object IDs, use pObjIdSeqs. */
+ case RTCRPKCS7ATTRIBUTETYPE_MS_STATEMENT_TYPE:
+ if (pAttr->uValues.pObjIdSeqs->cItems != 1)
+ RTPrintf("%s%u object IDs:", pThis->szPrefix, pAttr->uValues.pObjIdSeqs->cItems);
+ for (unsigned i = 0; i < pAttr->uValues.pObjIdSeqs->cItems; i++)
+ {
+ uint32_t const cObjIds = pAttr->uValues.pObjIdSeqs->papItems[i]->cItems;
+ for (unsigned j = 0; j < cObjIds; j++)
+ {
+ if (pAttr->uValues.pObjIdSeqs->cItems == 1)
+ RTPrintf("%s ", pThis->szPrefix);
+ else
+ RTPrintf("%s ObjIdSeq[%u]: ", pThis->szPrefix, i);
+ if (cObjIds != 1)
+ RTPrintf(" ObjId[%u]: ", j);
+ HandleShowExeWorkerDisplayObjIdSimple(pThis, pAttr->uValues.pObjIdSeqs->papItems[i]->papItems[i], "\n");
+ }
+ }
+ break;
+
+ /* Octet strings, use pOctetStrings. */
+ case RTCRPKCS7ATTRIBUTETYPE_OCTET_STRINGS:
+ if (pAttr->uValues.pOctetStrings->cItems != 1)
+ RTPrintf("%s%u octet strings:", pThis->szPrefix, pAttr->uValues.pOctetStrings->cItems);
+ for (unsigned i = 0; i < pAttr->uValues.pOctetStrings->cItems; i++)
+ {
+ PCRTASN1OCTETSTRING pOctetString = pAttr->uValues.pOctetStrings->papItems[i];
+ uint32_t cbContent = pOctetString->Asn1Core.cb;
+ if (cbContent > 0 && (cbContent <= 128 || pThis->cVerbosity >= 2))
+ {
+ uint8_t const *pbContent = pOctetString->Asn1Core.uData.pu8;
+ uint32_t off = 0;
+ while (off < cbContent)
+ {
+ uint32_t cbNow = RT_MIN(cbContent - off, 16);
+ if (pAttr->uValues.pOctetStrings->cItems == 1)
+ RTPrintf("%s %#06x: %.*Rhxs\n", pThis->szPrefix, off, cbNow, &pbContent[off]);
+ else
+ RTPrintf("%s OctetString[%u]: %#06x: %.*Rhxs\n", pThis->szPrefix, i, off, cbNow, &pbContent[off]);
+ off += cbNow;
+ }
+ }
+ else
+ RTPrintf("%s: OctetString[%u]: %u bytes\n", pThis->szPrefix, i, pOctetString->Asn1Core.cb);
+ }
+ break;
+
+ /* Counter signatures (PKCS \#9), use pCounterSignatures. */
+ case RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES:
+ RTPrintf("%s%u counter signatures, %u bytes in total\n", pThis->szPrefix,
+ pAttr->uValues.pCounterSignatures->cItems, pAttr->uValues.pCounterSignatures->SetCore.Asn1Core.cb);
+ for (uint32_t i = 0; i < pAttr->uValues.pCounterSignatures->cItems; i++)
+ {
+ size_t offPrefix2 = offPrefix;
+ if (pAttr->uValues.pContentInfos->cItems > 1)
+ offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, "CounterSig[%u]: ", i);
+ else
+ offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, " ");
+
+ int rc2 = HandleShowExeWorkerPkcs7DisplaySignerInfo(pThis, offPrefix2,
+ pAttr->uValues.pCounterSignatures->papItems[i]);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ break;
+
+ /* Signing time (PKCS \#9), use pSigningTime. */
+ case RTCRPKCS7ATTRIBUTETYPE_SIGNING_TIME:
+ for (uint32_t i = 0; i < pAttr->uValues.pSigningTime->cItems; i++)
+ {
+ PCRTASN1TIME pTime = pAttr->uValues.pSigningTime->papItems[i];
+ char szTS[RTTIME_STR_LEN];
+ RTTimeToString(&pTime->Time, szTS, sizeof(szTS));
+ if (pAttr->uValues.pSigningTime->cItems == 1)
+ RTPrintf("%s %s (%.*s)\n", pThis->szPrefix, szTS, pTime->Asn1Core.cb, pTime->Asn1Core.uData.pch);
+ else
+ RTPrintf("%s #%u: %s (%.*s)\n", pThis->szPrefix, i, szTS, pTime->Asn1Core.cb, pTime->Asn1Core.uData.pch);
+ }
+ break;
+
+ /* Microsoft timestamp info (RFC-3161) signed data, use pContentInfo. */
+ case RTCRPKCS7ATTRIBUTETYPE_MS_TIMESTAMP:
+ case RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE:
+ if (pAttr->uValues.pContentInfos->cItems > 1)
+ RTPrintf("%s%u nested signatures, %u bytes in total\n", pThis->szPrefix,
+ pAttr->uValues.pContentInfos->cItems, pAttr->uValues.pContentInfos->SetCore.Asn1Core.cb);
+ for (unsigned i = 0; i < pAttr->uValues.pContentInfos->cItems; i++)
+ {
+ size_t offPrefix2 = offPrefix;
+ if (pAttr->uValues.pContentInfos->cItems > 1)
+ offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, "NestedSig[%u]: ", i);
+ else
+ offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, " ");
+ // offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, "NestedSig: ", i);
+ PCRTCRPKCS7CONTENTINFO pContentInfo = pAttr->uValues.pContentInfos->papItems[i];
+ int rc2;
+ if (RTCrPkcs7ContentInfo_IsSignedData(pContentInfo))
+ rc2 = HandleShowExeWorkerPkcs7Display(pThis, pContentInfo->u.pSignedData, offPrefix2, pContentInfo);
+ else
+ rc2 = RTMsgErrorRc(VERR_ASN1_UNEXPECTED_OBJ_ID, "%sPKCS#7 content in nested signature is not 'signedData': %s",
+ pThis->szPrefix, pContentInfo->ContentType.szObjId);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ break;
+
+ case RTCRPKCS7ATTRIBUTETYPE_APPLE_MULTI_CD_PLIST:
+ if (pAttr->uValues.pContentInfos->cItems != 1)
+ RTPrintf("%s%u plists, expected only 1.\n", pThis->szPrefix, pAttr->uValues.pOctetStrings->cItems);
+ for (unsigned i = 0; i < pAttr->uValues.pOctetStrings->cItems; i++)
+ {
+ PCRTASN1OCTETSTRING pOctetString = pAttr->uValues.pOctetStrings->papItems[i];
+ size_t cbContent = pOctetString->Asn1Core.cb;
+ char const *pchContent = pOctetString->Asn1Core.uData.pch;
+ rc = RTStrValidateEncodingEx(pchContent, cbContent, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(rc))
+ {
+ while (cbContent > 0)
+ {
+ const char *pchNewLine = (const char *)memchr(pchContent, '\n', cbContent);
+ size_t cchToWrite = pchNewLine ? pchNewLine - pchContent : cbContent;
+ if (pAttr->uValues.pOctetStrings->cItems == 1)
+ RTPrintf("%s %.*s\n", pThis->szPrefix, cchToWrite, pchContent);
+ else
+ RTPrintf("%s plist[%u]: %.*s\n", pThis->szPrefix, i, cchToWrite, pchContent);
+ if (!pchNewLine)
+ break;
+ pchContent = pchNewLine + 1;
+ cbContent -= cchToWrite + 1;
+ }
+ }
+ else
+ {
+ if (pAttr->uValues.pContentInfos->cItems != 1)
+ RTPrintf("%s: plist[%u]: Invalid UTF-8: %Rrc\n", pThis->szPrefix, i, rc);
+ else
+ RTPrintf("%s: Invalid UTF-8: %Rrc\n", pThis->szPrefix, rc);
+ for (uint32_t off = 0; off < cbContent; off += 16)
+ {
+ size_t cbNow = RT_MIN(cbContent - off, 16);
+ if (pAttr->uValues.pOctetStrings->cItems == 1)
+ RTPrintf("%s %#06x: %.*Rhxs\n", pThis->szPrefix, off, cbNow, &pchContent[off]);
+ else
+ RTPrintf("%s plist[%u]: %#06x: %.*Rhxs\n", pThis->szPrefix, i, off, cbNow, &pchContent[off]);
+ }
+ }
+ }
+ break;
+
+ case RTCRPKCS7ATTRIBUTETYPE_INVALID:
+ RTPrintf("%sINVALID!\n", pThis->szPrefix);
+ break;
+ case RTCRPKCS7ATTRIBUTETYPE_NOT_PRESENT:
+ RTPrintf("%sNOT PRESENT!\n", pThis->szPrefix);
+ break;
+ default:
+ RTPrintf("%senmType=%d!\n", pThis->szPrefix, pAttr->enmType);
+ break;
+ }
+ return rc;
+}
+
+
+/**
+ * Displays a SignerInfo structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The show exe instance data.
+ * @param offPrefix The current prefix offset.
+ * @param pSignerInfo The structure to display.
+ */
+static int HandleShowExeWorkerPkcs7DisplaySignerInfo(PSHOWEXEPKCS7 pThis, size_t offPrefix, PCRTCRPKCS7SIGNERINFO pSignerInfo)
+{
+ int rc = RTAsn1Integer_ToString(&pSignerInfo->IssuerAndSerialNumber.SerialNumber,
+ pThis->szTmp, sizeof(pThis->szTmp), 0 /*fFlags*/, NULL);
+ if (RT_FAILURE(rc))
+ RTStrPrintf(pThis->szTmp, sizeof(pThis->szTmp), "%Rrc", rc);
+ RTPrintf("%s Serial No: %s\n", pThis->szPrefix, pThis->szTmp);
+
+ rc = RTCrX509Name_FormatAsString(&pSignerInfo->IssuerAndSerialNumber.Name, pThis->szTmp, sizeof(pThis->szTmp), NULL);
+ if (RT_FAILURE(rc))
+ RTStrPrintf(pThis->szTmp, sizeof(pThis->szTmp), "%Rrc", rc);
+ RTPrintf("%s Issuer: %s\n", pThis->szPrefix, pThis->szTmp);
+
+ const char *pszType = RTCrDigestTypeToName(RTCrX509AlgorithmIdentifier_GetDigestType(&pSignerInfo->DigestAlgorithm,
+ true /*fPureDigestsOnly*/));
+ if (!pszType)
+ pszType = pSignerInfo->DigestAlgorithm.Algorithm.szObjId;
+ RTPrintf("%s Digest Algorithm: %s", pThis->szPrefix, pszType);
+ if (pThis->cVerbosity > 1)
+ RTPrintf(" (%s)\n", pSignerInfo->DigestAlgorithm.Algorithm.szObjId);
+ else
+ RTPrintf("\n");
+
+ HandleShowExeWorkerDisplayObjId(pThis, &pSignerInfo->DigestEncryptionAlgorithm.Algorithm,
+ "Digest Encryption Algorithm: ", "\n");
+
+ if (pSignerInfo->AuthenticatedAttributes.cItems == 0)
+ RTPrintf("%s Authenticated Attributes: none\n", pThis->szPrefix);
+ else
+ {
+ RTPrintf("%s Authenticated Attributes: %u item%s\n", pThis->szPrefix,
+ pSignerInfo->AuthenticatedAttributes.cItems, pSignerInfo->AuthenticatedAttributes.cItems > 1 ? "s" : "");
+ for (unsigned j = 0; j < pSignerInfo->AuthenticatedAttributes.cItems; j++)
+ {
+ PRTCRPKCS7ATTRIBUTE pAttr = pSignerInfo->AuthenticatedAttributes.papItems[j];
+ size_t offPrefix3 = offPrefix+ RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix,
+ " AuthAttrib[%u]: ", j);
+ HandleShowExeWorkerPkcs7DisplayAttrib(pThis, offPrefix3, pAttr);
+ }
+ pThis->szPrefix[offPrefix] = '\0';
+ }
+
+ if (pSignerInfo->UnauthenticatedAttributes.cItems == 0)
+ RTPrintf("%s Unauthenticated Attributes: none\n", pThis->szPrefix);
+ else
+ {
+ RTPrintf("%s Unauthenticated Attributes: %u item%s\n", pThis->szPrefix,
+ pSignerInfo->UnauthenticatedAttributes.cItems, pSignerInfo->UnauthenticatedAttributes.cItems > 1 ? "s" : "");
+ for (unsigned j = 0; j < pSignerInfo->UnauthenticatedAttributes.cItems; j++)
+ {
+ PRTCRPKCS7ATTRIBUTE pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[j];
+ size_t offPrefix3 = offPrefix + RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix,
+ " UnauthAttrib[%u]: ", j);
+ HandleShowExeWorkerPkcs7DisplayAttrib(pThis, offPrefix3, pAttr);
+ }
+ pThis->szPrefix[offPrefix] = '\0';
+ }
+
+ /** @todo show the encrypted stuff (EncryptedDigest)? */
+ return rc;
+}
+
+
+/**
+ * Displays a Microsoft SPC indirect data structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The show exe instance data.
+ * @param offPrefix The current prefix offset.
+ * @param pIndData The indirect data to display.
+ */
+static int HandleShowExeWorkerPkcs7DisplaySpcIdirectDataContent(PSHOWEXEPKCS7 pThis, size_t offPrefix,
+ PCRTCRSPCINDIRECTDATACONTENT pIndData)
+{
+ /*
+ * The image hash.
+ */
+ RTDIGESTTYPE const enmDigestType = RTCrX509AlgorithmIdentifier_GetDigestType(&pIndData->DigestInfo.DigestAlgorithm,
+ true /*fPureDigestsOnly*/);
+ const char *pszDigestType = RTCrDigestTypeToName(enmDigestType);
+ RTPrintf("%s Digest Type: %s", pThis->szPrefix, pszDigestType);
+ if (pThis->cVerbosity > 1)
+ RTPrintf(" (%s)\n", pIndData->DigestInfo.DigestAlgorithm.Algorithm.szObjId);
+ else
+ RTPrintf("\n");
+ RTPrintf("%s Digest: %.*Rhxs\n",
+ pThis->szPrefix, pIndData->DigestInfo.Digest.Asn1Core.cb, pIndData->DigestInfo.Digest.Asn1Core.uData.pu8);
+
+ /*
+ * The data/file/url.
+ */
+ switch (pIndData->Data.enmType)
+ {
+ case RTCRSPCAAOVTYPE_PE_IMAGE_DATA:
+ {
+ RTPrintf("%s Data Type: PE Image Data\n", pThis->szPrefix);
+ PRTCRSPCPEIMAGEDATA pPeImage = pIndData->Data.uValue.pPeImage;
+ /** @todo display "Flags". */
+
+ switch (pPeImage->T0.File.enmChoice)
+ {
+ case RTCRSPCLINKCHOICE_MONIKER:
+ {
+ PRTCRSPCSERIALIZEDOBJECT pMoniker = pPeImage->T0.File.u.pMoniker;
+ if (RTCrSpcSerializedObject_IsPresent(pMoniker))
+ {
+ if (RTUuidCompareStr(pMoniker->Uuid.Asn1Core.uData.pUuid, RTCRSPCSERIALIZEDOBJECT_UUID_STR) == 0)
+ {
+ RTPrintf("%s Moniker: SpcSerializedObject (%RTuuid)\n",
+ pThis->szPrefix, pMoniker->Uuid.Asn1Core.uData.pUuid);
+
+ PCRTCRSPCSERIALIZEDOBJECTATTRIBUTES pData = pMoniker->u.pData;
+ if (pData)
+ for (uint32_t i = 0; i < pData->cItems; i++)
+ {
+ RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix,
+ "MonikerAttrib[%u]: ", i);
+
+ switch (pData->papItems[i]->enmType)
+ {
+ case RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V2:
+ case RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1:
+ {
+ PCRTCRSPCSERIALIZEDPAGEHASHES pPgHashes = pData->papItems[i]->u.pPageHashes;
+ uint32_t const cbHash = pData->papItems[i]->enmType
+ == RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1
+ ? 160/8 /*SHA-1*/ : 256/8 /*SHA-256*/;
+ uint32_t const cPages = pPgHashes->RawData.Asn1Core.cb / (cbHash + sizeof(uint32_t));
+
+ RTPrintf("%sPage Hashes version %u - %u pages (%u bytes total)\n", pThis->szPrefix,
+ pData->papItems[i]->enmType
+ == RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1 ? 1 : 2,
+ cPages, pPgHashes->RawData.Asn1Core.cb);
+ if (pThis->cVerbosity > 0)
+ {
+ PCRTCRSPCPEIMAGEPAGEHASHES pPg = pPgHashes->pData;
+ for (unsigned iPg = 0; iPg < cPages; iPg++)
+ {
+ uint32_t offHash = 0;
+ do
+ {
+ if (offHash == 0)
+ RTPrintf("%.*s Page#%04u/%#08x: ",
+ offPrefix, pThis->szPrefix, iPg, pPg->Generic.offFile);
+ else
+ RTPrintf("%.*s ", offPrefix, pThis->szPrefix);
+ uint32_t cbLeft = cbHash - offHash;
+ if (cbLeft > 24)
+ cbLeft = 16;
+ RTPrintf("%.*Rhxs\n", cbLeft, &pPg->Generic.abHash[offHash]);
+ offHash += cbLeft;
+ } while (offHash < cbHash);
+ pPg = (PCRTCRSPCPEIMAGEPAGEHASHES)&pPg->Generic.abHash[cbHash];
+ }
+
+ if (pThis->cVerbosity > 3)
+ RTPrintf("%.*Rhxd\n",
+ pPgHashes->RawData.Asn1Core.cb,
+ pPgHashes->RawData.Asn1Core.uData.pu8);
+ }
+ break;
+ }
+
+ case RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_UNKNOWN:
+ HandleShowExeWorkerDisplayObjIdSimple(pThis, &pData->papItems[i]->Type, "\n");
+ break;
+ case RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_NOT_PRESENT:
+ RTPrintf("%sNot present!\n", pThis->szPrefix);
+ break;
+ default:
+ RTPrintf("%senmType=%d!\n", pThis->szPrefix, pData->papItems[i]->enmType);
+ break;
+ }
+ pThis->szPrefix[offPrefix] = '\0';
+ }
+ else
+ RTPrintf("%s pData is NULL!\n", pThis->szPrefix);
+ }
+ else
+ RTPrintf("%s Moniker: Unknown UUID: %RTuuid\n",
+ pThis->szPrefix, pMoniker->Uuid.Asn1Core.uData.pUuid);
+ }
+ else
+ RTPrintf("%s Moniker: not present\n", pThis->szPrefix);
+ break;
+ }
+
+ case RTCRSPCLINKCHOICE_URL:
+ {
+ const char *pszUrl = NULL;
+ int rc = pPeImage->T0.File.u.pUrl
+ ? RTAsn1String_QueryUtf8(pPeImage->T0.File.u.pUrl, &pszUrl, NULL)
+ : VERR_NOT_FOUND;
+ if (RT_SUCCESS(rc))
+ RTPrintf("%s URL: '%s'\n", pThis->szPrefix, pszUrl);
+ else
+ RTPrintf("%s URL: rc=%Rrc\n", pThis->szPrefix, rc);
+ break;
+ }
+
+ case RTCRSPCLINKCHOICE_FILE:
+ {
+ const char *pszFile = NULL;
+ int rc = pPeImage->T0.File.u.pT2 && pPeImage->T0.File.u.pT2->File.u.pAscii
+ ? RTAsn1String_QueryUtf8(pPeImage->T0.File.u.pT2->File.u.pAscii, &pszFile, NULL)
+ : VERR_NOT_FOUND;
+ if (RT_SUCCESS(rc))
+ RTPrintf("%s File: '%s'\n", pThis->szPrefix, pszFile);
+ else
+ RTPrintf("%s File: rc=%Rrc\n", pThis->szPrefix, rc);
+ if (pThis->cVerbosity > 4 && pPeImage->T0.File.u.pT2 == NULL)
+ RTPrintf("%s pT2=NULL\n", pThis->szPrefix);
+ else if (pThis->cVerbosity > 4)
+ {
+ PCRTASN1STRING pStr = pPeImage->T0.File.u.pT2->File.u.pAscii;
+ RTPrintf("%s pT2=%p/%p LB %#x fFlags=%#x pOps=%p (%s)\n"
+ "%s enmChoice=%d pStr=%p/%p LB %#x fFlags=%#x\n",
+ pThis->szPrefix,
+ pPeImage->T0.File.u.pT2,
+ pPeImage->T0.File.u.pT2->CtxTag2.Asn1Core.uData.pu8,
+ pPeImage->T0.File.u.pT2->CtxTag2.Asn1Core.cb,
+ pPeImage->T0.File.u.pT2->CtxTag2.Asn1Core.fFlags,
+ pPeImage->T0.File.u.pT2->CtxTag2.Asn1Core.pOps,
+ pPeImage->T0.File.u.pT2->CtxTag2.Asn1Core.pOps
+ ? pPeImage->T0.File.u.pT2->CtxTag2.Asn1Core.pOps->pszName : "",
+ pThis->szPrefix,
+ pPeImage->T0.File.u.pT2->File.enmChoice,
+ pStr,
+ pStr ? pStr->Asn1Core.uData.pu8 : NULL,
+ pStr ? pStr->Asn1Core.cb : 0,
+ pStr ? pStr->Asn1Core.fFlags : 0);
+ }
+ break;
+ }
+
+ case RTCRSPCLINKCHOICE_NOT_PRESENT:
+ RTPrintf("%s File not present!\n", pThis->szPrefix);
+ break;
+ default:
+ RTPrintf("%s enmChoice=%d!\n", pThis->szPrefix, pPeImage->T0.File.enmChoice);
+ break;
+ }
+ break;
+ }
+
+ case RTCRSPCAAOVTYPE_UNKNOWN:
+ HandleShowExeWorkerDisplayObjId(pThis, &pIndData->Data.Type, " Data Type: ", "\n");
+ break;
+ case RTCRSPCAAOVTYPE_NOT_PRESENT:
+ RTPrintf("%s Data Type: Not present!\n", pThis->szPrefix);
+ break;
+ default:
+ RTPrintf("%s Data Type: enmType=%d!\n", pThis->szPrefix, pIndData->Data.enmType);
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Display an PKCS#7 signed data instance.
+ *
+ * @returns IPRT status code.
+ * @param pThis The show exe instance data.
+ * @param pSignedData The signed data to display.
+ * @param offPrefix The current prefix offset.
+ * @param pContentInfo The content info structure (for the size).
+ */
+static int HandleShowExeWorkerPkcs7Display(PSHOWEXEPKCS7 pThis, PRTCRPKCS7SIGNEDDATA pSignedData, size_t offPrefix,
+ PCRTCRPKCS7CONTENTINFO pContentInfo)
+{
+ pThis->szPrefix[offPrefix] = '\0';
+ RTPrintf("%sPKCS#7 signature: %u (%#x) bytes\n", pThis->szPrefix,
+ RTASN1CORE_GET_RAW_ASN1_SIZE(&pContentInfo->SeqCore.Asn1Core),
+ RTASN1CORE_GET_RAW_ASN1_SIZE(&pContentInfo->SeqCore.Asn1Core));
+
+ /*
+ * Display list of signing algorithms.
+ */
+ RTPrintf("%sDigestAlgorithms: ", pThis->szPrefix);
+ if (pSignedData->DigestAlgorithms.cItems == 0)
+ RTPrintf("none");
+ for (unsigned i = 0; i < pSignedData->DigestAlgorithms.cItems; i++)
+ {
+ PCRTCRX509ALGORITHMIDENTIFIER pAlgoId = pSignedData->DigestAlgorithms.papItems[i];
+ const char *pszDigestType = RTCrDigestTypeToName(RTCrX509AlgorithmIdentifier_GetDigestType(pAlgoId,
+ true /*fPureDigestsOnly*/));
+ if (!pszDigestType)
+ pszDigestType = pAlgoId->Algorithm.szObjId;
+ RTPrintf(i == 0 ? "%s" : ", %s", pszDigestType);
+ if (pThis->cVerbosity > 1)
+ RTPrintf(" (%s)", pAlgoId->Algorithm.szObjId);
+ }
+ RTPrintf("\n");
+
+ /*
+ * Display the signed data content.
+ */
+ if (RTAsn1ObjId_CompareWithString(&pSignedData->ContentInfo.ContentType, RTCRSPCINDIRECTDATACONTENT_OID) == 0)
+ {
+ RTPrintf("%s ContentType: SpcIndirectDataContent (" RTCRSPCINDIRECTDATACONTENT_OID ")\n", pThis->szPrefix);
+ size_t offPrefix2 = RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, " SPC Ind Data: ");
+ HandleShowExeWorkerPkcs7DisplaySpcIdirectDataContent(pThis, offPrefix2 + offPrefix,
+ pSignedData->ContentInfo.u.pIndirectDataContent);
+ pThis->szPrefix[offPrefix] = '\0';
+ }
+ else
+ {
+ HandleShowExeWorkerDisplayObjId(pThis, &pSignedData->ContentInfo.ContentType, " ContentType: ", " - not implemented.\n");
+ RTPrintf("%s %u (%#x) bytes\n", pThis->szPrefix,
+ pSignedData->ContentInfo.Content.Asn1Core.cb, pSignedData->ContentInfo.Content.Asn1Core.cb);
+ }
+
+ /*
+ * Display certificates (Certificates).
+ */
+ if (pSignedData->Certificates.cItems > 0)
+ {
+ RTPrintf("%s Certificates: %u\n", pThis->szPrefix, pSignedData->Certificates.cItems);
+ for (uint32_t i = 0; i < pSignedData->Certificates.cItems; i++)
+ {
+ PCRTCRPKCS7CERT pCert = pSignedData->Certificates.papItems[i];
+ if (i != 0 && pThis->cVerbosity >= 2)
+ RTPrintf("\n");
+ switch (pCert->enmChoice)
+ {
+ case RTCRPKCS7CERTCHOICE_X509:
+ {
+ PCRTCRX509CERTIFICATE pX509Cert = pCert->u.pX509Cert;
+ int rc2 = RTAsn1QueryObjIdName(&pX509Cert->SignatureAlgorithm.Algorithm, pThis->szTmp, sizeof(pThis->szTmp));
+ RTPrintf("%s Certificate #%u: %s\n", pThis->szPrefix, i,
+ RT_SUCCESS(rc2) ? pThis->szTmp : pX509Cert->SignatureAlgorithm.Algorithm.szObjId);
+
+ rc2 = RTCrX509Name_FormatAsString(&pX509Cert->TbsCertificate.Subject,
+ pThis->szTmp, sizeof(pThis->szTmp), NULL);
+ if (RT_FAILURE(rc2))
+ RTStrPrintf(pThis->szTmp, sizeof(pThis->szTmp), "%Rrc", rc2);
+ RTPrintf("%s Subject: %s\n", pThis->szPrefix, pThis->szTmp);
+
+ rc2 = RTCrX509Name_FormatAsString(&pX509Cert->TbsCertificate.Issuer,
+ pThis->szTmp, sizeof(pThis->szTmp), NULL);
+ if (RT_FAILURE(rc2))
+ RTStrPrintf(pThis->szTmp, sizeof(pThis->szTmp), "%Rrc", rc2);
+ RTPrintf("%s Issuer: %s\n", pThis->szPrefix, pThis->szTmp);
+
+
+ char szNotAfter[RTTIME_STR_LEN];
+ RTPrintf("%s Valid: %s thru %s\n", pThis->szPrefix,
+ RTTimeToString(&pX509Cert->TbsCertificate.Validity.NotBefore.Time,
+ pThis->szTmp, sizeof(pThis->szTmp)),
+ RTTimeToString(&pX509Cert->TbsCertificate.Validity.NotAfter.Time,
+ szNotAfter, sizeof(szNotAfter)));
+ break;
+ }
+
+ default:
+ RTPrintf("%s Certificate #%u: Unsupported type\n", pThis->szPrefix, i);
+ break;
+ }
+
+
+ if (pThis->cVerbosity >= 2)
+ RTAsn1Dump(RTCrPkcs7Cert_GetAsn1Core(pSignedData->Certificates.papItems[i]), 0,
+ ((uint32_t)offPrefix + 9) / 2, RTStrmDumpPrintfV, g_pStdOut);
+ }
+
+ /** @todo display certificates properly. */
+ }
+
+ if (pSignedData->Crls.cb > 0)
+ RTPrintf("%s CRLs: %u bytes\n", pThis->szPrefix, pSignedData->Crls.cb);
+
+ /*
+ * Show signatures (SignerInfos).
+ */
+ unsigned const cSigInfos = pSignedData->SignerInfos.cItems;
+ if (cSigInfos != 1)
+ RTPrintf("%s SignerInfos: %u signers\n", pThis->szPrefix, cSigInfos);
+ else
+ RTPrintf("%s SignerInfos:\n", pThis->szPrefix);
+ int rc = VINF_SUCCESS;
+ for (unsigned i = 0; i < cSigInfos; i++)
+ {
+ size_t offPrefix2 = offPrefix;
+ if (cSigInfos != 1)
+ offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, "SignerInfo[%u]: ", i);
+
+ int rc2 = HandleShowExeWorkerPkcs7DisplaySignerInfo(pThis, offPrefix2, pSignedData->SignerInfos.papItems[i]);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ pThis->szPrefix[offPrefix] = '\0';
+
+ return rc;
+}
+
+
+/*
+ * The 'show-exe' command.
+ */
+static RTEXITCODE HelpShowExe(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "show-exe [--verbose|-v] [--quiet|-q] <exe1> [exe2 [..]]\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE HandleShowExe(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ };
+
+ unsigned cVerbosity = 0;
+ RTLDRARCH enmLdrArch = RTLDRARCH_WHATEVER;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) && ch != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (ch)
+ {
+ case 'v': cVerbosity++; break;
+ case 'q': cVerbosity = 0; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpShowExe(g_pStdOut, RTSIGNTOOLHELP_FULL);
+ default: return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (ch != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given.");
+
+ /*
+ * Do it.
+ */
+ unsigned iFile = 0;
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ do
+ {
+ RTPrintf(iFile == 0 ? "%s:\n" : "\n%s:\n", ValueUnion.psz);
+
+ SHOWEXEPKCS7 This;
+ RT_ZERO(This);
+ This.cVerbosity = cVerbosity;
+
+ RTEXITCODE rcExitThis = SignToolPkcs7Exe_InitFromFile(&This, ValueUnion.psz, cVerbosity, enmLdrArch);
+ if (rcExitThis == RTEXITCODE_SUCCESS)
+ {
+ rc = HandleShowExeWorkerPkcs7Display(&This, This.pSignedData, 0, &This.ContentInfo);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+ SignToolPkcs7Exe_Delete(&This);
+ }
+ if (rcExitThis != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS)
+ rcExit = rcExitThis;
+
+ iFile++;
+ } while ((ch = RTGetOpt(&GetState, &ValueUnion)) == VINF_GETOPT_NOT_OPTION);
+ if (ch != 0)
+ return RTGetOptPrintError(ch, &ValueUnion);
+
+ return rcExit;
+}
+
+
+/*
+ * The 'show-cat' command.
+ */
+static RTEXITCODE HelpShowCat(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "show-cat [--verbose|-v] [--quiet|-q] <cat1> [cat2 [..]]\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE HandleShowCat(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ };
+
+ unsigned cVerbosity = 0;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) && ch != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (ch)
+ {
+ case 'v': cVerbosity++; break;
+ case 'q': cVerbosity = 0; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpShowCat(g_pStdOut, RTSIGNTOOLHELP_FULL);
+ default: return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (ch != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given.");
+
+ /*
+ * Do it.
+ */
+ unsigned iFile = 0;
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ do
+ {
+ RTPrintf(iFile == 0 ? "%s:\n" : "\n%s:\n", ValueUnion.psz);
+
+ SHOWEXEPKCS7 This;
+ RT_ZERO(This);
+ This.cVerbosity = cVerbosity;
+
+ RTEXITCODE rcExitThis = SignToolPkcs7_InitFromFile(&This, ValueUnion.psz, cVerbosity);
+ if (rcExitThis == RTEXITCODE_SUCCESS)
+ {
+ This.hLdrMod = NIL_RTLDRMOD;
+
+ rc = HandleShowExeWorkerPkcs7Display(&This, This.pSignedData, 0, &This.ContentInfo);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+ SignToolPkcs7Exe_Delete(&This);
+ }
+ if (rcExitThis != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS)
+ rcExit = rcExitThis;
+
+ iFile++;
+ } while ((ch = RTGetOpt(&GetState, &ValueUnion)) == VINF_GETOPT_NOT_OPTION);
+ if (ch != 0)
+ return RTGetOptPrintError(ch, &ValueUnion);
+
+ return rcExit;
+}
+
+
+/*********************************************************************************************************************************
+* The 'hash-exe' command. *
+*********************************************************************************************************************************/
+static RTEXITCODE HelpHashExe(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "hash-exe [--verbose|-v] [--quiet|-q] <exe1> [exe2 [..]]\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE HandleHashExe(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ };
+
+ unsigned cVerbosity = 0;
+ RTLDRARCH enmLdrArch = RTLDRARCH_WHATEVER;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) && ch != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (ch)
+ {
+ case 'v': cVerbosity++; break;
+ case 'q': cVerbosity = 0; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpHashExe(g_pStdOut, RTSIGNTOOLHELP_FULL);
+ default: return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (ch != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given.");
+
+ /*
+ * Do it.
+ */
+ unsigned iFile = 0;
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ do
+ {
+ RTPrintf(iFile == 0 ? "%s:\n" : "\n%s:\n", ValueUnion.psz);
+
+ RTERRINFOSTATIC ErrInfo;
+ RTLDRMOD hLdrMod;
+ rc = RTLdrOpenEx(ValueUnion.psz, RTLDR_O_FOR_VALIDATION, enmLdrArch, &hLdrMod, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t abHash[RTSHA512_HASH_SIZE];
+ char szDigest[RTSHA512_DIGEST_LEN + 1];
+
+ /* SHA-1: */
+ rc = RTLdrHashImage(hLdrMod, RTDIGESTTYPE_SHA1, abHash, sizeof(abHash));
+ if (RT_SUCCESS(rc))
+ RTSha1ToString(abHash, szDigest, sizeof(szDigest));
+ else
+ RTStrPrintf(szDigest, sizeof(szDigest), "%Rrc", rc);
+ RTPrintf(" SHA-1: %s\n", szDigest);
+
+ /* SHA-256: */
+ rc = RTLdrHashImage(hLdrMod, RTDIGESTTYPE_SHA256, abHash, sizeof(abHash));
+ if (RT_SUCCESS(rc))
+ RTSha256ToString(abHash, szDigest, sizeof(szDigest));
+ else
+ RTStrPrintf(szDigest, sizeof(szDigest), "%Rrc", rc);
+ RTPrintf(" SHA-256: %s\n", szDigest);
+
+ /* SHA-512: */
+ rc = RTLdrHashImage(hLdrMod, RTDIGESTTYPE_SHA512, abHash, sizeof(abHash));
+ if (RT_SUCCESS(rc))
+ RTSha512ToString(abHash, szDigest, sizeof(szDigest));
+ else
+ RTStrPrintf(szDigest, sizeof(szDigest), "%Rrc", rc);
+ RTPrintf(" SHA-512: %s\n", szDigest);
+
+ RTLdrClose(hLdrMod);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("Failed to open '%s': %Rrc%#RTeim", ValueUnion.psz, rc, &ErrInfo.Core);
+
+ } while ((ch = RTGetOpt(&GetState, &ValueUnion)) == VINF_GETOPT_NOT_OPTION);
+ if (ch != 0)
+ return RTGetOptPrintError(ch, &ValueUnion);
+
+ return rcExit;
+}
+
+
+/*********************************************************************************************************************************
+* The 'make-tainfo' command. *
+*********************************************************************************************************************************/
+static RTEXITCODE HelpMakeTaInfo(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT,
+ "make-tainfo [--verbose|--quiet] [--cert <cert.der>] [-o|--output] <tainfo.der>\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+
+typedef struct MAKETAINFOSTATE
+{
+ int cVerbose;
+ const char *pszCert;
+ const char *pszOutput;
+} MAKETAINFOSTATE;
+
+
+/** @callback_method_impl{FNRTASN1ENCODEWRITER} */
+static DECLCALLBACK(int) handleMakeTaInfoWriter(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo)
+{
+ RT_NOREF_PV(pErrInfo);
+ return RTStrmWrite((PRTSTREAM)pvUser, pvBuf, cbToWrite);
+}
+
+
+static RTEXITCODE HandleMakeTaInfo(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--cert", 'c', RTGETOPT_REQ_STRING },
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ };
+
+ MAKETAINFOSTATE State = { 0, NULL, NULL };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ switch (ch)
+ {
+ case 'c':
+ if (State.pszCert)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "The --cert option can only be used once.");
+ State.pszCert = ValueUnion.psz;
+ break;
+
+ case 'o':
+ case VINF_GETOPT_NOT_OPTION:
+ if (State.pszOutput)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Multiple output files specified.");
+ State.pszOutput = ValueUnion.psz;
+ break;
+
+ case 'v': State.cVerbose++; break;
+ case 'q': State.cVerbose = 0; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpMakeTaInfo(g_pStdOut, RTSIGNTOOLHELP_FULL);
+ default: return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (!State.pszCert)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No input certificate was specified.");
+ if (!State.pszOutput)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No output file was specified.");
+
+ /*
+ * Read the certificate.
+ */
+ RTERRINFOSTATIC StaticErrInfo;
+ RTCRX509CERTIFICATE Certificate;
+ rc = RTCrX509Certificate_ReadFromFile(&Certificate, State.pszCert, 0, &g_RTAsn1DefaultAllocator,
+ RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error reading certificate from %s: %Rrc - %s",
+ State.pszCert, rc, StaticErrInfo.szMsg);
+ /*
+ * Construct the trust anchor information.
+ */
+ RTCRTAFTRUSTANCHORINFO TrustAnchor;
+ rc = RTCrTafTrustAnchorInfo_Init(&TrustAnchor, &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /* Public key. */
+ Assert(RTCrX509SubjectPublicKeyInfo_IsPresent(&TrustAnchor.PubKey));
+ RTCrX509SubjectPublicKeyInfo_Delete(&TrustAnchor.PubKey);
+ rc = RTCrX509SubjectPublicKeyInfo_Clone(&TrustAnchor.PubKey, &Certificate.TbsCertificate.SubjectPublicKeyInfo,
+ &g_RTAsn1DefaultAllocator);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTCrX509SubjectPublicKeyInfo_Clone failed: %Rrc", rc);
+ RTAsn1Core_ResetImplict(RTCrX509SubjectPublicKeyInfo_GetAsn1Core(&TrustAnchor.PubKey)); /* temporary hack. */
+
+ /* Key Identifier. */
+ PCRTASN1OCTETSTRING pKeyIdentifier = NULL;
+ if (Certificate.TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_SUBJECT_KEY_IDENTIFIER)
+ pKeyIdentifier = Certificate.TbsCertificate.T3.pSubjectKeyIdentifier;
+ else if ( (Certificate.TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_AUTHORITY_KEY_IDENTIFIER)
+ && RTCrX509Certificate_IsSelfSigned(&Certificate)
+ && RTAsn1OctetString_IsPresent(&Certificate.TbsCertificate.T3.pAuthorityKeyIdentifier->KeyIdentifier) )
+ pKeyIdentifier = &Certificate.TbsCertificate.T3.pAuthorityKeyIdentifier->KeyIdentifier;
+ else if ( (Certificate.TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_OLD_AUTHORITY_KEY_IDENTIFIER)
+ && RTCrX509Certificate_IsSelfSigned(&Certificate)
+ && RTAsn1OctetString_IsPresent(&Certificate.TbsCertificate.T3.pOldAuthorityKeyIdentifier->KeyIdentifier) )
+ pKeyIdentifier = &Certificate.TbsCertificate.T3.pOldAuthorityKeyIdentifier->KeyIdentifier;
+ if (pKeyIdentifier && pKeyIdentifier->Asn1Core.cb > 0)
+ {
+ Assert(RTAsn1OctetString_IsPresent(&TrustAnchor.KeyIdentifier));
+ RTAsn1OctetString_Delete(&TrustAnchor.KeyIdentifier);
+ rc = RTAsn1OctetString_Clone(&TrustAnchor.KeyIdentifier, pKeyIdentifier, &g_RTAsn1DefaultAllocator);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTAsn1OctetString_Clone failed: %Rrc", rc);
+ RTAsn1Core_ResetImplict(RTAsn1OctetString_GetAsn1Core(&TrustAnchor.KeyIdentifier)); /* temporary hack. */
+ }
+ else
+ RTMsgWarning("No key identifier found or has zero length.");
+
+ /* Subject */
+ if (RT_SUCCESS(rc))
+ {
+ Assert(!RTCrTafCertPathControls_IsPresent(&TrustAnchor.CertPath));
+ rc = RTCrTafCertPathControls_Init(&TrustAnchor.CertPath, &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(RTCrX509Name_IsPresent(&TrustAnchor.CertPath.TaName));
+ RTCrX509Name_Delete(&TrustAnchor.CertPath.TaName);
+ rc = RTCrX509Name_Clone(&TrustAnchor.CertPath.TaName, &Certificate.TbsCertificate.Subject,
+ &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ RTAsn1Core_ResetImplict(RTCrX509Name_GetAsn1Core(&TrustAnchor.CertPath.TaName)); /* temporary hack. */
+ rc = RTCrX509Name_RecodeAsUtf8(&TrustAnchor.CertPath.TaName, &g_RTAsn1DefaultAllocator);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTCrX509Name_RecodeAsUtf8 failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTCrX509Name_Clone failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTCrTafCertPathControls_Init failed: %Rrc", rc);
+ }
+
+ /* Check that what we've constructed makes some sense. */
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrTafTrustAnchorInfo_CheckSanity(&TrustAnchor, 0, RTErrInfoInitStatic(&StaticErrInfo), "TAI");
+ if (RT_FAILURE(rc))
+ RTMsgError("RTCrTafTrustAnchorInfo_CheckSanity failed: %Rrc - %s", rc, StaticErrInfo.szMsg);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Encode it and write it to the output file.
+ */
+ uint32_t cbEncoded;
+ rc = RTAsn1EncodePrepare(RTCrTafTrustAnchorInfo_GetAsn1Core(&TrustAnchor), RTASN1ENCODE_F_DER, &cbEncoded,
+ RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ if (State.cVerbose >= 1)
+ RTAsn1Dump(RTCrTafTrustAnchorInfo_GetAsn1Core(&TrustAnchor), 0, 0, RTStrmDumpPrintfV, g_pStdOut);
+
+ PRTSTREAM pStrm;
+ rc = RTStrmOpen(State.pszOutput, "wb", &pStrm);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1EncodeWrite(RTCrTafTrustAnchorInfo_GetAsn1Core(&TrustAnchor), RTASN1ENCODE_F_DER,
+ handleMakeTaInfoWriter, pStrm, RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrmClose(pStrm);
+ if (RT_SUCCESS(rc))
+ RTMsgInfo("Successfully wrote TrustedAnchorInfo to '%s'.", State.pszOutput);
+ else
+ RTMsgError("RTStrmClose failed: %Rrc", rc);
+ }
+ else
+ {
+ RTMsgError("RTAsn1EncodeWrite failed: %Rrc - %s", rc, StaticErrInfo.szMsg);
+ RTStrmClose(pStrm);
+ }
+ }
+ else
+ RTMsgError("Error opening '%s' for writing: %Rrcs", State.pszOutput, rc);
+ }
+ else
+ RTMsgError("RTAsn1EncodePrepare failed: %Rrc - %s", rc, StaticErrInfo.szMsg);
+ }
+
+ RTCrTafTrustAnchorInfo_Delete(&TrustAnchor);
+ }
+ else
+ RTMsgError("RTCrTafTrustAnchorInfo_Init failed: %Rrc", rc);
+
+ RTCrX509Certificate_Delete(&Certificate);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+
+/*
+ * The 'version' command.
+ */
+static RTEXITCODE HelpVersion(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmPrintf(pStrm, "version\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+static RTEXITCODE HandleVersion(int cArgs, char **papszArgs)
+{
+ RT_NOREF_PV(cArgs); RT_NOREF_PV(papszArgs);
+#ifndef IN_BLD_PROG /* RTBldCfgVersion or RTBldCfgRevision in build time IPRT lib. */
+ RTPrintf("%s\n", RTBldCfgVersion());
+ return RTEXITCODE_SUCCESS;
+#else
+ return RTEXITCODE_FAILURE;
+#endif
+}
+
+
+
+/**
+ * Command mapping.
+ */
+static struct
+{
+ /** The command. */
+ const char *pszCmd;
+ /**
+ * Handle the command.
+ * @returns Program exit code.
+ * @param cArgs Number of arguments.
+ * @param papszArgs The argument vector, starting with the command name.
+ */
+ RTEXITCODE (*pfnHandler)(int cArgs, char **papszArgs);
+ /**
+ * Produce help.
+ * @returns RTEXITCODE_SUCCESS to simplify handling '--help' in the handler.
+ * @param pStrm Where to send help text.
+ * @param enmLevel The level of the help information.
+ */
+ RTEXITCODE (*pfnHelp)(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel);
+}
+/** Mapping commands to handler and helper functions. */
+const g_aCommands[] =
+{
+ { "extract-exe-signer-cert", HandleExtractExeSignerCert, HelpExtractExeSignerCert },
+ { "extract-signer-root", HandleExtractSignerRoot, HelpExtractSignerRoot },
+ { "extract-timestamp-root", HandleExtractTimestampRoot, HelpExtractTimestampRoot },
+ { "extract-exe-signature", HandleExtractExeSignature, HelpExtractExeSignature },
+ { "add-nested-exe-signature", HandleAddNestedExeSignature, HelpAddNestedExeSignature },
+ { "add-nested-cat-signature", HandleAddNestedCatSignature, HelpAddNestedCatSignature },
+#ifndef IPRT_SIGNTOOL_NO_SIGNING
+ { "add-timestamp-exe-signature", HandleAddTimestampExeSignature, HelpAddTimestampExeSignature },
+ { "sign", HandleSign, HelpSign },
+#endif
+#ifndef IPRT_IN_BUILD_TOOL
+ { "verify-exe", HandleVerifyExe, HelpVerifyExe },
+#endif
+ { "show-exe", HandleShowExe, HelpShowExe },
+ { "show-cat", HandleShowCat, HelpShowCat },
+ { "hash-exe", HandleHashExe, HelpHashExe },
+ { "make-tainfo", HandleMakeTaInfo, HelpMakeTaInfo },
+ { "help", HandleHelp, HelpHelp },
+ { "--help", HandleHelp, NULL },
+ { "-h", HandleHelp, NULL },
+ { "version", HandleVersion, HelpVersion },
+ { "--version", HandleVersion, NULL },
+ { "-V", HandleVersion, NULL },
+};
+
+
+/*
+ * The 'help' command.
+ */
+static RTEXITCODE HelpHelp(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmPrintf(pStrm, "help [cmd-patterns]\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+static RTEXITCODE HandleHelp(int cArgs, char **papszArgs)
+{
+ PRTSTREAM const pStrm = g_pStdOut;
+ RTSIGNTOOLHELP enmLevel = cArgs <= 1 ? RTSIGNTOOLHELP_USAGE : RTSIGNTOOLHELP_FULL;
+ uint32_t cShowed = 0;
+ uint32_t cchWidth;
+ if (RT_FAILURE(RTStrmQueryTerminalWidth(g_pStdOut, &cchWidth)))
+ cchWidth = 80;
+
+ RTStrmPrintf(pStrm,
+ "Usage: RTSignTool <command> [command-options]\n"
+ " or: RTSignTool <-V|--version|version>\n"
+ " or: RTSignTool <-h|--help|help> [command-pattern [..]]\n"
+ "\n"
+ );
+
+ if (enmLevel == RTSIGNTOOLHELP_USAGE)
+ RTStrmPrintf(pStrm, "Syntax summary for the RTSignTool commands:\n");
+
+ for (uint32_t iCmd = 0; iCmd < RT_ELEMENTS(g_aCommands); iCmd++)
+ {
+ if (g_aCommands[iCmd].pfnHelp)
+ {
+ bool fShow = false;
+ if (cArgs <= 1)
+ fShow = true;
+ else
+ for (int iArg = 1; iArg < cArgs; iArg++)
+ if (RTStrSimplePatternMultiMatch(papszArgs[iArg], RTSTR_MAX, g_aCommands[iCmd].pszCmd, RTSTR_MAX, NULL))
+ {
+ fShow = true;
+ break;
+ }
+ if (fShow)
+ {
+ if (enmLevel == RTSIGNTOOLHELP_FULL)
+ RTPrintf("%.*s\n", RT_MIN(cchWidth, 100),
+ "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
+ g_aCommands[iCmd].pfnHelp(pStrm, enmLevel);
+ cShowed++;
+ }
+ }
+ }
+ return cShowed ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse global arguments.
+ */
+ int iArg = 1;
+ /* none presently. */
+
+ /*
+ * Command dispatcher.
+ */
+ if (iArg < argc)
+ {
+ const char *pszCmd = argv[iArg];
+ uint32_t i = RT_ELEMENTS(g_aCommands);
+ while (i-- > 0)
+ if (!strcmp(g_aCommands[i].pszCmd, pszCmd))
+ return g_aCommands[i].pfnHandler(argc - iArg, &argv[iArg]);
+ RTMsgError("Unknown command '%s'.", pszCmd);
+ }
+ else
+ RTMsgError("No command given. (try --help)");
+
+ return RTEXITCODE_SYNTAX;
+}
+
diff --git a/src/VBox/Runtime/tools/RTTar.cpp b/src/VBox/Runtime/tools/RTTar.cpp
new file mode 100644
index 00000000..a9d47193
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTTar.cpp
@@ -0,0 +1,54 @@
+/* $Id: RTTar.cpp $ */
+/** @file
+ * IPRT - TAR Utility.
+ */
+
+/*
+ * Copyright (C) 2010-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/zip.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTZipTarCmd(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTTraceLogTool.cpp b/src/VBox/Runtime/tools/RTTraceLogTool.cpp
new file mode 100644
index 00000000..dead0e9a
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTTraceLogTool.cpp
@@ -0,0 +1,343 @@
+/* $Id: RTTraceLogTool.cpp $ */
+/** @file
+ * IPRT - Utility for reading/receiving and dissecting trace logs.
+ */
+
+/*
+ * Copyright (C) 2018-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/tracelog.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/tcp.h>
+
+
+/**
+ * The tracelog tool TCP server/client state.
+ */
+typedef struct RTTRACELOGTOOLTCP
+{
+ /** Flag whether this is a server. */
+ bool fIsServer;
+ /** The TCP socket handle for the connection. */
+ RTSOCKET hSock;
+ /** The TCP server. */
+ PRTTCPSERVER pTcpSrv;
+} RTTRACELOGTOOLTCP;
+/** Pointer to the TCP server/client state. */
+typedef RTTRACELOGTOOLTCP *PRTTRACELOGTOOLTCP;
+
+
+static void rtTraceLogTcpDestroy(PRTTRACELOGTOOLTCP pTrcLogTcp)
+{
+ if (pTrcLogTcp->fIsServer)
+ RTTcpServerDestroy(pTrcLogTcp->pTcpSrv);
+ if (pTrcLogTcp->hSock != NIL_RTSOCKET)
+ {
+ if (pTrcLogTcp->fIsServer)
+ RTTcpServerDisconnectClient2(pTrcLogTcp->hSock);
+ else
+ RTTcpClientClose(pTrcLogTcp->hSock);
+ }
+ RTMemFree(pTrcLogTcp);
+}
+
+
+static DECLCALLBACK(int) rtTraceLogToolTcpInput(void *pvUser, void *pvBuf, size_t cbBuf, size_t *pcbRead,
+ RTMSINTERVAL cMsTimeout)
+{
+ PRTTRACELOGTOOLTCP pTrcLogTcp = (PRTTRACELOGTOOLTCP)pvUser;
+ if ( pTrcLogTcp->fIsServer
+ && pTrcLogTcp->hSock == NIL_RTSOCKET)
+ {
+ int rc = RTTcpServerListen2(pTrcLogTcp->pTcpSrv, &pTrcLogTcp->hSock);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ int rc = RTTcpSelectOne(pTrcLogTcp->hSock, cMsTimeout);
+ if (RT_SUCCESS(rc))
+ rc = RTTcpReadNB(pTrcLogTcp->hSock, pvBuf, cbBuf, pcbRead);
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) rtTraceLogToolTcpClose(void *pvUser)
+{
+ PRTTRACELOGTOOLTCP pTrcLogTcp = (PRTTRACELOGTOOLTCP)pvUser;
+ rtTraceLogTcpDestroy(pTrcLogTcp);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tries to create a new trace log reader using the given input.
+ *
+ * @returns IPRT status code.
+ * @param phTraceLogRdr Where to store the handle to the trace log reader instance on success.
+ * @param pszInput The input path.
+ * @param pszSave The optional path to save
+ */
+static int rtTraceLogToolReaderCreate(PRTTRACELOGRDR phTraceLogRdr, const char *pszInput, const char *pszSave)
+{
+ RT_NOREF(pszSave);
+
+ /* Try treating the input as a file first. */
+ int rc = RTTraceLogRdrCreateFromFile(phTraceLogRdr, pszInput);
+ if (RT_FAILURE(rc))
+ {
+ /*
+ * Check whether the input looks like a port number or an address:port pair.
+ * The former will create a server listening on the port while the latter tries
+ * to connect to the given address:port combination.
+ */
+ uint32_t uPort = 0;
+ bool fIsServer = false;
+ PRTTCPSERVER pTcpSrv = NULL;
+ RTSOCKET hSock = NIL_RTSOCKET;
+ rc = RTStrToUInt32Full(pszInput, 10, &uPort);
+ if (rc == VINF_SUCCESS)
+ {
+ fIsServer = true;
+ rc = RTTcpServerCreateEx(NULL, uPort, &pTcpSrv);
+ }
+ else
+ {
+ /* Try treating the input as an address:port pair. */
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Initialize structure and reader. */
+ PRTTRACELOGTOOLTCP pTrcLogTcp = (PRTTRACELOGTOOLTCP)RTMemAllocZ(sizeof(*pTrcLogTcp));
+ if (pTrcLogTcp)
+ {
+ pTrcLogTcp->fIsServer = fIsServer;
+ pTrcLogTcp->hSock = hSock;
+ pTrcLogTcp->pTcpSrv = pTcpSrv;
+ rc = RTTraceLogRdrCreate(phTraceLogRdr, rtTraceLogToolTcpInput, rtTraceLogToolTcpClose, pTrcLogTcp);
+ if (RT_FAILURE(rc))
+ rtTraceLogTcpDestroy(pTrcLogTcp);
+ }
+ else
+ {
+ if (fIsServer)
+ RTTcpServerDestroy(pTcpSrv);
+ else
+ RTSocketClose(hSock);
+ }
+ }
+ }
+ return rc;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--input", 'i', RTGETOPT_REQ_STRING },
+ { "--save", 's', RTGETOPT_REQ_STRING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ const char *pszInput = NULL;
+ const char *pszSave = NULL;
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 'h':
+ RTPrintf("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"
+ " -i,--input=<file|port|address:port>\n"
+ " Input path, can be a file a port to start listening on for incoming connections or an address:port to connect to\n"
+ " -s,--save=file\n"
+ " Save the input to a file for later use\n"
+ " -h, -?, --help\n"
+ " Display this help text and exit successfully.\n"
+ " -V, --version\n"
+ " Display the revision and exit successfully.\n"
+ , RTPathFilename(argv[0]));
+ return RTEXITCODE_SUCCESS;
+ case 'V':
+ RTPrintf("$Revision: 155244 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ case 'i':
+ pszInput = ValueUnion.psz;
+ break;
+ case 's':
+ pszSave = ValueUnion.psz;
+ break;
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ if (!pszInput)
+ {
+ RTPrintf("An input path must be given\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ /*
+ * Create trace log reader instance.
+ */
+ RTTRACELOGRDR hTraceLogRdr = NIL_RTTRACELOGRDR;
+ rc = rtTraceLogToolReaderCreate(&hTraceLogRdr, pszInput, pszSave);
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ RTTRACELOGRDRPOLLEVT enmEvt = RTTRACELOGRDRPOLLEVT_INVALID;
+ rc = RTTraceLogRdrEvtPoll(hTraceLogRdr, &enmEvt, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ switch (enmEvt)
+ {
+ case RTTRACELOGRDRPOLLEVT_HDR_RECVD:
+ RTMsgInfo("A valid header was received\n");
+ break;
+ case RTTRACELOGRDRPOLLEVT_TRACE_EVENT_RECVD:
+ {
+ RTTRACELOGRDREVT hTraceLogEvt;
+ rc = RTTraceLogRdrQueryLastEvt(hTraceLogRdr, &hTraceLogEvt);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTTRACELOGEVTDESC pEvtDesc = RTTraceLogRdrEvtGetDesc(hTraceLogEvt);
+ RTMsgInfo("%llu %llu %s\n",
+ RTTraceLogRdrEvtGetSeqNo(hTraceLogEvt),
+ RTTraceLogRdrEvtGetTs(hTraceLogEvt),
+ pEvtDesc->pszId);
+ for (unsigned i = 0; i < pEvtDesc->cEvtItems; i++)
+ {
+ RTTRACELOGEVTVAL Val;
+ unsigned cVals = 0;
+ rc = RTTraceLogRdrEvtFillVals(hTraceLogEvt, i, &Val, 1, &cVals);
+ if (RT_SUCCESS(rc))
+ {
+ switch (Val.pItemDesc->enmType)
+ {
+ case RTTRACELOGTYPE_BOOL:
+ RTMsgInfo(" %s: %s\n", Val.pItemDesc->pszName, Val.u.f ? "true" : "false");
+ break;
+ case RTTRACELOGTYPE_UINT8:
+ RTMsgInfo(" %s: %u\n", Val.pItemDesc->pszName, Val.u.u8);
+ break;
+ case RTTRACELOGTYPE_INT8:
+ RTMsgInfo(" %s: %d\n", Val.pItemDesc->pszName, Val.u.i8);
+ break;
+ case RTTRACELOGTYPE_UINT16:
+ RTMsgInfo(" %s: %u\n", Val.pItemDesc->pszName, Val.u.u16);
+ break;
+ case RTTRACELOGTYPE_INT16:
+ RTMsgInfo(" %s: %d\n", Val.pItemDesc->pszName, Val.u.i16);
+ break;
+ case RTTRACELOGTYPE_UINT32:
+ RTMsgInfo(" %s: %u\n", Val.pItemDesc->pszName, Val.u.u32);
+ break;
+ case RTTRACELOGTYPE_INT32:
+ RTMsgInfo(" %s: %d\n", Val.pItemDesc->pszName, Val.u.i32);
+ break;
+ case RTTRACELOGTYPE_UINT64:
+ RTMsgInfo(" %s: %llu\n", Val.pItemDesc->pszName, Val.u.u64);
+ break;
+ case RTTRACELOGTYPE_INT64:
+ RTMsgInfo(" %s: %lld\n", Val.pItemDesc->pszName, Val.u.i64);
+ break;
+ case RTTRACELOGTYPE_RAWDATA:
+ RTMsgInfo(" %s:\n"
+ "%.*Rhxd\n", Val.pItemDesc->pszName, Val.u.RawData.cb, Val.u.RawData.pb);
+ break;
+ case RTTRACELOGTYPE_FLOAT32:
+ case RTTRACELOGTYPE_FLOAT64:
+ RTMsgInfo(" %s: Float32 and Float64 data not supported yet\n", Val.pItemDesc->pszName);
+ break;
+ case RTTRACELOGTYPE_POINTER:
+ RTMsgInfo(" %s: %#llx\n", Val.pItemDesc->pszName, Val.u.uPtr);
+ break;
+ case RTTRACELOGTYPE_SIZE:
+ RTMsgInfo(" %s: %llu\n", Val.pItemDesc->pszName, Val.u.sz);
+ break;
+ default:
+ RTMsgError(" %s: Invalid type given %d\n", Val.pItemDesc->pszName, Val.pItemDesc->enmType);
+ }
+ }
+ else
+ RTMsgInfo(" Failed to retrieve event data with %Rrc\n", rc);
+ }
+ }
+ break;
+ }
+ default:
+ RTMsgInfo("Invalid event received: %d\n", enmEvt);
+ }
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Polling for an event failed with %Rrc\n", rc);
+ } while (RT_SUCCESS(rc));
+
+ RTTraceLogRdrDestroy(hTraceLogRdr);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create trace log reader with %Rrc\n", rc);
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTUnzip.cpp b/src/VBox/Runtime/tools/RTUnzip.cpp
new file mode 100644
index 00000000..4aeb2397
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTUnzip.cpp
@@ -0,0 +1,54 @@
+/* $Id: RTUnzip.cpp $ */
+/** @file
+ * IPRT - TAR Utility.
+ */
+
+/*
+ * Copyright (C) 2010-2023 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/zip.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTZipUnzipCmd(argc, argv);
+}
+