diff options
Diffstat (limited to 'src/VBox/Runtime/tools')
29 files changed, 16964 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..25603ff0 --- /dev/null +++ b/src/VBox/Runtime/tools/Makefile.kmk @@ -0,0 +1,286 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the IPRT tools. +# + +# +# Copyright (C) 2006-2022 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 += VBOX_OPENSSL_BLDPROG + bldRTSignTool_DEFS += IPRT_WITH_OPENSSL + bldRTSignTool_SOURCES += \ + ../common/crypto/pkix-signature-builtin.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..204d09f5 --- /dev/null +++ b/src/VBox/Runtime/tools/RTCat.cpp @@ -0,0 +1,330 @@ +/* $Id: RTCat.cpp $ */ +/** @file + * IPRT - cat like utility. + */ + +/* + * Copyright (C) 2017-2022 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..dd08ccc1 --- /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-2022 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..8cd91464 --- /dev/null +++ b/src/VBox/Runtime/tools/RTCp.cpp @@ -0,0 +1,361 @@ +/* $Id: RTCp.cpp $ */ +/** @file + * IPRT - cp like utility. + */ + +/* + * Copyright (C) 2017-2022 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..45097943 --- /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-2022 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..99271e0d --- /dev/null +++ b/src/VBox/Runtime/tools/RTDbgSymSrv.cpp @@ -0,0 +1,600 @@ +/* $Id: RTDbgSymSrv.cpp $ */ +/** @file + * IPRT - Debug Symbol Server. + */ + +/* + * Copyright (C) 2021-2022 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..d92cea5c --- /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-2022 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: 153224 $\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..015525ab --- /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-2022 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..b7ef8a30 --- /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-2022 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: 153224 $\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..0f737cdb --- /dev/null +++ b/src/VBox/Runtime/tools/RTFuzzClient.cpp @@ -0,0 +1,54 @@ +/* $Id: RTFuzzClient.cpp $ */ +/** @file + * IPRT - Fuzzing client utility. + */ + +/* + * Copyright (C) 2019-2022 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..637afd7e --- /dev/null +++ b/src/VBox/Runtime/tools/RTFuzzMaster.cpp @@ -0,0 +1,54 @@ +/* $Id: RTFuzzMaster.cpp $ */ +/** @file + * IPRT - Fuzzing master Utility. + */ + +/* + * Copyright (C) 2018-2022 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..cb92151d --- /dev/null +++ b/src/VBox/Runtime/tools/RTGzip.cpp @@ -0,0 +1,55 @@ +/* $Id: RTGzip.cpp $ */ +/** @file + * IPRT - GZIP Utility. + */ + +/* + * Copyright (C) 2010-2022 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..67a0821d --- /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-2022 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: 153224 $\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..06780994 --- /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-2022 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: 153224 $\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..37312572 --- /dev/null +++ b/src/VBox/Runtime/tools/RTIsoMaker.cpp @@ -0,0 +1,54 @@ +/* $Id: RTIsoMaker.cpp $ */ +/** @file + * IPRT - ISO maker Utility. + */ + +/* + * Copyright (C) 2010-2022 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..70a94624 --- /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-2022 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: 153224 $\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..88614b76 --- /dev/null +++ b/src/VBox/Runtime/tools/RTLdrCheckImports.cpp @@ -0,0 +1,708 @@ +/* $Id: RTLdrCheckImports.cpp $ */ +/** @file + * IPRT - Module dependency checker. + */ + +/* + * Copyright (C) 2010-2022 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..57fd4ac7 --- /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-2022 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: 153224 $\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..31a84e44 --- /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-2022 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..0f0f5883 --- /dev/null +++ b/src/VBox/Runtime/tools/RTManifest.cpp @@ -0,0 +1,404 @@ +/* $Id: RTManifest.cpp $ */ +/** @file + * IPRT - Manifest Utility. + */ + +/* + * Copyright (C) 2010-2022 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..0dbb795c --- /dev/null +++ b/src/VBox/Runtime/tools/RTMkDir.cpp @@ -0,0 +1,379 @@ +/* $Id: RTMkDir.cpp $ */ +/** @file + * IPRT - Creates directory. + */ + +/* + * Copyright (C) 2013-2022 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..c1098013 --- /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-2022 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: 153224 $"); + 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..51d3d9dd --- /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-2022 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..8306c25f --- /dev/null +++ b/src/VBox/Runtime/tools/RTRmDir.cpp @@ -0,0 +1,369 @@ +/* $Id: RTRmDir.cpp $ */ +/** @file + * IPRT - Removes directory. + */ + +/* + * Copyright (C) 2013-2022 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..1d4de7f4 --- /dev/null +++ b/src/VBox/Runtime/tools/RTShutdown.cpp @@ -0,0 +1,114 @@ +/* $Id: RTShutdown.cpp $ */ +/** @file + * IPRT Testcase - System Shutdown. + */ + +/* + * Copyright (C) 2012-2022 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..9a29d7fa --- /dev/null +++ b/src/VBox/Runtime/tools/RTSignTool.cpp @@ -0,0 +1,6327 @@ +/* $Id: RTSignTool.cpp $ */ +/** @file + * IPRT - Signing Tool. + */ + +/* + * Copyright (C) 2006-2022 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_QueryDigestType(&pSignerInfo->DigestAlgorithm)); + 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_QueryDigestType(&pIndData->DigestInfo.DigestAlgorithm); + 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_QueryDigestType(pAlgoId)); + 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..4c6d0cc8 --- /dev/null +++ b/src/VBox/Runtime/tools/RTTar.cpp @@ -0,0 +1,54 @@ +/* $Id: RTTar.cpp $ */ +/** @file + * IPRT - TAR Utility. + */ + +/* + * Copyright (C) 2010-2022 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..220d623b --- /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-2022 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: 153224 $\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..7e7e5576 --- /dev/null +++ b/src/VBox/Runtime/tools/RTUnzip.cpp @@ -0,0 +1,54 @@ +/* $Id: RTUnzip.cpp $ */ +/** @file + * IPRT - TAR Utility. + */ + +/* + * Copyright (C) 2010-2022 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); +} + |