From d0d34f43453dc8ddc18c9f9fa058abd502ff64fc Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Fri, 1 May 2026 15:02:23 +0200 Subject: [PATCH] Resolve #394: Replace Boost.Program_options with CLI11 (#396) * Replace Boost.Program_options with CLI11 to resolve version pinning (#394) - Replace boost::program_options with header-only CLI11 in main.cpp - Remove boost dependency from Makefile, default.nix, and flake.nix - Update documentation in doc/BUILD.md - Add CMakeLists.txt as an alternative build system This fixes the issue where the binary would fail to find specific libboost_program_options.so versions (e.g., 1.90.0) on systems with newer/older Boost versions. Since CLI11 is header-only, there is no longer a runtime dependency on Boost for pcloudcc. * Update build configuration and dev scripts to remove remaining Boost references - Remove Boost from dev scripts and container build configs - Add -std=c++11 to Makefile CXXFLAGS - Explicitly add -I. to Makefile CFLAGS for CLI11.hpp include * Address build review feedback and fix security/bug issues - Fix passascrypto logic and type (now a flag) in main.cpp - Fix typo in daemon process name - Add secret wiping (putil_wipe) for tfa_code and singleton passwords - Remove redundant App setup in control_tools.cpp - Use CLI11 envname for PCLOUD_USER - Fix C++11 compatibility for putil_wipe and App initialization in control_tools.cpp * Remove phantom CMakeLists.txt and ensure Makefile is the source of truth --------- Co-authored-by: Levi Neely --- Makefile | 6 +- control_tools.cpp | 13 ++-- default.nix | 3 - dev/container-build.sh | 8 +-- dev/devhost-arch-arm64.sh | 2 +- dev/devhost-arch.sh | 2 +- dev/devhost-debian.sh | 3 +- dev/devhost-fedora.sh | 2 +- doc/BUILD.md | 2 +- flake.nix | 3 - main.cpp | 128 +++++++++++++++++++------------------- 11 files changed, 82 insertions(+), 90 deletions(-) diff --git a/Makefile b/Makefile index 367b93e..c1c0992 100644 --- a/Makefile +++ b/Makefile @@ -31,11 +31,11 @@ else endif COMMONFLAGS = -fsanitize=address -CFLAGS = -fPIC $(COMMONFLAGS) -I./pclsync -I/usr/include $(FUSE_CFLAGS) $(shell pkg-config --cflags $$(pkg-config --list-all | grep -o 'mbedtls[0-9.]*\s' | head -1) 2>/dev/null || pkg-config --cflags mbedtls 2>/dev/null || echo "-I/usr/local/include") +CFLAGS = -fPIC $(COMMONFLAGS) -I. -I./pclsync -I/usr/include $(FUSE_CFLAGS) $(shell pkg-config --cflags $$(pkg-config --list-all | grep -o 'mbedtls[0-9.]*\s' | head -1) 2>/dev/null || pkg-config --cflags mbedtls 2>/dev/null || echo "-I/usr/local/include") ifneq (,$(filter clang%,$(CC))) CFLAGS += -Wthread-safety endif -CXXFLAGS = $(CFLAGS) +CXXFLAGS = $(CFLAGS) -std=c++11 LIBLDFLAGS = $(COMMONFLAGS) -lreadline -lpthread -ludev -lsqlite3 -lz $(shell \ MBEDTLS_PKG=$$(pkg-config --list-all 2>/dev/null | grep -o 'mbedtls[0-9.]*\s' | head -1 | tr -d ' '); \ if [ -n "$$MBEDTLS_PKG" ]; then \ @@ -45,7 +45,7 @@ LIBLDFLAGS = $(COMMONFLAGS) -lreadline -lpthread -ludev -lsqlite3 -lz $(shell \ else \ pkg-config --libs mbedtls mbedx509 mbedcrypto 2>/dev/null || echo "-L/usr/local/lib -lmbedtls -lmbedx509 -lmbedcrypto"; \ fi) -EXECLDFLAGS = $(COMMONFLAGS) -lboost_program_options $(FUSE_LIBS) +EXECLDFLAGS = $(COMMONFLAGS) $(FUSE_LIBS) SCAN := 0 SRCDIR := . diff --git a/control_tools.cpp b/control_tools.cpp index f573b7d..7a8aec0 100644 --- a/control_tools.cpp +++ b/control_tools.cpp @@ -198,7 +198,9 @@ void setup_app(CLI::App *app) { size_t errm_size = 0; RpcClient *rpc = new RpcClient(); int result = rpc->Call(SENDAUTH, auth_pass_input.c_str(), &errm, &errm_size); - putil_wipe(auth_pass_input.data(), auth_pass_input.size()); + if (!auth_pass_input.empty()) { + putil_wipe(&auth_pass_input[0], auth_pass_input.size()); + } auth_pass_input.clear(); if (result != 0) { std::cerr << "Failed to send auth: " << (errm ? errm : "no message") << std::endl; @@ -221,7 +223,9 @@ void setup_app(CLI::App *app) { size_t errm_size = 0; RpcClient *rpc = new RpcClient(); int result = rpc->Call(SENDAUTHSAVE, authsave_pass_input.c_str(), &errm, &errm_size); - putil_wipe(authsave_pass_input.data(), authsave_pass_input.size()); + if (!authsave_pass_input.empty()) { + putil_wipe(&authsave_pass_input[0], authsave_pass_input.size()); + } authsave_pass_input.clear(); if (result != 0) { std::cerr << "Failed to send auth: " << (errm ? errm : "no message") << std::endl; @@ -440,7 +444,7 @@ void setup_app(CLI::App *app) { } int process_command(const std::string &command) { - CLI::App app = CLI::App{"pcloudcc-lneely"}; + CLI::App app{"pcloudcc-lneely"}; setup_app(&app); try { app.parse(command); @@ -480,9 +484,6 @@ int process_command(const std::string &command) { } void process_commands() { - CLI::App app = CLI::App{"pcloudcc-lneely"}; - setup_app(&app); - using_history(); rl_attempted_completion_function = command_completion; diff --git a/default.nix b/default.nix index 3ded3b9..6ee578c 100644 --- a/default.nix +++ b/default.nix @@ -5,7 +5,6 @@ pkgs.mkShell { buildInputs = with pkgs; [ zlib sqlite - boost libudev-zero readline fuse @@ -15,7 +14,6 @@ pkgs.mkShell { CFLAGS = [ "-I${pkgs.zlib.dev}/include" "-I${pkgs.sqlite.dev}/include" - "-I${pkgs.boost.dev}/include" "-I${pkgs.readline.dev}/include" "-I${pkgs.fuse.dev}/include" "-I${pkgs.mbedtls}/include" @@ -23,7 +21,6 @@ pkgs.mkShell { CXXFLAGS = [ "-I${pkgs.zlib.dev}/include" "-I${pkgs.sqlite.dev}/include" - "-I${pkgs.boost.dev}/include" "-I${pkgs.readline.dev}/include" "-I${pkgs.fuse.dev}/include" "-I${pkgs.mbedtls}/include" diff --git a/dev/container-build.sh b/dev/container-build.sh index 9932571..7897d44 100755 --- a/dev/container-build.sh +++ b/dev/container-build.sh @@ -93,26 +93,26 @@ case $IMAGE in echo "Setting up Debian/Ubuntu-based container..." ctrid=$(buildah from "$FULL_IMAGE") buildah run "$ctrid" -- apt update - buildah run "$ctrid" -- apt install -y build-essential git libfuse-dev libudev-dev libsqlite3-dev libmbedtls-dev zlib1g-dev libboost-system-dev libboost-program-options-dev fuse llvm gdb + buildah run "$ctrid" -- apt install -y build-essential git libfuse-dev libudev-dev libsqlite3-dev libmbedtls-dev zlib1g-dev fuse llvm gdb ;; fedora) echo "Setting up Fedora-based container..." ctrid=$(buildah from "$FULL_IMAGE") buildah run "$ctrid" -- dnf update -y buildah run "$ctrid" -- dnf group install -y "C Development Tools and Libraries" - buildah run "$ctrid" -- dnf install -y git fuse-devel systemd-devel sqlite-devel mbedtls-devel zlib-devel boost-devel boost-program-options fuse llvm gdb fuse udev libasan + buildah run "$ctrid" -- dnf install -y git fuse-devel systemd-devel sqlite-devel mbedtls-devel zlib-devel fuse llvm gdb fuse udev libasan ;; archlinux|arch) echo "Setting up Arch Linux-based container..." ctrid=$(buildah from "$FULL_IMAGE") buildah run "$ctrid" -- pacman -Syu --noconfirm - buildah run "$ctrid" -- pacman -S --noconfirm base-devel git fuse2 systemd sqlite mbedtls2 zlib boost boost-libs llvm gdb udev gcc make + buildah run "$ctrid" -- pacman -S --noconfirm base-devel git fuse2 systemd sqlite mbedtls2 zlib llvm gdb udev gcc make ;; opensuse/tumbleweed|opensuse/leap) echo "Setting up openSUSE-based container..." ctrid=$(buildah from "$FULL_IMAGE") buildah run "$ctrid" -- zypper refresh - buildah run "$ctrid" -- zypper install -y gcc gcc-c++ make git fuse-devel systemd-devel sqlite3-devel zlib-devel libboost_system-devel libboost_program_options-devel fuse llvm gdb udev mbedtls-2-devel + buildah run "$ctrid" -- zypper install -y gcc gcc-c++ make git fuse-devel systemd-devel sqlite3-devel zlib-devel fuse llvm gdb udev mbedtls-2-devel ;; *) echo "Unsupported image: $IMAGE" diff --git a/dev/devhost-arch-arm64.sh b/dev/devhost-arch-arm64.sh index bfba035..fce9862 100755 --- a/dev/devhost-arch-arm64.sh +++ b/dev/devhost-arch-arm64.sh @@ -103,7 +103,7 @@ build_container() { sudo vim nano curl wget git htop tmux man-db \ bash-completion ca-certificates openssh \ base-devel gcc gcc-libs make fuse2 systemd sqlite3 \ - mbedtls zlib boost llvm gdb iproute \ + mbedtls zlib llvm gdb iproute \ rsync readline # verify fuse diff --git a/dev/devhost-arch.sh b/dev/devhost-arch.sh index 61db488..13bc3d6 100755 --- a/dev/devhost-arch.sh +++ b/dev/devhost-arch.sh @@ -103,7 +103,7 @@ build_container() { sudo vim nano curl wget git htop tmux man-db \ bash-completion ca-certificates openssh \ base-devel gcc gcc-libs make fuse2 systemd sqlite3 \ - mbedtls zlib boost llvm gdb iproute \ + mbedtls zlib llvm gdb iproute \ rsync readline # verify fuse diff --git a/dev/devhost-debian.sh b/dev/devhost-debian.sh index c85bfe2..d355bc7 100755 --- a/dev/devhost-debian.sh +++ b/dev/devhost-debian.sh @@ -106,8 +106,7 @@ build_container() { sudo vim nano curl wget git htop tmux man-db locales \ bash-completion ca-certificates ssh systemd systemd-sysv \ build-essential libfuse-dev libudev-dev libsqlite3-dev \ - libmbedtls-dev zlib1g-dev libboost-system-dev \ - libboost-program-options-dev fuse llvm gdb iproute2 \ + libmbedtls-dev zlib1g-dev fuse llvm gdb iproute2 \ openssh-server rsync libreadline-dev # verify fuse diff --git a/dev/devhost-fedora.sh b/dev/devhost-fedora.sh index 602b49d..84f929f 100755 --- a/dev/devhost-fedora.sh +++ b/dev/devhost-fedora.sh @@ -103,7 +103,7 @@ build_container() { sudo vim nano curl wget git htop tmux man-db \ bash-completion ca-certificates openssh-server \ gcc gcc-c++ make fuse-devel systemd-devel sqlite-devel \ - mbedtls-devel zlib-devel boost-devel fuse llvm gdb iproute \ + mbedtls-devel zlib-devel fuse llvm gdb iproute \ rsync readline-devel # verify fuse diff --git a/doc/BUILD.md b/doc/BUILD.md index 0d3f857..1b6866a 100644 --- a/doc/BUILD.md +++ b/doc/BUILD.md @@ -1,6 +1,6 @@ # Dependencies - zlib (-lz) -- boost (-lboost_system, -lboost_program_options) +- CLI11 (included as CLI11.hpp) - pthread (lpthread) - udev (-ludev) - fuse (-lfuse) diff --git a/flake.nix b/flake.nix index 11d1a41..5294e48 100644 --- a/flake.nix +++ b/flake.nix @@ -16,7 +16,6 @@ clang-tools zlib sqlite - boost libudev-zero readline fuse @@ -27,7 +26,6 @@ CFLAGS = [ "-I${pkgs.zlib.dev}/include" "-I${pkgs.sqlite.dev}/include" - "-I${pkgs.boost.dev}/include" "-I${pkgs.readline.dev}/include" "-I${pkgs.fuse.dev}/include" "-I${pkgs.mbedtls}/include" @@ -36,7 +34,6 @@ CXXFLAGS = [ "-I${pkgs.zlib.dev}/include" "-I${pkgs.sqlite.dev}/include" - "-I${pkgs.boost.dev}/include" "-I${pkgs.readline.dev}/include" "-I${pkgs.fuse.dev}/include" "-I${pkgs.mbedtls}/include" diff --git a/main.cpp b/main.cpp index b5cd5fb..659c8fc 100644 --- a/main.cpp +++ b/main.cpp @@ -28,16 +28,19 @@ #include #include #include +#include +#include +#include -#include +#include "CLI11.hpp" #include "control_tools.h" #include "pclsync_lib.h" #include "pclsync/psettings.h" #include "pclsync/psignal.h" +#include "pclsync/putil.h" -namespace po = boost::program_options; namespace ct = control_tools; namespace cc = console_client; @@ -48,10 +51,9 @@ int main(int argc, char **argv) { psignal_register(SIGSEGV); psignal_register(SIGABRT); psignal_register(SIGBUS); - + std::cout << "pCloud console client (" << version << ")" << std::endl; std::string username = ""; - std::string password = ""; std::string tfa_code = ""; bool daemon = false; bool commands = false; @@ -60,43 +62,41 @@ int main(int argc, char **argv) { bool passwordsw = false; bool save_pass = false; bool crypto = false; + bool passascrypto_sw = false; bool trusted_device = false; - po::variables_map vm; + std::string mountpoint = ""; + uint64_t cache_size_gb = 0; + std::string log_path = ""; + std::string log_level = ""; + std::string fs_event_log = ""; + std::string fuse_opts = ""; + + CLI::App app{"Allowed options"}; + app.set_help_flag("-h,--help", "Show this help message."); + + app.add_option("-u,--username", username, "pCloud account name.") + ->envname("PCLOUD_USER"); + app.add_flag("-p,--password", passwordsw, "Ask for pCloud account password."); + app.add_option("-t,--tfa_code", tfa_code, "pCloud tfa code"); + app.add_flag("-r,--trusted_device", trusted_device, "Trust this device."); + app.add_flag("-c,--crypto", crypto, "Ask for crypto password."); + app.add_flag("-y,--passascrypto", passascrypto_sw, "User password is the same as crypto password."); + app.add_flag("-d,--daemonize", daemon, "Run the process as a background daemon."); + app.add_flag("-o,--commands", commands, "Keep parent process alive and process commands."); + app.add_option("-m,--mountpoint", mountpoint, "Specify where pCloud filesystem is mounted."); + app.add_flag("-k,--commands_only", commands_only, "Open command prompt to interact with running daemon."); + app.add_flag("-n,--newuser", newuser, "Register a new pCloud user account."); + app.add_flag("-s,--savepassword", save_pass, "Save user password in the database."); + app.add_option("--cache-size", cache_size_gb, "Maximum cache size in GB (default: 5GB)."); + app.add_option("--log-path", log_path, "Custom path for debug.log (default: ~/.pcloud/debug.log)."); + app.add_option("--log-level", log_level, "Logging level: NONE, ERROR, WARNING, INFO (default), NOTICE, DEBUG."); + app.add_option("--fs-event-log", fs_event_log, "Path to filesystem events log (default: disabled)."); + app.add_option("-O,--fuse-opts", fuse_opts, "FUSE mount options (e.g., 'allow_other,allow_root')."); + + app.allow_extras(); try { - po::options_description desc("Allowed options"); - desc.add_options() - ("help,h", "Show this help message.") - ("username,u", po::value(&username), "pCloud account name.") - ("password,p", po::bool_switch(&passwordsw), "Ask for pCloud account password.") - ("tfa_code,t", po::value(&tfa_code), "pCloud tfa code") - ("trusted_device,r", po::bool_switch(&trusted_device), "Trust this device.") - ("crypto,c", po::bool_switch(&crypto), "Ask for crypto password.") - ("passascrypto,y", po::value(), "User password is the same as crypto password.") - ("daemonize,d", po::bool_switch(&daemon), "Run the process as a background daemon.") - ("commands ,o", po::bool_switch(&commands), "Keep parent process alive and process commands. ") - ("mountpoint,m", po::value(), "Specify where pCloud filesystem is mounted.") - ("commands_only,k", po::bool_switch(&commands_only), "Open command prompt to interact with running daemon.") - ("newuser,n", po::bool_switch(&newuser), "Register a new pCloud user account.") - ("savepassword,s", po::bool_switch(&save_pass), "Save user password in the database.") - ("cache-size", po::value(), "Maximum cache size in GB (default: 5GB).") - ("log-path", po::value(), "Custom path for debug.log (default: ~/.pcloud/debug.log).") - ("log-level", po::value(), "Logging level: NONE, ERROR, WARNING, INFO (default), NOTICE, DEBUG.") - ("fs-event-log", po::value(), "Path to filesystem events log (default: disabled).") - ("fuse-opts,O", po::value(), "FUSE mount options (e.g., 'allow_other,allow_root')."); - - po::command_line_parser parser{argc, argv}; - po::positional_options_description p; - parser.options(desc).positional(p).allow_unregistered(); - po::parsed_options parsed_options = parser.run(); - po::store(parsed_options, vm); - - po::notify(vm); - - if (vm.count("help")) { - std::cout << desc << "\n"; - return 0; - } + app.parse(argc, argv); if (commands_only) { ct::process_commands(); @@ -104,22 +104,14 @@ int main(int argc, char **argv) { } bool has_piped_input = !isatty(STDIN_FILENO); - if (has_piped_input && !vm.count("help")) { + if (has_piped_input && app.count("-h") == 0 && app.count("--help") == 0) { std::string line; if (std::getline(std::cin, line) && !line.empty()) { return ct::process_command(line); } } - - // Environment variable fallbacks - if (!vm.count("username")) { - const char *env_user = std::getenv("PCLOUD_USER"); - if (env_user && env_user[0]) - username = env_user; - } - - if (!vm.count("username") && username.empty()) { + if (username.empty()) { std::cout << "Username option is required, specify with " << "-u or --username, or set PCLOUD_USER." << std::endl; return 1; @@ -129,7 +121,7 @@ int main(int argc, char **argv) { memset(argv[i], 0, strlen(argv[i])); } if (daemon) { - strncpy(argv[0], "pCloudDriveDeamon", strlen(argv[0])); + strncpy(argv[0], "pCloudDriveDaemon", strlen(argv[0])); } else { strncpy(argv[0], "pCloudDrive", strlen(argv[0])); } @@ -147,8 +139,8 @@ int main(int argc, char **argv) { cc::clibrary::pclsync_lib::get_lib().set_trusted_device(trusted_device); if (crypto) { cc::clibrary::pclsync_lib::get_lib().setup_crypto_ = true; - if (vm.count("passascrypto")) { - cc::clibrary::pclsync_lib::get_lib().set_crypto_pass(password); + if (passascrypto_sw) { + cc::clibrary::pclsync_lib::get_lib().set_crypto_pass(cc::clibrary::pclsync_lib::get_lib().get_password()); } else { const char *env_crypto = std::getenv("PCLOUD_CRYPTO_PASSWORD"); if (env_crypto && env_crypto[0]) { @@ -161,13 +153,11 @@ int main(int argc, char **argv) { } else cc::clibrary::pclsync_lib::get_lib().setup_crypto_ = false; - if (vm.count("mountpoint")) { - cc::clibrary::pclsync_lib::get_lib().set_mount( - vm["mountpoint"].as()); + if (app.count("--mountpoint") > 0 || app.count("-m") > 0) { + cc::clibrary::pclsync_lib::get_lib().set_mount(mountpoint); } - if (vm.count("cache-size")) { - uint64_t cache_size_gb = vm["cache-size"].as(); + if (app.count("--cache-size") > 0) { /* Validate cache size: minimum 1GB, maximum 1TB */ if (cache_size_gb < 1 || cache_size_gb > 1024) { std::cerr << "error: cache-size must be between 1 and 1024 GB" << std::endl; @@ -180,8 +170,7 @@ int main(int argc, char **argv) { setenv("PCLOUD_CACHE_SIZE", cache_size_str, 1); } - if (vm.count("log-path")) { - std::string log_path = vm["log-path"].as(); + if (app.count("--log-path") > 0) { /* Validate log path: must not be empty or start with /etc or /sys */ if (log_path.empty() || log_path.compare(0, 5, "/etc/") == 0 || log_path.compare(0, 5, "/sys/") == 0) { std::cerr << "error: invalid log-path" << std::endl; @@ -190,15 +179,14 @@ int main(int argc, char **argv) { setenv("PCLOUD_LOG_PATH", log_path.c_str(), 1); } - if (vm.count("log-level")) { - setenv("PCLOUD_LOG_LEVEL", vm["log-level"].as().c_str(), 1); + if (app.count("--log-level") > 0) { + setenv("PCLOUD_LOG_LEVEL", log_level.c_str(), 1); } else { /* Set default log level to INFO */ setenv("PCLOUD_LOG_LEVEL", "INFO", 1); } - if (vm.count("fs-event-log")) { - std::string fs_event_log = vm["fs-event-log"].as(); + if (app.count("--fs-event-log") > 0) { /* Validate fs-event-log path: must not be empty or start with /etc or /sys */ if (fs_event_log.empty() || fs_event_log.compare(0, 5, "/etc/") == 0 || fs_event_log.compare(0, 5, "/sys/") == 0) { std::cerr << "error: invalid fs-event-log" << std::endl; @@ -207,14 +195,16 @@ int main(int argc, char **argv) { setenv("PCLOUD_FS_EVENT_LOG", fs_event_log.c_str(), 1); } - if (vm.count("fuse-opts")) { - setenv("PCLOUD_FUSE_OPTS", vm["fuse-opts"].as().c_str(), 1); + if (app.count("--fuse-opts") > 0 || app.count("-O") > 0) { + setenv("PCLOUD_FUSE_OPTS", fuse_opts.c_str(), 1); } cc::clibrary::pclsync_lib::get_lib().newuser_ = newuser; cc::clibrary::pclsync_lib::get_lib().set_savepass(save_pass); cc::clibrary::pclsync_lib::get_lib().set_daemon(daemon); - } catch (std::exception &e) { + } catch (const CLI::ParseError &e) { + return app.exit(e); + } catch (const std::exception &e) { std::cerr << "error: " << e.what() << std::endl; return 1; } catch (...) { @@ -233,5 +223,13 @@ int main(int argc, char **argv) { } } + if (!tfa_code.empty()) { + putil_wipe(&tfa_code[0], tfa_code.size()); + } + + cc::clibrary::pclsync_lib::get_lib().wipe_password(); + cc::clibrary::pclsync_lib::get_lib().wipe_crypto_pass(); + cc::clibrary::pclsync_lib::get_lib().wipe_tfa_code(); + return 0; }