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 <code>` 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 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-02 21:11:07 +01:00
parent e3b9f7bfd8
commit bccf00ba52
4 changed files with 88 additions and 12 deletions

View File

@ -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 <code>: 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;

View File

@ -7,5 +7,6 @@ enum command_ids_ {
STOPSYNC,
CHECKPENDING,
SYNCPAUSE,
SYNCRESUME
SYNCRESUME,
SENDTFA
};

View File

@ -34,6 +34,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
@ -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;
}

View File

@ -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();