From bccf00ba5248d5e07817d17ae9e7b224e57da46e Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Mon, 2 Mar 2026 21:11:07 +0100 Subject: [PATCH] Fix #90: Support 2FA when running as a daemon In daemon mode, when PSTATUS_TFA_REQUIRED fires: - Automatically send an SMS 2FA code via psync_tfa_send_sms() - Log delivery details to syslog with instructions to use `echo 'tfa CODE' | pcloudcc -k` - Block on a condition variable until the code arrives via RPC Add a new `tfa ` command to the pcloudcc -k control interface. This sends the code to the daemon over the existing Unix socket RPC channel (SENDTFA message type). Bad codes (PSTATUS_BAD_TFA_CODE) are handled: the daemon logs a syslog warning and waits for a corrected code without re-sending SMS. Devices are trusted by default (trusted=1 in psync_tfa_set_code) so repeated 2FA prompts are avoided for the configured trust period. TFA codes are wiped from memory after use. Co-Authored-By: Claude Sonnet 4.6 --- control_tools.cpp | 22 ++++++++++++++ pclsync/pcommands.h | 3 +- pclsync_lib.cpp | 70 +++++++++++++++++++++++++++++++++++++++------ pclsync_lib.h | 5 ++-- 4 files changed, 88 insertions(+), 12 deletions(-) diff --git a/control_tools.cpp b/control_tools.cpp index 59fd253..9ceee38 100644 --- a/control_tools.cpp +++ b/control_tools.cpp @@ -60,6 +60,7 @@ static char* command_generator(const char* text, int state) { "sync", "sync ls", "sync add", "sync remove", "sync rm", "sync pause", "sync resume", "s", "s ls", "s add", "s remove", "s rm", "s pause", "s resume", "pending", "p", + "tfa", "finalize", "f", "quit", "q", nullptr @@ -104,6 +105,7 @@ void setup_app(CLI::App *app) { << " pause: Pause syncing (filesystem remains mounted)" << std::endl << " resume: Resume syncing" << std::endl << " pending(p): Check for pending transfers" << std::endl + << " tfa : Supply 2FA code to a running daemon" << std::endl << " finalize(f): Kill daemon and quit" << std::endl << " quit(q): Exit this program" << std::endl; }); @@ -151,6 +153,26 @@ void setup_app(CLI::App *app) { exit(0); }); + // tfa command + auto tfa_cmd = app->add_subcommand("tfa", "Supply 2FA code to a running daemon"); + static std::string tfa_code_input; + tfa_cmd->add_option("code", tfa_code_input, "2FA code")->required(); + tfa_cmd->callback([] { + char *errm = NULL; + size_t errm_size = 0; + RpcClient *rpc = new RpcClient(); + if (int result = rpc->Call(SENDTFA, tfa_code_input.c_str(), &errm, &errm_size) != 0) { + std::cerr << "Failed to send 2FA code: " << (errm ? errm : "no message") << std::endl; + if (errm) { free(errm); } + delete rpc; + return result; + } + delete rpc; + std::cout << "2FA code sent." << std::endl; + if (errm) { free(errm); } + return 0; + }); + // pending command app->add_subcommand("pending", "Check for pending transfers")->alias("p")->callback([] { char *errm = NULL; diff --git a/pclsync/pcommands.h b/pclsync/pcommands.h index 35f07ee..6935561 100644 --- a/pclsync/pcommands.h +++ b/pclsync/pcommands.h @@ -7,5 +7,6 @@ enum command_ids_ { STOPSYNC, CHECKPENDING, SYNCPAUSE, - SYNCRESUME + SYNCRESUME, + SENDTFA }; diff --git a/pclsync_lib.cpp b/pclsync_lib.cpp index df2dd1e..fa057bf 100644 --- a/pclsync_lib.cpp +++ b/pclsync_lib.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,8 @@ namespace cc = console_client; namespace clib = cc::clibrary; static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t tfa_mtx = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t tfa_cond = PTHREAD_COND_INITIALIZER; static const std::string client_name = "pCloud CC v3.0.0"; @@ -164,22 +167,56 @@ void clib::pclsync_lib::read_password() { read_from_stdin(password_); } -void clib::pclsync_lib::read_tfa_code() +void clib::pclsync_lib::read_tfa_code(bool auto_sms) { if (daemon_) { - std::cout << "Not able to read 2fa code when started as daemon." << std::endl; - exit(1); + if (auto_sms) { + char *country_code = nullptr; + char *phone_number = nullptr; + int result = psync_tfa_send_sms(&country_code, &phone_number); + if (result == 0) { + if (country_code && phone_number) { + syslog(LOG_NOTICE, + "pcloudcc: 2FA required. SMS sent to +%s %s. " + "Provide code with: echo 'tfa CODE' | pcloudcc -k", + country_code, phone_number); + free(country_code); + free(phone_number); + } else { + syslog(LOG_NOTICE, + "pcloudcc: 2FA required. SMS sent. " + "Provide code with: echo 'tfa CODE' | pcloudcc -k"); + } + } else { + syslog(LOG_WARNING, + "pcloudcc: 2FA required but SMS failed (error %d). " + "Provide code with: echo 'tfa CODE' | pcloudcc -k", result); + } + } else { + syslog(LOG_WARNING, + "pcloudcc: 2FA code was rejected. " + "Provide correct code with: echo 'tfa CODE' | pcloudcc -k"); + } + + // Block until code arrives via RPC (pcloudcc -k) + pthread_mutex_lock(&tfa_mtx); + while (tfa_code_.empty()) { + pthread_cond_wait(&tfa_cond, &tfa_mtx); + } + pthread_mutex_unlock(&tfa_mtx); + return; } - + + // Interactive mode std::cout << "2FA required. Enter code from authenticator app, or type 'sms' to receive code via SMS: "; std::string input; std::getline(std::cin, input); - + if (input == "sms" || input == "SMS") { char *country_code = nullptr; char *phone_number = nullptr; int result = psync_tfa_send_sms(&country_code, &phone_number); - + if (result == 0) { if (country_code && phone_number) { std::cout << "SMS sent to +" << country_code << " " << phone_number << std::endl; @@ -199,6 +236,15 @@ void clib::pclsync_lib::read_tfa_code() } } +int clib::pclsync_lib::receive_tfa_code(const char *code) { + pthread_mutex_lock(&tfa_mtx); + get_lib().tfa_code_ = std::string(code); + pthread_cond_signal(&tfa_cond); + pthread_mutex_unlock(&tfa_mtx); + std::cout << "2FA code received." << std::endl; + return 0; +} + void clib::pclsync_lib::read_cryptopass() { read_from_stdin(crypto_pass_); } @@ -379,10 +425,15 @@ static void status_change(pstatus_t *status) { if (clib::pclsync_lib::get_lib().get_tfa_code().empty()) { clib::pclsync_lib::get_lib().read_tfa_code(); } - psync_tfa_set_code(clib::pclsync_lib::get_lib().get_tfa_code().c_str(), - clib::pclsync_lib::get_lib().get_trusted_device(), - 0); + 1 /* trusted */, 0); + clib::pclsync_lib::get_lib().wipe_tfa_code(); + } else if (status->status == PSTATUS_BAD_TFA_CODE) { + clib::pclsync_lib::get_lib().wipe_tfa_code(); + clib::pclsync_lib::get_lib().read_tfa_code(false /* auto_sms */); + psync_tfa_set_code(clib::pclsync_lib::get_lib().get_tfa_code().c_str(), + 1 /* trusted */, 0); + clib::pclsync_lib::get_lib().wipe_tfa_code(); } else if (status->status == PSTATUS_BAD_LOGIN_DATA) { if (!clib::pclsync_lib::get_lib().newuser_) { clib::pclsync_lib::get_lib().read_password(); @@ -602,6 +653,7 @@ int clib::pclsync_lib::init() { prpc_register(STOPSYNC, &remove_sync_folder); prpc_register(SYNCPAUSE, &pause_sync); prpc_register(SYNCRESUME, &resume_sync); + prpc_register(SENDTFA, &receive_tfa_code); return 0; } diff --git a/pclsync_lib.h b/pclsync_lib.h index 59a05ac..ba72268 100644 --- a/pclsync_lib.h +++ b/pclsync_lib.h @@ -47,7 +47,7 @@ public: ~pclsync_lib(); pclsync_lib(); - bool trusted_device_; + bool trusted_device_ = true; bool crypto_on_; bool save_pass_; bool setup_crypto_; @@ -87,7 +87,7 @@ public: static pclsync_lib &get_lib(); // Console - void read_tfa_code(); + void read_tfa_code(bool auto_sms = true); void read_password(); void read_cryptopass(); @@ -104,6 +104,7 @@ public: static int remove_sync_folder(const char *path); static int pause_sync(const char *unused); static int resume_sync(const char *unused); + static int receive_tfa_code(const char *code); int logout(); int unlink();