* 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> * Add auth command: supply password to running daemon Mirrors the tfa command pattern. When PSTATUS_LOGIN_REQUIRED fires in daemon mode, read_password() now logs a syslog notice and blocks on a condition variable rather than calling exit(1). The new `auth <password>` control command (SENDAUTH RPC type) signals the condvar and unblocks the daemon. Usage: echo 'auth PASSWORD' | pcloudcc -k The password is wiped from the CLI-side buffer immediately after the RPC call. putil_wipe() is used for both the tfa and auth transient strings on the sending side. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add status command: show current sync state from CLI Adds a `status` (alias `st`) command to the interactive CLI and to the pcloudcc -k control interface. The daemon-side handler calls psync_get_status(), formats the status name plus download/upload strings, and writes the result to shared memory. The client reads and prints it. Example output: Status: READY Download: idle Upload: idle Also works non-interactively: echo 'status' | pcloudcc -k Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Levi Neely <lkn@darkstar.example.net> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e3b9f7bfd8
commit
d6858a0166
|
|
@ -41,6 +41,7 @@
|
|||
|
||||
#include "pclsync_lib.h"
|
||||
#include "pclsync/pshm.h"
|
||||
#include "pclsync/putil.h"
|
||||
#include "pclsync/pfoldersync.h"
|
||||
#include "pclsync/pcommands.h"
|
||||
|
||||
|
|
@ -60,6 +61,9 @@ 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",
|
||||
"status", "st",
|
||||
"tfa",
|
||||
"auth",
|
||||
"finalize", "f",
|
||||
"quit", "q",
|
||||
nullptr
|
||||
|
|
@ -104,6 +108,9 @@ 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
|
||||
<< " status(st): Show current sync state" << std::endl
|
||||
<< " tfa <code>: Supply 2FA code to a running daemon" << std::endl
|
||||
<< " auth <password>: Supply password to a running daemon" << std::endl
|
||||
<< " finalize(f): Kill daemon and quit" << std::endl
|
||||
<< " quit(q): Exit this program" << std::endl;
|
||||
});
|
||||
|
|
@ -151,6 +158,75 @@ 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;
|
||||
});
|
||||
|
||||
// auth command
|
||||
auto auth_cmd = app->add_subcommand("auth", "Supply password to a running daemon");
|
||||
static std::string auth_pass_input;
|
||||
auth_cmd->add_option("password", auth_pass_input, "Password")->required();
|
||||
auth_cmd->callback([] {
|
||||
char *errm = NULL;
|
||||
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());
|
||||
auth_pass_input.clear();
|
||||
if (result != 0) {
|
||||
std::cerr << "Failed to send auth: " << (errm ? errm : "no message") << std::endl;
|
||||
if (errm) { free(errm); }
|
||||
delete rpc;
|
||||
return result;
|
||||
}
|
||||
delete rpc;
|
||||
std::cout << "Auth sent." << std::endl;
|
||||
if (errm) { free(errm); }
|
||||
return 0;
|
||||
});
|
||||
|
||||
// status command
|
||||
app->add_subcommand("status", "Show current sync state")->alias("st")->callback([] {
|
||||
char *errm = NULL;
|
||||
size_t errm_size = 0;
|
||||
RpcClient *rpc = new RpcClient();
|
||||
if (int result = rpc->Call(GETSTATUS, "", &errm, &errm_size) != 0) {
|
||||
std::cerr << "Status failed: " << (errm ? errm : "no message") << std::endl;
|
||||
if (errm) { free(errm); }
|
||||
delete rpc;
|
||||
return result;
|
||||
}
|
||||
delete rpc;
|
||||
|
||||
char *status_str = nullptr;
|
||||
if (pshm_read((void**)&status_str, nullptr) && status_str) {
|
||||
std::cout << status_str << std::endl;
|
||||
free(status_str);
|
||||
} else {
|
||||
std::cerr << "Failed to read status from daemon." << std::endl;
|
||||
if (errm) { free(errm); }
|
||||
return -1;
|
||||
}
|
||||
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,8 @@ enum command_ids_ {
|
|||
STOPSYNC,
|
||||
CHECKPENDING,
|
||||
SYNCPAUSE,
|
||||
SYNCRESUME
|
||||
SYNCRESUME,
|
||||
SENDTFA,
|
||||
SENDAUTH,
|
||||
GETSTATUS
|
||||
};
|
||||
|
|
|
|||
107
pclsync_lib.cpp
107
pclsync_lib.cpp
|
|
@ -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,10 @@ 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 pthread_mutex_t auth_mtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t auth_cond = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
static const std::string client_name = "pCloud CC v3.0.0";
|
||||
|
||||
|
|
@ -161,16 +166,70 @@ clib::pclsync_lib &clib::pclsync_lib::get_lib() {
|
|||
}
|
||||
|
||||
void clib::pclsync_lib::read_password() {
|
||||
if (daemon_) {
|
||||
syslog(LOG_NOTICE,
|
||||
"pcloudcc: password required. "
|
||||
"Provide with: echo 'auth PASSWORD' | pcloudcc -k");
|
||||
pthread_mutex_lock(&auth_mtx);
|
||||
while (password_.empty()) {
|
||||
pthread_cond_wait(&auth_cond, &auth_mtx);
|
||||
}
|
||||
pthread_mutex_unlock(&auth_mtx);
|
||||
return;
|
||||
}
|
||||
read_from_stdin(password_);
|
||||
}
|
||||
|
||||
void clib::pclsync_lib::read_tfa_code()
|
||||
int clib::pclsync_lib::receive_auth(const char *pass) {
|
||||
pthread_mutex_lock(&auth_mtx);
|
||||
get_lib().password_ = std::string(pass);
|
||||
pthread_cond_signal(&auth_cond);
|
||||
pthread_mutex_unlock(&auth_mtx);
|
||||
std::cout << "Auth received." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
@ -199,6 +258,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 +447,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();
|
||||
|
|
@ -467,6 +540,25 @@ int clib::pclsync_lib::check_pending(const char *unused) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int clib::pclsync_lib::get_status(const char *unused) {
|
||||
(void)unused;
|
||||
|
||||
pstatus_t st;
|
||||
psync_get_status(&st);
|
||||
|
||||
char buf[512];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Status: %s\n"
|
||||
"Download: %s\n"
|
||||
"Upload: %s",
|
||||
status2string(st.status),
|
||||
st.downloadstr ? st.downloadstr : "",
|
||||
st.uploadstr ? st.uploadstr : "");
|
||||
|
||||
pshm_write(buf, strlen(buf) + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int clib::pclsync_lib::finalize(const char *unused) {
|
||||
(void)unused;
|
||||
psync_destroy();
|
||||
|
|
@ -602,6 +694,9 @@ 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);
|
||||
prpc_register(SENDAUTH, &receive_auth);
|
||||
prpc_register(GETSTATUS, &get_status);
|
||||
|
||||
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,9 @@ 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);
|
||||
static int receive_auth(const char *pass);
|
||||
static int get_status(const char *unused);
|
||||
|
||||
int logout();
|
||||
int unlink();
|
||||
|
|
|
|||
Loading…
Reference in New Issue