diff --git a/modules/build/CMakeLists.txt b/modules/build/CMakeLists.txt index 62010fa..e1e64c7 100644 --- a/modules/build/CMakeLists.txt +++ b/modules/build/CMakeLists.txt @@ -13,15 +13,20 @@ endif() # Find OpenSSL find_package(OpenSSL REQUIRED) +# Find LibArchive +find_package(LibArchive REQUIRED) + # Module version - used by DPM add_library(build MODULE build.cpp src/helpers.cpp src/cli_parsers.cpp src/commands.cpp - src/package_staging.cpp + src/staging.cpp + src/signing.cpp src/checksums.cpp src/metadata.cpp + src/sealing.cpp ) # Set output properties @@ -36,10 +41,11 @@ target_include_directories(build PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${DPM_ROOT_DIR} ${OPENSSL_INCLUDE_DIR} + ${LibArchive_INCLUDE_DIRS} ) # Link with filesystem library and OpenSSL -target_link_libraries(build stdc++fs ${OPENSSL_LIBRARIES}) +target_link_libraries(build stdc++fs ${OPENSSL_LIBRARIES} ${LibArchive_LIBRARIES}) # Standalone version - used for debugging add_executable(build_standalone @@ -47,9 +53,11 @@ add_executable(build_standalone src/helpers.cpp src/cli_parsers.cpp src/commands.cpp - src/package_staging.cpp + src/staging.cpp + src/signing.cpp src/checksums.cpp src/metadata.cpp + src/sealing.cpp ) # Define the BUILD_STANDALONE macro for the standalone build @@ -60,10 +68,11 @@ target_include_directories(build_standalone PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${DPM_ROOT_DIR} ${OPENSSL_INCLUDE_DIR} + ${LibArchive_INCLUDE_DIRS} ) # Link with filesystem library and OpenSSL for standalone -target_link_libraries(build_standalone stdc++fs ${OPENSSL_LIBRARIES}) +target_link_libraries(build_standalone stdc++fs ${OPENSSL_LIBRARIES} ${LibArchive_LIBRARIES}) # Set the output name for the standalone executable set_target_properties( diff --git a/modules/build/build.cpp b/modules/build/build.cpp index 38bfec3..57bd782 100644 --- a/modules/build/build.cpp +++ b/modules/build/build.cpp @@ -79,6 +79,7 @@ extern "C" const char* dpm_get_description(void) { * @param argv Array of argument strings * @return 0 on success, non-zero on failure */ +// In build.cpp, update the dpm_module_execute function extern "C" int dpm_module_execute(const char* command, int argc, char** argv) { // Parse the command Command cmd = parse_command(command); @@ -94,6 +95,15 @@ extern "C" int dpm_module_execute(const char* command, int argc, char** argv) { case CMD_MANIFEST: return cmd_manifest(argc, argv); + case CMD_SIGN: + return cmd_sign(argc, argv); + + case CMD_SEAL: + return cmd_seal(argc, argv); + + case CMD_UNSEAL: + return cmd_unseal(argc, argv); + case CMD_UNKNOWN: default: return cmd_unknown(command, argc, argv); diff --git a/modules/build/include/cli_parsers.hpp b/modules/build/include/cli_parsers.hpp index 8206f13..110f300 100644 --- a/modules/build/include/cli_parsers.hpp +++ b/modules/build/include/cli_parsers.hpp @@ -12,10 +12,13 @@ * @brief Enumeration of supported commands for the build module */ enum Command { - CMD_UNKNOWN, /**< Unknown or unsupported command */ - CMD_HELP, /**< Display help information */ - CMD_STAGE, /**< Stage a new DPM package */ - CMD_MANIFEST, /**< Regenerate a stage manifest */ + CMD_UNKNOWN, /**< Unknown or unsupported command */ + CMD_HELP, /**< Display help information */ + CMD_STAGE, /**< Stage a new DPM package */ + CMD_MANIFEST, /**< Regenerate a stage manifest */ + CMD_SIGN, /**< Sign a package or stage directory */ + CMD_SEAL, /**< Seal a package stage directory */ + CMD_UNSEAL, /**< Unseal a package stage directory */ }; /** diff --git a/modules/build/include/commands.hpp b/modules/build/include/commands.hpp index 4cc2bb6..bd71ebb 100644 --- a/modules/build/include/commands.hpp +++ b/modules/build/include/commands.hpp @@ -3,7 +3,9 @@ #include "cli_parsers.hpp" #include #include -#include "package_staging.hpp" +#include "staging.hpp" +#include "signing.hpp" +#include "sealing.hpp" // Added this include #include #include @@ -29,6 +31,17 @@ int cmd_stage(int argc, char** argv); */ int cmd_manifest(int argc, char** argv); +/** + * @brief Handler for the sign command + * + * Signs a DPM package or package stage directory using GPG. + * + * @param argc Number of arguments + * @param argv Array of arguments + * @return 0 on success, non-zero on failure + */ +int cmd_sign(int argc, char** argv); + /** * @brief Handler for the help command * @@ -40,7 +53,6 @@ int cmd_manifest(int argc, char** argv); */ int cmd_help(int argc, char** argv); - /** * @brief Handler for the help command * @@ -52,6 +64,17 @@ int cmd_help(int argc, char** argv); */ int cmd_stage_help(int argc, char** argv); +/** + * @brief Handler for the sign help command + * + * Displays information about sign command options. + * + * @param argc Number of arguments + * @param argv Array of arguments + * @return 0 on success, non-zero on failure + */ +int cmd_sign_help(int argc, char** argv); + /** * @brief Handler for the manifest help command * @@ -73,4 +96,51 @@ int cmd_manifest_help(int argc, char** argv); * @param argv Array of arguments * @return 1 to indicate failure */ -int cmd_unknown(const char* command, int argc, char** argv); \ No newline at end of file +int cmd_unknown(const char* command, int argc, char** argv); + +/** + * @brief Handler for the seal command + * + * Seals a DPM package stage directory by replacing contents, metadata, + * hooks, and signatures directories with gzipped tarballs. + * + * @param argc Number of arguments + * @param argv Array of arguments + * @return 0 on success, non-zero on failure + */ +int cmd_seal(int argc, char** argv); + +/** + * @brief Handler for the seal help command + * + * Displays information about seal command options. + * + * @param argc Number of arguments + * @param argv Array of arguments + * @return 0 on success, non-zero on failure + */ +int cmd_seal_help(int argc, char** argv); + +/** + * @brief Handler for the unseal command + * + * Unseals a DPM package file by extracting and expanding the gzipped + * tarballs back into a package stage directory structure. + * + * @param argc Number of arguments + * @param argv Array of arguments + * @return 0 on success, non-zero on failure + */ +int cmd_unseal(int argc, char** argv); + +/** + * @brief Handler for the unseal help command + * + * Displays information about unseal command options. + * + * @param argc Number of arguments + * @param argv Array of arguments + * @return 0 on success, non-zero on failure + */ +int cmd_unseal_help(int argc, char** argv); + diff --git a/modules/build/include/package_staging.hpp b/modules/build/include/staging.hpp similarity index 97% rename from modules/build/include/package_staging.hpp rename to modules/build/include/staging.hpp index f83afbf..ec91272 100644 --- a/modules/build/include/package_staging.hpp +++ b/modules/build/include/staging.hpp @@ -1,5 +1,5 @@ /** -* @file package_staging.hpp +* @file staging.hpp * @brief Functions for staging DPM packages * * Defines functions for creating and manipulating DPM package staging structures. diff --git a/modules/build/src/cli_parsers.cpp b/modules/build/src/cli_parsers.cpp index 1ec047a..5f5595e 100644 --- a/modules/build/src/cli_parsers.cpp +++ b/modules/build/src/cli_parsers.cpp @@ -237,11 +237,26 @@ Command parse_command(const char* cmd_str) { return CMD_STAGE; } - // Check for stage command, including when it has additional arguments + // Check for manifest command, including when it has additional arguments if (strncmp(cmd_str, "manifest", 8) == 0) { return CMD_MANIFEST; } + // Check for sign command, including when it has additional arguments + if (strncmp(cmd_str, "sign", 4) == 0) { + return CMD_SIGN; + } + + // Check for seal command, including when it has additional arguments + if (strncmp(cmd_str, "seal", 4) == 0) { + return CMD_SEAL; + } + + // Check for unseal command, including when it has additional arguments + if (strncmp(cmd_str, "unseal", 6) == 0) { + return CMD_UNSEAL; + } + // Check if cmd_str is a help option if (strcmp(cmd_str, "-h") == 0 || strcmp(cmd_str, "--help") == 0) { return CMD_HELP; diff --git a/modules/build/src/commands.cpp b/modules/build/src/commands.cpp index 6c1615e..2454515 100644 --- a/modules/build/src/commands.cpp +++ b/modules/build/src/commands.cpp @@ -395,12 +395,103 @@ int cmd_stage(int argc, char** argv) { ); } +int cmd_sign(int argc, char** argv) { + // Parse command line options + std::string key_id = ""; + std::string stage_dir = ""; + std::string package_path = ""; + bool force = false; + bool verbose = false; + bool show_help = false; + + // Process command-line arguments + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + + if (arg == "-k" || arg == "--key-id") { + if (i + 1 < argc) { + key_id = argv[i + 1]; + i++; // Skip the next argument + } + } else if (arg == "-s" || arg == "--stage") { + if (i + 1 < argc) { + stage_dir = argv[i + 1]; + i++; // Skip the next argument + } + } else if (arg == "-p" || arg == "--package") { + if (i + 1 < argc) { + package_path = argv[i + 1]; + i++; // Skip the next argument + } + } else if (arg == "-f" || arg == "--force") { + force = true; + } else if (arg == "-v" || arg == "--verbose") { + verbose = true; + } else if (arg == "-h" || arg == "--help" || arg == "help") { + show_help = true; + } + } + + // If help was requested, show it and return + if (show_help) { + return cmd_sign_help(argc, argv); + } + + // Set verbose logging if requested + if (verbose) { + dpm_set_logging_level(LOG_DEBUG); + } + + // Validate that key ID is provided + if (key_id.empty()) { + dpm_log(LOG_ERROR, "GPG key ID is required (--key-id/-k)"); + return cmd_sign_help(argc, argv); + } + + // Validate that either stage or package is provided, but not both + if (stage_dir.empty() && package_path.empty()) { + dpm_log(LOG_ERROR, "Either a package stage directory (--stage/-s) or a package file (--package/-p) must be specified"); + return cmd_sign_help(argc, argv); + } + + if (!stage_dir.empty() && !package_path.empty()) { + dpm_log(LOG_ERROR, "Cannot specify both package stage directory (--stage/-s) and package file (--package/-p)"); + return cmd_sign_help(argc, argv); + } + + // Expand paths if needed + if (!stage_dir.empty()) { + stage_dir = expand_path(stage_dir); + // Check if stage directory exists + if (!std::filesystem::exists(stage_dir)) { + dpm_log(LOG_ERROR, ("Stage directory does not exist: " + stage_dir).c_str()); + return 1; + } + + // Sign the stage directory + return sign_stage_directory(stage_dir, key_id, force); + } else { + package_path = expand_path(package_path); + // Check if package file exists + if (!std::filesystem::exists(package_path)) { + dpm_log(LOG_ERROR, ("Package file does not exist: " + package_path).c_str()); + return 1; + } + + // Sign the package file + return sign_package_file(package_path, key_id, force); + } +} + int cmd_help(int argc, char** argv) { - dpm_log(LOG_INFO, "DPM Build Module - Creates DPM packages according to specification."); + dpm_log(LOG_INFO, "DPM Build Module - Creates DPM packages."); dpm_log(LOG_INFO, ""); dpm_log(LOG_INFO, "Available commands:"); dpm_log(LOG_INFO, " stage - Stage a new DPM package directory"); dpm_log(LOG_INFO, " manifest - Generate or refresh package manifest"); + dpm_log(LOG_INFO, " sign - Sign a package or package stage directory"); + dpm_log(LOG_INFO, " seal - Seal a package stage directory into final format"); + dpm_log(LOG_INFO, " unseal - Unseal a package back to stage format"); dpm_log(LOG_INFO, " help - Display this help message"); dpm_log(LOG_INFO, ""); dpm_log(LOG_INFO, "Usage: dpm build "); @@ -447,3 +538,226 @@ int cmd_stage_help(int argc, char** argv) { dpm_log(LOG_INFO, " -h, --help Display this help message"); return 0; } + +int cmd_sign_help(int argc, char** argv) { + dpm_log(LOG_INFO, "Usage: dpm build sign [options]"); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, "Sign a DPM package or package stage directory using GPG."); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, "Options:"); + dpm_log(LOG_INFO, " -k, --key-id ID GPG key ID or email to use for signing (required)"); + dpm_log(LOG_INFO, " -s, --stage DIR Package stage directory to sign"); + dpm_log(LOG_INFO, " -p, --package FILE Package file to sign"); + dpm_log(LOG_INFO, " -f, --force Force signing even if warnings occur"); + dpm_log(LOG_INFO, " -v, --verbose Enable verbose output"); + dpm_log(LOG_INFO, " -h, --help Display this help message"); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, "Either --stage or --package must be specified, but not both."); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, "Examples:"); + dpm_log(LOG_INFO, " dpm build sign --key-id=\"user@example.com\" --stage=./my-package-1.0.x86_64"); + dpm_log(LOG_INFO, " dpm build sign --key-id=\"AB123CD456\" --package=./my-package-1.0.x86_64.dpm"); + return 0; +} + + +int cmd_unseal(int argc, char** argv) { + // Parse command line options + std::string input_path = ""; + std::string output_dir = ""; + bool components_mode = false; + bool force = false; + bool verbose = false; + bool show_help = false; + + // Process command-line arguments + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + + if (arg == "-i" || arg == "--input") { + if (i + 1 < argc) { + input_path = argv[i + 1]; + i++; // Skip the next argument + } + } else if (arg == "-o" || arg == "--output") { + if (i + 1 < argc) { + output_dir = argv[i + 1]; + i++; // Skip the next argument + } + } else if (arg == "-c" || arg == "--components") { + components_mode = true; + } else if (arg == "-f" || arg == "--force") { + force = true; + } else if (arg == "-v" || arg == "--verbose") { + verbose = true; + } else if (arg == "-h" || arg == "--help" || arg == "help") { + show_help = true; + } + } + + // If help was requested, show it and return + if (show_help) { + return cmd_unseal_help(argc, argv); + } + + // Validate that input path is provided + if (input_path.empty()) { + dpm_log(LOG_ERROR, "Input path is required (--input/-i)"); + return cmd_unseal_help(argc, argv); + } + + // Check for invalid option combinations + if (components_mode && !output_dir.empty()) { + dpm_log(LOG_ERROR, "Output directory (-o/--output) cannot be specified in components mode (-c/--components)"); + return cmd_unseal_help(argc, argv); + } + + // Expand path if needed + input_path = expand_path(input_path); + + // Check if input path exists + if (!std::filesystem::exists(input_path)) { + dpm_log(LOG_ERROR, ("Input path does not exist: " + input_path).c_str()); + return 1; + } + + // Set verbose logging if requested + if (verbose) { + dpm_set_logging_level(LOG_DEBUG); + } + + // Determine which operation to perform based on components_mode flag + if (components_mode) { + // We're unsealing components of a stage directory + if (!std::filesystem::is_directory(input_path)) { + dpm_log(LOG_ERROR, ("Input path must be a directory in components mode: " + input_path).c_str()); + return 1; + } + + // Call unseal_stage_components with just the input path + return unseal_stage_components(input_path); + } else { + // We're unsealing a package file + if (std::filesystem::is_directory(input_path)) { + dpm_log(LOG_ERROR, ("Input path must be a file when not in components mode: " + input_path).c_str()); + return 1; + } + + // Call unseal_package + return unseal_package(input_path, output_dir, force); + } +} + +int cmd_unseal_help(int argc, char** argv) { + dpm_log(LOG_INFO, "Usage: dpm build unseal [options]"); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, "Unseals a DPM package file or package stage components."); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, "Options:"); + dpm_log(LOG_INFO, " -i, --input PATH Path to package file or stage directory (required)"); + dpm_log(LOG_INFO, " -o, --output DIR Directory to extract package to (optional, package mode only)"); + dpm_log(LOG_INFO, " -c, --components Component mode: unseal components in a stage directory"); + dpm_log(LOG_INFO, " Without this flag, input is treated as a package file"); + dpm_log(LOG_INFO, " -f, --force Force unsealing even if warnings occur or directory exists"); + dpm_log(LOG_INFO, " -v, --verbose Enable verbose output"); + dpm_log(LOG_INFO, " -h, --help Display this help message"); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, "Examples:"); + dpm_log(LOG_INFO, " # Unseal a package file to a directory:"); + dpm_log(LOG_INFO, " dpm build unseal --input=./my-package-1.0.x86_64.dpm"); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, " # Unseal a package file to a specific directory:"); + dpm_log(LOG_INFO, " dpm build unseal --input=./my-package-1.0.x86_64.dpm --output=./extract"); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, " # Unseal components in a stage directory:"); + dpm_log(LOG_INFO, " dpm build unseal --input=./my-package-1.0.x86_64 --components"); + return 0; +} + +int cmd_seal(int argc, char** argv) { + // Parse command line options + std::string stage_dir = ""; + std::string output_dir = ""; + bool force = false; + bool verbose = false; + bool finalize = false; + bool show_help = false; + + // Process command-line arguments + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + + if (arg == "-s" || arg == "--stage") { + if (i + 1 < argc) { + stage_dir = argv[i + 1]; + i++; // Skip the next argument + } + } else if (arg == "-o" || arg == "--output") { + if (i + 1 < argc) { + output_dir = argv[i + 1]; + i++; // Skip the next argument + } + } else if (arg == "-f" || arg == "--force") { + force = true; + } else if (arg == "-z" || arg == "--finalize") { + finalize = true; + } else if (arg == "-v" || arg == "--verbose") { + verbose = true; + } else if (arg == "-h" || arg == "--help" || arg == "help") { + show_help = true; + } + } + + // If help was requested, show it and return + if (show_help) { + return cmd_seal_help(argc, argv); + } + + // Validate that stage directory is provided + if (stage_dir.empty()) { + dpm_log(LOG_ERROR, "Stage directory is required (--stage/-s)"); + return cmd_seal_help(argc, argv); + } + + // Expand path if needed + stage_dir = expand_path(stage_dir); + + // Check if stage directory exists + if (!std::filesystem::exists(stage_dir)) { + dpm_log(LOG_ERROR, ("Stage directory does not exist: " + stage_dir).c_str()); + return 1; + } + + // Set verbose logging if requested + if (verbose) { + dpm_set_logging_level(LOG_DEBUG); + } + + // Call the appropriate sealing function based on the finalize flag + if (finalize) { + return seal_final_package(stage_dir, output_dir, force); + } else { + return seal_stage_components(stage_dir, force); + } +} + +int cmd_seal_help(int argc, char** argv) { + dpm_log(LOG_INFO, "Usage: dpm build seal [options]"); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, "Seals a package stage directory by replacing contents, metadata,"); + dpm_log(LOG_INFO, "hooks, and signatures directories with gzipped tarballs."); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, "Options:"); + dpm_log(LOG_INFO, " -s, --stage DIR Package stage directory to seal (required)"); + dpm_log(LOG_INFO, " -o, --output DIR Output directory for the finalized package (optional)"); + dpm_log(LOG_INFO, " -f, --force Force sealing even if warnings occur"); + dpm_log(LOG_INFO, " -z, --finalize Also compress the entire stage as a final package"); + dpm_log(LOG_INFO, " -v, --verbose Enable verbose output"); + dpm_log(LOG_INFO, " -h, --help Display this help message"); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, "Examples:"); + dpm_log(LOG_INFO, " dpm build seal --stage=./my-package-1.0.x86_64"); + dpm_log(LOG_INFO, " dpm build seal --stage=./my-package-1.0.x86_64 --finalize"); + dpm_log(LOG_INFO, " dpm build seal --stage=./my-package-1.0.x86_64 --finalize --output=/tmp"); + return 0; +} \ No newline at end of file diff --git a/modules/build/src/package_staging.cpp b/modules/build/src/staging.cpp similarity index 99% rename from modules/build/src/package_staging.cpp rename to modules/build/src/staging.cpp index e3193f4..54c9ff3 100644 --- a/modules/build/src/package_staging.cpp +++ b/modules/build/src/staging.cpp @@ -10,7 +10,7 @@ * Part of the Dark Horse Linux Package Manager (DPM) */ -#include "package_staging.hpp" +#include "staging.hpp" // generates a directory for the stage according to naming convention std::filesystem::path stage_determine_rootdir_path(