diff --git a/.gitignore b/.gitignore index e0292b19..67fba194 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ *.o *.a + +# An in-tree "cmake ." of the vendored mbedtls generates a Makefile per configured +# subdirectory. Its own .gitignore covers CMakeCache.txt/CMakeFiles/cmake_install.cmake +# but not these; the tree tracks only CMakeLists.txt files, never a Makefile. +/pCloudCC/lib/mbedtls/**/Makefile diff --git a/pCloudCC/lib/pclsync/pdiff.c b/pCloudCC/lib/pclsync/pdiff.c index c7f00407..eade9375 100644 --- a/pCloudCC/lib/pclsync/pdiff.c +++ b/pCloudCC/lib/pclsync/pdiff.c @@ -482,6 +482,18 @@ static psync_socket *get_connected_socket(){ digest=0; continue; } + else if (psync_my_2fa_code_type && psync_my_2fa_code[0]){ + /* Unexpected error while a two factor code was pending. The API did not + * accept the code we just sent -- 1022 "Please provide 'code'" is what a + * malformed one gets. Keeping the code would resend it every + * PSYNC_SLEEP_BEFORE_RECONNECT forever, with no way for the user to + * correct it, so discard it and ask again. */ + debug(D_WARNING, "got %lu while submitting a two factor code, discarding it", (unsigned long)result); + psync_my_2fa_code_type=0; + psync_my_2fa_code[0]=0; + psync_set_status(PSTATUS_TYPE_AUTH, PSTATUS_AUTH_BADCODE); + psync_wait_status(PSTATUS_TYPE_AUTH, PSTATUS_AUTH_PROVIDED); + } else psync_milisleep(PSYNC_SLEEP_BEFORE_RECONNECT); continue; diff --git a/pCloudCC/pclsync_lib.cpp b/pCloudCC/pclsync_lib.cpp index 4f49a59e..5d353b42 100644 --- a/pCloudCC/pclsync_lib.cpp +++ b/pCloudCC/pclsync_lib.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -163,10 +164,132 @@ static char const * status2string (uint32_t status){ case PSTATUS_SCANNING: return "SCANNING"; case PSTATUS_USER_MISMATCH: return "USER_MISMATCH"; case PSTATUS_ACCOUT_EXPIRED: return "ACCOUT_EXPIRED"; + case PSTATUS_TFA_REQUIRED: return "TFA_REQUIRED"; + case PSTATUS_BAD_TFA_CODE: return "BAD_TFA_CODE"; default :return "Unrecognized status"; } } +/* Keep in sync with psync_my_2fa_code in plibs.c, which is a char[32]; the + * library silently truncates anything longer. */ +static const size_t PSYNC_TFA_CODE_MAX=31; + +static std::string trim(const std::string &s){ + size_t b=s.find_first_not_of(" \t\r\n"); + if (b==std::string::npos) + return ""; + return s.substr(b, s.find_last_not_of(" \t\r\n")-b+1); +} + +static bool starts_with_ci(const std::string &s, const char *prefix){ + size_t n=strlen(prefix); + if (s.size()PSYNC_TFA_CODE_MAX){ + std::cout << "That code is too long (" << stripped.size() << " characters, max " + << PSYNC_TFA_CODE_MAX << ")." << std::endl; + return false; + } + if (!is_recovery){ + for (size_t i=0; i12){ + std::cout << "A login code sent to a device is 6 digits." << std::endl; + return false; + } + } + + out=stripped; + return true; +} + +static void prompt_and_submit_tfa(bool request_code){ + if (request_code){ + plogged_device_list_t *devs=NULL; + int rc=psync_tfa_send_nofification(&devs); + if (rc==0 && devs && devs->entrycnt>0){ + std::cout << "A login code was sent via notification to:" << std::endl; + for (uint32_t i=0; ientrycnt; ++i) + std::cout << " - " << devs->devices[i].name << std::endl; + psync_free(devs); + } else { + if (devs) psync_free(devs); + char *country_code=NULL, *phone=NULL; + rc=psync_tfa_send_sms(&country_code, &phone); + if (rc==0){ + std::cout << "A login code was sent via SMS"; + if (country_code && phone) + std::cout << " to +" << country_code << " " << phone; + std::cout << "." << std::endl; + } else { + std::cout << "Could not auto-send a code (notification rc=" << rc + << "). If you have a recovery code, enter it prefixed with 'r:'." << std::endl; + } + if (country_code) psync_free(country_code); + if (phone) psync_free(phone); + } + } + std::string code; + int is_recovery=0; + while (1){ + std::cout << "Enter login code (prefix with 'r:' for recovery code): " << std::flush; + std::string line; + if (!std::getline(std::cin, line)){ + std::cout << "No input available to read the login code from." << std::endl; + exit(1); + } + if (normalize_tfa_code(line, code, is_recovery)) + break; + } + if (is_recovery) + std::cout << "Submitting as a recovery code." << std::endl; + psync_tfa_set_code(code.c_str(), 1 /*trust this device*/, is_recovery); +} + static void status_change(pstatus_t* status) { static int cryptocheck=0; static int mount_set=0; @@ -207,6 +330,21 @@ static void status_change(pstatus_t* status) { } } + else if (status->status==PSTATUS_TFA_REQUIRED){ + if (clib::pclsync_lib::get_lib().is_daemon()){ + std::cout << "TFA required but running as daemon; cannot prompt for code." << std::endl; + exit(1); + } + prompt_and_submit_tfa(true); + } + else if (status->status==PSTATUS_BAD_TFA_CODE){ + if (clib::pclsync_lib::get_lib().is_daemon()){ + std::cout << "Bad TFA code and running as daemon; cannot re-prompt." << std::endl; + exit(1); + } + std::cout << "Code rejected, try again." << std::endl; + prompt_and_submit_tfa(false); + } if (status->status==PSTATUS_READY || status->status==PSTATUS_UPLOADING || status->status==PSTATUS_DOWNLOADING || status->status==PSTATUS_DOWNLOADINGANDUPLOADING){ if (!cryptocheck){ cryptocheck=1; diff --git a/pCloudCC/pclsync_lib.h b/pCloudCC/pclsync_lib.h index 17d8b417..a57856c7 100644 --- a/pCloudCC/pclsync_lib.h +++ b/pCloudCC/pclsync_lib.h @@ -57,6 +57,7 @@ namespace console_client { void setupsetup_crypto(bool p) {setup_crypto_ = p;} void set_newuser(bool p) {newuser_ = p;} void set_daemon(bool p) {daemon_ = p;} + bool is_daemon() const { return daemon_; } void set_status_callback(status_callback_t p) {status_callback_ = p;} //Console void get_pass_from_console();