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:
parent
e3b9f7bfd8
commit
bccf00ba52
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -7,5 +7,6 @@ enum command_ids_ {
|
|||
STOPSYNC,
|
||||
CHECKPENDING,
|
||||
SYNCPAUSE,
|
||||
SYNCRESUME
|
||||
SYNCRESUME,
|
||||
SENDTFA
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue