#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "popen2.h" #include "tcoin_defs.cpp" /* OLD VALUES */ /* #define TCOIN_PATH "/home/login/tcoin" #define TCOIN_MSG_PATH "/home/login/tcoin/messages/" #define TCOIN_SALT_PATH "/home/login/tcoin/salts/" #define TCOIN_PASS_PATH "/home/login/tcoin/passwords/" #define TCOIN_PROG_ACT_PATH "/home/login/tcoin/program_accounting/" #define PROG_ACT_W_SLASH "program_accounting/" #define PCOIN_KEY_PATH_W_SLASH "/home/login/bin/pcoin_keys/" #define TCOIN_CODEZ_PATH "/home/login/bin/tcoin_codez" #define TCOIN_BIN_PATH_W_SPACE "/home/login/bin/tcoin " #define TCOIN_PATH_W_SLASH "/home/login/tcoin/" #define TCOIN_SCRYPT_PATH "/home/login/bin/scrypt" */ #define LS_HOME_CMD "/bin/ls /home" #define BIN_ECHO_CMD "/bin/echo $$" #ifndef KROWBAR_OFF #define KROWBAR_SCORE_PATH "/home/krowbar/Code/irc/data/tildescores.txt" #define JU_SCORE_PATH "/home/jmjl/dev/juju/data/tildescores.txt" #endif #if !defined(TILDEINSTITUTE) && !defined(TILDEGURU) #define WHOAMI_PATH "/usr/bin/whoami" #else #define WHOAMI_PATH "/usr/bin/getent passwd $(/usr/bin/id -ru) | /usr/bin/cut -d: -f1" #endif #ifndef DA_OFF #define TROIDO_DACOINS_CMD "cd /home/troido/daily_adventure/client/ && /home/troido/daily_adventure/client/daclient printinfo 2>&1 | /bin/grep -oP '(?<=\"Coins\", )[[:digit:]]+'" #endif #ifndef MINERCOIN_OFF #define MINERCOIN_CMD_PRE_USERNAME "/bin/grep -oP '(?<=\"~" #define MINERCOIN_CMD_PRE_USERNAME2 "/bin/grep -oP '(?<=\"" #define MINERCOIN_CMD_POST_USERNAME "\": )[[:digit:]]+' /home/minerobber/Code/minerbot/minercoin.json" #endif #define USERNAME_LENGTH_LIMIT 25 #define TCOIN_MSG_LENGTH_LIMIT 280 #define ERR_NO_INIT 4 #define ERR_ALREADY_ON 40 #define ERR_NO_PWD_FILE 10 #define ERR_WRONG_PWD 20 #define ERR_WRONG_PWD2 30 #define ERR_ALREADY_OFF 20 #define ERR_DAMAGED_SALT_FILE 40 #define ERR_2_SALT_FILES 60 #define ERR_NOT_LOGGED_IN 5 #define ERR_MAIN_SEND_TOO_FEW_ARGS 6 #define ERR_MAIN_SEND_TOO_MANY_ARGS 7 #define ERR_MAIN_SEND_TOO_MANY_ARGS_MSG 104 #define ERR_MAIN_MSG_TOO_LONG 19 #define ERR_SILENTSEND 2 #define ERR_UNKNOWN_ARG 3 #define ERR_TCOIN_TO_SELF 5 #define ERR_RECEIVER_BLOCKED 4 #define ERR_NEGATIVE_SEND_AMOUNT 2 #define ERR_INSUFFICIENT_FUNDS 3 #define ERR_RECEIVER_NOT_FOUND 1 #define ERR_GET_FILE_VALUE_COULDNT_OPEN_BASEFILE 1 #define ERR_ADD_FILE_VALUE_COULDNT_OPEN_BASEFILE 1 #define ERR_ADD_FILE_VALUE_FATAL 999 #define ERR_GET_FILE_VALUE_COULDNT_OPEN_BASEFILE 1 #define ERR_ADD_FILE_VALUE_COULDNT_OPEN_BASEFILE 1 #define ERR_SEND_MESSAGE_RECEIVER_MSG_FILE_UNABLE_TO_BE_UPDATED_FATAL 101 #define ERR_SEND_MESSAGE_PROGRAM_RECEIVER_MSG_FILE_UNABLE_TO_BE_UPDATED_FATAL 103 #define ERR_SEND_MESSAGE_SENDER_MSG_FILE_UNABLE_TO_BE_UPDATED_FATAL 102 void exit_program(const int error_number) { // Cleanup to do before exiting the program //TODO: Print the actual error code to stderr // Finally, we can exit std::exit(error_number); } //custom function to convert ("abcd.de") to ("abcde") long long int strtol100(const char* amount_str) { long long int result = 0; int multiplier = 1; int i=0; if(amount_str[i]=='-') { multiplier = -1; ++i; } else if(amount_str[i]=='+') { ++i; } else if(amount_str[i]=='\0') //empty string { return (long long int)(0); } //before the decimal point while(amount_str[i]!='.') { if(amount_str[i]>='0' && amount_str[i]<='9') { result = result*10 + ((long long int)(amount_str[i]) - (long long int)('0')); } else if(amount_str[i]=='\0') //e.g. 500 { result *= (multiplier*100); //multiplied by 100 to get centitildecoins return result; } else //error { return (long long int)(0); } ++i; } //at decimal point ++i; //after decimal point (i.e., tenth's place) if(amount_str[i]>='0' && amount_str[i]<='9') result = result*100 + ((long long int)(amount_str[i]) - (long long int)('0'))*10; else if(amount_str[i]=='\0') //e.g. "500." { result *= (multiplier*100); //multiplied by 100 to get centitildecoins return result; } else //error { return (long long int)(0); } //before hundredth's place ++i; //at hundredth's place if(amount_str[i]>='0' && amount_str[i]<='9') result += ((long long int)(amount_str[i]) - (long long int)('0')); else if(amount_str[i]=='\0') //e.g. "500.3" { result *= multiplier; return result; } else //error { return (long long int)(0); } result *= multiplier; return result; } //custom function to convert integer string to long long int fast long long int strtol_fast(const char* amount_str) { long long int result = 0; int multiplier = 1; int i=0; if(amount_str[i]=='-') { multiplier = -1; ++i; } else if(amount_str[i]=='+') { ++i; } else if(amount_str[i]=='\0') //empty string { return (long long int)(0); } //before the end of the string while(amount_str[i]>='0' && amount_str[i]<='9') { result = result*10 + ((long long int)(amount_str[i]) - (long long int)('0')); ++i; } result *= multiplier; return result; } //string constant time compare (only checks for equality (return 0 if equal)) int strctcmp(const char*a, const char*b) { if(!(*a) || !(*b)) //*a or *b are empty (NULL characters) return 1; int r = 0; for (; *a && *b; ++a, ++b) { r |= *a != *b; } return r; } std::string exec(const char* cmd) { int i=0; do { std::array buffer; std::string result; try { std::shared_ptr pipe(popen(cmd, "r"), pclose); if (!pipe) { ++i; continue; } while (!feof(pipe.get())) { if (fgets(buffer.data(), 128, pipe.get()) != nullptr) result += buffer.data(); } return result; } catch (const std::exception& e) { ++i; std::cout << "popen() failed - " << i << " (exception " << e.what() << ")" << std::endl; continue; } } while(i < 100); throw std::runtime_error("popen() failed!"); return std::string(""); //dummy line, never executes } std::string exec2(const char* cmd, std::string input) { std::string data_length_cmd_str = std::string(cmd) + std::string(PIPED_WORD_COUNT_CMD); const char* data_length_cmd_cstr = data_length_cmd_str.c_str(); std::string data_length_str = exec(data_length_cmd_cstr); long long int data_length = strtol_fast(data_length_str.c_str())+1; std::vector buffer; buffer.reserve(data_length); std::string result; files_t *fp = popen2(cmd); if (!fp) throw std::runtime_error("popen2() failed!"); fputs((input+std::string("\n")).c_str(), fp->in); std::fflush(fp->in); while (!feof(fp->out)) { if (fgets(buffer.data(), data_length, fp->out) != nullptr) result += buffer.data(); } pclose2(fp); return result; } long long int get_file_value(const char* file_name) { char* file_path = new char[strlen(file_name)+sizeof(TCOIN_PATH_W_SLASH)+4]; std::strcpy(file_path, TCOIN_PATH_W_SLASH); std::strcat(file_path, file_name); std::strcat(file_path, ".txt"); std::ifstream file(file_path); if(!file) { if(!strcmp(file_name, "base/base")) { std::cerr << "\nError! Could not open file at " << file_path << "!\n\n"; exit_program(ERR_GET_FILE_VALUE_COULDNT_OPEN_BASEFILE); } else { std::cerr << "\nError! Could not open file at " << file_path << "! Assuming its internal content is \"0\\n\".\n\n"; return (long long int)(0); } } std::ostringstream ss; ss << file.rdbuf(); file.close(); delete[] file_path; return strtol_fast(ss.str().c_str()); } int add_file_value(const char* file_name, const long long int &value_to_add, const long long int &base_amount) { char* file_path = new char[strlen(file_name)+sizeof(TCOIN_PATH_W_SLASH)+4]; char* temp_file_path = new char[strlen(file_name)+sizeof(TCOIN_PATH_W_SLASH)+8]; std::strcpy(file_path, TCOIN_PATH_W_SLASH); std::strcat(file_path, file_name); std::strcpy(temp_file_path, file_path); std::strcat(file_path, ".txt"); std::strcat(temp_file_path, "_tmp"); std::strcat(temp_file_path, ".txt"); std::ifstream file(file_path); if(!file) { if(!strcmp(file_name, "base/base")) { std::cerr << "\nError! Could not open file at " << file_path << "!\n\n"; file.close(); delete[] file_path; delete[] temp_file_path; exit_program(ERR_ADD_FILE_VALUE_COULDNT_OPEN_BASEFILE); } } std::ostringstream ss; ss << file.rdbuf(); long long int old_value = strtol_fast(ss.str().c_str()); //sufficient funds check if(value_to_add < 0 && (old_value + base_amount + value_to_add < 0)) { file.close(); delete[] file_path; delete[] temp_file_path; return 1; } long long int new_value = old_value + value_to_add; // Writing new value to file file.close(); std::ofstream file2(temp_file_path); file2 << new_value << "\n"; file2.close(); chmod(temp_file_path, (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); if(!file2) //error { std::cerr << "Fatal error " << ERR_ADD_FILE_VALUE_FATAL << ": the file \"" << file_name << "\" was unable to be updated. Please contact " << TCOIN_ERR_CONTACT_EMAIL << " to report this error (because it requires manual recovery)."; exit_program(ERR_ADD_FILE_VALUE_FATAL); } else { std::remove(file_path); while(1) { if(!std::rename(temp_file_path, file_path)) { chmod(file_path, (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); break; } } } delete[] file_path; delete[] temp_file_path; return 0; } std::string get_username() { static std::string username; if(username.empty()) { username = exec(WHOAMI_PATH); username.pop_back(); //to get rid of newline at the end } return username; } std::string formatted_amount(long long int const& amount, char const* appended_chars_default = "", char const* appended_chars_singular = "") { std::ostringstream ss; bool is_non_negative = amount >= 0 ? true : false; if(!is_non_negative) //i.e., is negative ss << "-"; if(((is_non_negative*2-1)*amount) % 100 == 0) ss << (is_non_negative*2-1)*amount/100; else if((is_non_negative*2-1)*amount % 100 < 10) ss << (is_non_negative*2-1)*amount/100 << ".0" << (is_non_negative*2-1)*amount % 100; else ss << (is_non_negative*2-1)*amount/100 << "." << (is_non_negative*2-1)*amount % 100; if(((is_non_negative*2-1)*amount == 100) && strcmp(appended_chars_singular, "")) ss << appended_chars_singular; else ss << appended_chars_default; std::string formatted_string(ss.str()); return formatted_string; } void cout_formatted_amount(long long int const& amount, char const* appended_chars_default = "", char const* appended_chars_singular = "", bool negative_with_parentheses = false) { bool amount_is_negative = (amount < 0); if(negative_with_parentheses && amount_is_negative) std::cout << "("; std::cout << formatted_amount(amount, appended_chars_default, appended_chars_singular); if(negative_with_parentheses && amount_is_negative) std::cout << ")"; } long long int base_amount; long long int user_amount; long long int krowbar_amount[2]; //krowbar's and ju's tilde game amount long long int da_amount; //troido's daily adventure amount long long int minercoin_amount[2]; //minerbot's minercoin game amount (tilded username (~username) and non-tilded username (username)) void show_breakdown(const long long int &amount0 = 0, char const* amount0_source = "", const long long int &amount1 = 0, char const* amount1_source = "", const long long int &amount2 = 0, char const* amount2_source = "", const long long int &amount3 = 0, char const* amount3_source = "", const long long int &amount4 = 0, char const* amount4_source = "", const long long int &amount5 = 0, char const* amount5_source = "") { bool a0 = (amount0 != 0 && strcmp(amount0_source, "")); bool a1 = (amount1 != 0 && strcmp(amount1_source, "")); bool a2 = (amount2 != 0 && strcmp(amount2_source, "")); bool a3 = (amount3 != 0 && strcmp(amount3_source, "")); bool a4 = (amount4 != 0 && strcmp(amount4_source, "")); bool a5 = (amount5 != 0 && strcmp(amount5_source, "")); if(a0 || a1 || a2 || a3 || a4 || a5) { std::cout << "Breakdown: "; if(a0) { cout_formatted_amount(amount0, "", "", true); std::cout << " [" << amount0_source << "]"; if(a1 || a2 || a3 || a4 || a5) { std::cout << " + "; } } if(a1) { cout_formatted_amount(amount1, "", "", true); std::cout << " [" << amount1_source << "]"; if(a2 || a3 || a4 || a5) { std::cout << " + "; } } if(a2) { cout_formatted_amount(amount2, "", "", true); std::cout << " [" << amount2_source << "]"; if(a3 || a4 || a5) { std::cout << " + "; } } if(a3) { cout_formatted_amount(amount3, "", "", true); std::cout << " [" << amount3_source << "]"; if(a4 || a5) { std::cout << " + "; } } if(a4) { cout_formatted_amount(amount4, "", "", true); std::cout << " [" << amount4_source << "]"; if(a5) { std::cout << " + "; } } if(a5) { cout_formatted_amount(amount5, "", "", true); std::cout << " [" << amount5_source << "]"; } std::cout << "\n"; } } void show_balance(char const* username, const long long int &amount, const long long int &amount0 = 0, char const* amount0_source = "", const long long int &amount1 = 0, char const* amount1_source = "", const long long int &amount2 = 0, char const* amount2_source = "", const long long int &amount3 = 0, char const* amount3_source = "", const long long int &amount4 = 0, char const* amount4_source = "", const long long int &amount5 = 0, char const* amount5_source = "") { std::cout << username << ", you have "; cout_formatted_amount(amount, " tildecoins", " tildecoin"); std::cout << " to your name.\n\n"; show_breakdown(amount0, amount0_source, amount1, amount1_source, amount2, amount2_source, amount3, amount3_source, amount4, amount4_source, amount5, amount5_source); std::cout << "\nThe command to send tildecoins to other users is `tcoin send ` or `tcoin -s `."; std::cout << "\nThe command to log out of tildecoin is `tcoin off`.\n\n"; } bool file_is_empty(std::ifstream& pFile) { return pFile.peek() == std::ifstream::traits_type::eof(); } bool files_are_same(const char* file_path1, const char* file_path2) { std::ifstream fin1(file_path1); if(!fin1) return false; std::ifstream fin2(file_path2); if(!fin2) return false; char c1; char c2; while(fin1.get(c1) && fin2.get(c2)) { if(c2 != c1) return false; } fin2.get(c2); //when fin1.get(c1) fails, fin2.get(c2) is not executed because of short-circuited boolean operators. This line compensates for that. if(fin1 || fin2) //one of the files must still be valid while both files are not simulatenously valid (after the while loop), which means the files are of different sizes return false; return true; //Because of (!fin1 && fin2) || (fin1 && !fin2), if any one //file is larger than the other, the files are deemed not the same. //If the last characters of the two files have not been read, then //both "fin1" and "fin2" will return true in the next iteration, and //in the current iteration, c1 and c2 have valid values that can be compared. //If the last characters of the two files were read, then "fin1" and "fin2" //will still return true until the next time fin1.get() or fin2.get() is called. //c1 and c2 still carry valid values (namely, the last characters of fin1 and fin2) //which are compared. At the next iteration, both fin1 and fin2 fail and the loop exits. //This means all c1's and c2's were equal in the iterations before. Thus, the two files are //deemed the same. } void clear_messages(const char* username) { std::string messages_path = std::string(TCOIN_MSG_PATH) + std::string(username) + std::string("_messages.txt"); long long int max_long_long_int = std::numeric_limits::max(); //we don't actually delete, but just generate a backup ;) for(long long int i = 1; i <= max_long_long_int; ++i) { std::string messages_backup_path = std::string(TCOIN_MSG_PATH) + std::string(username) + std::string("_messages") + std::to_string(i) + std::string(".txt"); std::ifstream fin(messages_backup_path.c_str()); if(!fin || file_is_empty(fin)) //if file did not open or was empty, i.e., is available as a file name to backup (backups are incrementally numbered) { fin.close(); rename(messages_path.c_str(), messages_backup_path.c_str()); chmod(messages_backup_path.c_str(), (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); break; } else fin.close(); } std::ofstream fout(messages_path.c_str(), std::fstream::trunc); fout << "\n"; fout.close(); chmod(messages_path.c_str(), (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); } void show_messages(const char* username) { std::string messages_path = std::string(TCOIN_MSG_PATH) + std::string(username) + std::string("_messages.txt"); std::ifstream fin(messages_path.c_str()); std::cout << "Messages:\n"; char ch; bool first_char_is_newline = false; bool reached_eof = false; for(int i=0; i < 2; ++i) { if(ch = fin.get()) { if(ch == std::istream::traits_type::eof()) //https://stackoverflow.com/questions/4533063/how-does-ifstreams-eof-work { if(first_char_is_newline && i==1) { reached_eof = true; std::cout << "No messages found.\n"; } break; } else { if(i==0 && ch=='\n') { first_char_is_newline = true; } std::cout << ch; } } } if(!reached_eof) { std::cout << fin.rdbuf(); } //removing eofbit fin.clear(); //moving back two places from the end to read the last two characters fin.seekg(-2, std::ios::end); char chs[2]; //chs = characters chs[0] = fin.get(); chs[1] = fin.get(); fin.get(); //to set eofbit again because I like it to be just the way it was before removing the eofbit if(chs[0]!='\n' && chs[1]=='\n') //if only one newline at the end of the file std::cout << "\n"; //print another one fin.close(); } void show_messages_tail(const char* username, int lineCount) { size_t const granularity = 100 * lineCount; std::string messages_path = std::string(TCOIN_MSG_PATH) + std::string(username) + std::string("_messages.txt"); std::ifstream source(messages_path.c_str(), std::ios_base::binary); source.seekg(0, std::ios_base::end); size_t size = static_cast(source.tellg()); std::vector buffer; int newlineCount = 0; //pseudo newline count while(source && buffer.size() != size && newlineCount <= lineCount) { buffer.resize(std::min(buffer.size() + granularity, size)); source.seekg(-static_cast(buffer.size()), std::ios_base::end); source.read(buffer.data(), buffer.size()); newlineCount = std::count(buffer.begin(), buffer.end(), '\n'); for(std::vector::size_type i = 0; i < (buffer.size()-1); ++i) if(buffer[i] == '\n' && (buffer[i+1] == '\n' || buffer[i+1] == ' ')) { newlineCount--; // An entry as follows: "\n \_message>\n\n" must be treated as a single newline-ended line (and thus count 1 newline (not 3)) ++i; //three consecutive newlines should not be "two pairs" of newlines } } std::vector::iterator start = buffer.begin(); while(newlineCount > lineCount) { start = std::find(start, buffer.end(), '\n') + 1; if(*start == ' ' || *start == '\n') continue; //we're counting cutting off a '\n ' (and '\n\n') as zero (and one) newline cut off because "\n \_message>\n\n" is one message --newlineCount; } std::cout << "Last " << lineCount << " Messages:\n"; std::vector::iterator end = remove(start, buffer.end(), '\r'); if((start == (end-1)) && (*(start) == '\n')) //there is only one character, and it is a newline (i.e.. no messages) std::cout << "\nNo messages found.\n\n"; else { if(*(start) != '\n') //if it starts with a newline, don't put another one std::cout << "\n"; std::cout << std::string(start, end); if(*(end-2) != '\n' && *(end-1) == '\n') //if it ends with two newlines, don't put another one std::cout << "\n"; } } bool program_exists(const char* username) { char *program_key_path = new char[strlen(username) + sizeof(PCOIN_KEY_PATH_W_SLASH) + 4]; //sizeof counts NULL char at the end too std::strcpy(program_key_path, PCOIN_KEY_PATH_W_SLASH); std::strcat(program_key_path, username); std::strcat(program_key_path, ".txt"); std::ifstream fin(program_key_path); bool return_value = false; if(!fin) //file doesn't exist { fin.close(); delete[] program_key_path; return_value = false; //program account not found } else { fin.close(); delete[] program_key_path; return_value = true; //program account found } return return_value; } bool username_exists(const char* username) { const static std::string all_usernames = exec(LS_HOME_CMD); std::istringstream iss(all_usernames); static std::vector usernames{std::istream_iterator{iss}, std::istream_iterator{}}; if(std::find(usernames.begin(), usernames.end(), username) != usernames.end()) { return true; } //program usernames check return program_exists(username); } bool user_has_initialised(const char* username) { bool return_value = true; //we assume the user has initialised and check for signs of that not being the case char *balance_file_path = new char[strlen(username) + sizeof(TCOIN_PATH_W_SLASH) + 4]; //sizeof counts NULL char at the end too std::strcpy(balance_file_path, TCOIN_PATH_W_SLASH); std::strcat(balance_file_path, username); std::strcat(balance_file_path, ".txt"); char *messages_file_path = new char[strlen(username) + sizeof(TCOIN_MSG_PATH) + 13]; //sizeof counts NULL char at the end too std::strcpy(messages_file_path, TCOIN_MSG_PATH); std::strcat(messages_file_path, username); std::strcat(messages_file_path, "_messages.txt"); char *password_file_path = new char[strlen(username) + sizeof(TCOIN_PASS_PATH) + 13]; //sizeof counts NULL char at the end too std::strcpy(password_file_path, TCOIN_PASS_PATH); std::strcat(password_file_path, username); std::strcat(password_file_path, "_password.txt"); char *salt_file_path = new char[strlen(username) + sizeof(TCOIN_SALT_PATH) + 9]; //sizeof counts NULL char at the end too std::strcpy(salt_file_path, TCOIN_SALT_PATH); std::strcat(salt_file_path, username); std::strcat(salt_file_path, "_salt.txt"); char *salt_logged_in_file_path = new char[strlen(username) + sizeof(TCOIN_SALT_PATH) + 19]; //sizeof counts NULL char at the end too std::strcpy(salt_logged_in_file_path, TCOIN_SALT_PATH); std::strcat(salt_logged_in_file_path, username); std::strcat(salt_logged_in_file_path, "_salt_logged_in.txt"); std::ifstream fin1(balance_file_path); std::ifstream fin2(messages_file_path); std::ifstream fin3(password_file_path); std::ifstream fin4(salt_file_path); std::ifstream fin5(salt_logged_in_file_path); if(!fin1 || !fin2 || !fin3 || file_is_empty(fin3) || (!fin4 && !fin5) || (fin4 && file_is_empty(fin4)) || (fin5 && file_is_empty(fin5))) return_value = false; //user has not initialised completely fin1.close(); fin2.close(); fin3.close(); fin4.close(); fin5.close(); delete[] balance_file_path; delete[] messages_file_path; delete[] password_file_path; delete[] salt_file_path; delete[] salt_logged_in_file_path; return return_value; } int log_off(const char* username) { std::string salt_logged_in_file = std::string(TCOIN_SALT_PATH) + std::string(username) + std::string("_salt_logged_in.txt"); std::string salt_file = std::string(TCOIN_SALT_PATH) + std::string(username) + std::string("_salt.txt"); std::ifstream fin(salt_logged_in_file.c_str()); if(!fin) //user is not currently logged in { std::cout << "\nSorry, you're already logged out. Thanks for being extra careful about being logged out before running untrusted programs (if you were going to do that).\n\n"; return ERR_ALREADY_OFF; } else //user is logged in { if(file_is_empty(fin)) //user is logged in but salt_logged_in_file is empty { remove(salt_logged_in_file.c_str()); remove(salt_file.c_str()); std::cout << "\nSorry, your salt file is damaged. You will have to run `tcoin init` and create a new passphrase.\n\n"; return ERR_DAMAGED_SALT_FILE; } else //user is logged in and salt_logged_in_file is not empty { fin.close(); std::ifstream fin2(salt_file.c_str()); if(!fin2 || (fin2 && file_is_empty(fin2))) //salt_file doesn't exist or is empty but salt_logged_in_file does { if(fin2 && file_is_empty(fin2)) remove(salt_file.c_str()); fin2.close(); rename(salt_logged_in_file.c_str(), salt_file.c_str()); std::cout << "\nYou have successfully logged out of tildecoin. Have a wonderful day!\n\n"; } else //salt_file exists and is non-empty, and salt_file_logged_in also exists (!) and is non-empty { fin2.close(); std::cout << "\nSorry, there's something seriously wrong with your salt files. You'll have to run `tcoin init` and create a new passphrase.\n\n"; return ERR_2_SALT_FILES; } } } return 0; } bool is_logged_on(const char* username) { std::string salt_logged_in_file = std::string(TCOIN_SALT_PATH) + std::string(username) + std::string("_salt_logged_in.txt"); std::string salt_file = std::string(TCOIN_SALT_PATH) + std::string(username) + std::string("_salt.txt"); std::ifstream fin(salt_logged_in_file.c_str()); if(!fin) return false; else if(file_is_empty(fin)) { fin.close(); remove(salt_logged_in_file.c_str()); remove(salt_file.c_str()); std::cout << "\n\nYour salt logged in file is empty. You'll have to run `tcoin init` and create a new passphrase.\n\n"; return false; } //control only reaches here if (fin && !file_is_empty(fin)), so the user is indeed logged in return true; } int log_on(const char* username) { if(is_logged_on(username)) { std::cout << "\nYou're already logged in. Please type `tcoin` to see your messages and balance.\n\n"; return ERR_ALREADY_ON; } std::string salt_file = std::string(TCOIN_SALT_PATH) + std::string(username) + std::string("_salt.txt"); std::string decrypted_password_file = std::string(TCOIN_PASS_PATH) + std::string(username) + std::string("_decrypted_password.txt"); std::string password_file = std::string(TCOIN_PASS_PATH) + std::string(username) + std::string("_password.txt"); std::ifstream fin(password_file.c_str()); if(!fin || (fin && file_is_empty(fin))) { std::cout << "\nSorry, your password file could not be opened. You will have to create a new passphrase by running `tcoin init`.\n\n"; return ERR_NO_PWD_FILE; } else { fin.close(); std::ifstream codefin(TCOIN_CODEZ_PATH); char code1[513], code2[513], code3[513]; codefin >> code1; codefin >> code2; codefin >> code3; codefin.close(); exec((std::string(TCOIN_BIN_PATH_W_SPACE) + std::string(code2)).c_str()); fin.open(decrypted_password_file.c_str()); if(!fin || (fin && file_is_empty(fin))) { if(fin && file_is_empty(fin)) remove(decrypted_password_file.c_str()); fin.close(); std::cout << "\nSorry, the passphrase you entered could not decrypt the encrypted password file. You are not logged on. Please run `tcoin on` to try again.\n\n"; return ERR_WRONG_PWD; } else { if(files_are_same(salt_file.c_str(), decrypted_password_file.c_str())) { std::string salt_logged_in_file = std::string(TCOIN_SALT_PATH) + std::string(username) + std::string("_salt_logged_in.txt"); rename(salt_file.c_str(), salt_logged_in_file.c_str()); remove(decrypted_password_file.c_str()); std::cout << "\nYou have now successfully logged on to tildecoin (please run `tcoin` to check your balance and messages). Please be aware that any programs you run now can siphon funds from your account without your knowing until it's too late. Please run `tcoin off` before running any untrusted programs.\n\n"; } else { remove(decrypted_password_file.c_str()); fin.close(); std::cout << "\nSorry, the decrypted password file did not match the salt file. You are not logged on. Please run `tcoin on` to try again.\n\n"; return ERR_WRONG_PWD2; } } } return 0; } int initialise_user(const char* username, const long long int &base_amount) { std::string balance_file = std::string(TCOIN_PATH_W_SLASH) + std::string(username) + std::string(".txt"); std::string messages_file = std::string(TCOIN_MSG_PATH) + std::string(username) + std::string("_messages.txt"); std::string password_file = std::string(TCOIN_PASS_PATH) + std::string(username) + std::string("_password.txt"); std::string salt_file = std::string(TCOIN_SALT_PATH) + std::string(username) + std::string("_salt.txt"); std::string salt_logged_in_file = std::string(TCOIN_SALT_PATH) + std::string(username) + std::string("_salt_logged_in.txt"); std::ifstream fin(balance_file.c_str()); bool flag_balance = false, flag_messages = false, flag_password_and_salt = false; if(!fin) { std::ofstream fout(balance_file.c_str(), std::fstream::trunc); fout << "0\n"; fout.close(); chmod(balance_file.c_str(), (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); flag_balance = true; } fin.close(); fin.open(messages_file.c_str()); if(!fin) { fin.close(); std::ofstream fout(messages_file.c_str(), std::fstream::trunc); fout << "\n"; fout.close(); chmod(messages_file.c_str(), (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); flag_messages = true; } fin.close(); fin.open(salt_file.c_str()); std::ifstream fin2(password_file.c_str()); std::ifstream fin3(salt_logged_in_file.c_str()); if((!fin && !fin3) || !fin2 || (fin && file_is_empty(fin)) || (fin3 && file_is_empty(fin3)) || file_is_empty(fin2)) //if salt or password file is missing or empty, we'd have to set up a new salt and password (i.e., salt file encrypted with passphrase) { fin.close(); fin2.close(); fin3.close(); remove(salt_file.c_str()); remove(salt_logged_in_file.c_str()); remove(password_file.c_str()); std::ofstream fout(salt_file.c_str(), std::fstream::trunc); const long long int rand_max_length = strlen(std::to_string(RAND_MAX).c_str()); { for(int i=0; i<16;++i) { fout.width(rand_max_length); fout.fill('0'); fout << rand(); } } fout << "\n"; fout.close(); chmod(salt_file.c_str(), (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); std::cout << "\nYour salt and/or password file(s) are missing. A new salt and password file will be created. Please enter your desired passphrase and re-enter to confirm the same below. You will need to enter it to log onto tildecoin. If you ^C before confirming the passphrase, you'll have created an empty password file and would have to run `tcoin init` again.\n\n"; std::ifstream codefin(TCOIN_CODEZ_PATH); char code1[513], code2[513], code3[513]; codefin >> code1; codefin >> code2; codefin >> code3; codefin.close(); exec((std::string(TCOIN_BIN_PATH_W_SPACE) + std::string(code1)).c_str()); fin.open(password_file.c_str()); if(!fin || (fin && file_is_empty(fin))) { if(file_is_empty(fin)) chmod(password_file.c_str(), (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); fin.close(); std::cout << "\nSomething went wrong in the password-file generation process. Your password file is now empty. You will have to run `tcoin init` again and choose a new passphrase.\n\n"; return 1; } else { chmod(password_file.c_str(), (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); fin.close(); } flag_password_and_salt=true; } fin.close(); fin2.close(); fin3.close(); if(flag_balance==true) { std::cout << "\nWelcome to tildecoin. "; cout_formatted_amount(base_amount, " tildecoins have been added to your account.\n"); if(flag_messages==false) std::cout << "\nPlease execute `tcoin --help` for help or just `tcoin` for a status update.\n"; } if(flag_messages==true) { std::cout << "\nYour tildecoin account is ready to send and receive messages!"; std::cout << "\nPlease execute `tcoin --help` for help or just `tcoin` for a status update.\n"; } if(flag_password_and_salt==true) { std::cout << "\nThe password-file generation process was completed successfully. Please run `tcoin on` to log on and `tcoin off` to log off. While logged on, be aware that any other programs you run can siphon funds from your account without your knowing until it's too late. Thus, please run `tcoin off` before running any untrusted programs.\n"; } if(!flag_balance && !flag_messages && !flag_password_and_salt) //the account was initialised despite being already initialised and having all the required files intact. std::cout << "\nYou took quite the chance initialising again. What if it nuked your balance, messages and passphrase?\n"; std::cout << '\n'; return 0; } int send_message(const char* sender_username, const char* receiver_username, const char* message, const long long int &amount_sent, const char* option) { std::string random_string = std::to_string(rand()); char *receiver_path = new char[strlen(receiver_username) + sizeof(TCOIN_MSG_PATH) + 13]; //sizeof() includes '\0' char *temp_receiver_path = new char[strlen(receiver_username) + strlen(random_string.c_str()) + sizeof(TCOIN_MSG_PATH) + 13]; std::strcpy(receiver_path, TCOIN_MSG_PATH); //length = 27 std::strcat(receiver_path, receiver_username); std::strcat(receiver_path, "_messages.txt"); //length = 13 std::strcpy(temp_receiver_path, TCOIN_MSG_PATH); std::strcat(temp_receiver_path, receiver_username); std::strcat(temp_receiver_path, random_string.c_str()); std::strcat(temp_receiver_path, "_messages.txt"); //create receiver's message file if none exists //the message will be included in the receiver's //account when she/he initialises her/his account //at a later time char *receiver_salt_path = new char[strlen(receiver_username) + sizeof(TCOIN_SALT_PATH) + 9]; //sizeof() includes '\0' char *receiver_salt_logged_in_path = new char[strlen(receiver_username) + sizeof(TCOIN_SALT_PATH) + 19]; std::strcpy(receiver_salt_path, TCOIN_SALT_PATH); std::strcat(receiver_salt_path, receiver_username); std::strcpy(receiver_salt_logged_in_path, receiver_salt_path); std::strcat(receiver_salt_path, "_salt.txt"); //length = 9 std::strcat(receiver_salt_logged_in_path, "_salt_logged_in.txt"); //length = 19 std::ifstream fin(receiver_path); std::ifstream fin2(receiver_salt_path); std::ifstream fin3(receiver_salt_logged_in_path); if((!fin || file_is_empty(fin)) && ((!program_exists(receiver_username)) || ((!fin2 || file_is_empty(fin2)) && (!fin3 || file_is_empty(fin3))))) { fin.close(); std::ofstream fout(receiver_path, std::fstream::trunc); fout << "\n"; fout.close(); } else fin.close(); fin2.close(); fin3.close(); chmod(receiver_path, (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); delete[] receiver_salt_path; delete[] receiver_salt_logged_in_path; while(1) { if(!std::rename(receiver_path, temp_receiver_path)) { char *really_temp_receiver_path = new char[strlen(temp_receiver_path) + 5]; //4 + 1 for '\0' std::strcpy(really_temp_receiver_path, temp_receiver_path); std::strcat(really_temp_receiver_path, "_tmp"); //length = 4 std::ifstream fin(temp_receiver_path); std::ofstream fout(really_temp_receiver_path); if(!file_is_empty(fin)) fout << fin.rdbuf(); fin.close(); time_t now = std::time(NULL); char dt[26]; #ifndef TILDEINSTITUTE std::strftime(dt, 26, "%a %b %_d %T %Y", std::gmtime(&now)); #else std::strftime(dt, 26, "%a %b %e %T %Y", std::gmtime(&now)); #endif char sender_formatted_string[26]; char sender_arrow_formatted_string[47]; char sender_arrow_string[47]; char receiver_formatted_string[26]; std::snprintf(sender_formatted_string, 26, "%25s", sender_username); std::snprintf(receiver_formatted_string, 26, "%-25s", receiver_username); int sender_username_length = std::strlen(sender_username); int number_of_chars = 26 >= sender_username_length ? 26 : sender_username_length; std::strncpy(sender_arrow_string, sender_username, number_of_chars); sender_arrow_string[number_of_chars] = '\0'; std::strcat(sender_arrow_string, " ----"); std::string amount_sent_str = formatted_amount(amount_sent); std::strncat(sender_arrow_string, amount_sent_str.c_str(), 10); std::strcat(sender_arrow_string, "----> "); std::snprintf(sender_arrow_formatted_string, 47, "%46s", sender_arrow_string); fout << dt << ": " << sender_arrow_formatted_string << receiver_username; if(!strcmp(message, "")) //if message is empty fout << "\n"; else { fout << "\n \\_ " << sender_username << " said: "; for(int i=0; message[i]!='\0'; ++i) { if(message[i] == '\n') fout << ""; //return-key pressed else if(message[i] == '\v') fout << ""; //vertical-tab symbol entered else fout << message[i]; } fout << "\n\n"; } fout.close(); chmod(really_temp_receiver_path, (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); if(!fout) //error { std::cerr << "Fatal error" << ERR_SEND_MESSAGE_RECEIVER_MSG_FILE_UNABLE_TO_BE_UPDATED_FATAL << ": the receiver message file was unable to be updated. Please contact " << TCOIN_ERR_CONTACT_EMAIL << " to report this error (because it requires manual recovery)."; exit_program(ERR_SEND_MESSAGE_RECEIVER_MSG_FILE_UNABLE_TO_BE_UPDATED_FATAL); } else { std::remove(temp_receiver_path); } while(1) { if(!std::rename(really_temp_receiver_path, temp_receiver_path)) break; } // unlock_receiver_messages while(1) { if(!std::rename(temp_receiver_path, receiver_path)) break; } chmod(receiver_path, (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); delete[] really_temp_receiver_path; delete[] temp_receiver_path; delete[] receiver_path; //additional place to write if sending to a program: if(program_exists(receiver_username)) { random_string = std::string("rand"); std::string program_receiver_path = std::string(TCOIN_PROG_ACT_PATH) + std::string(receiver_username) + std::string("/_MESSAGES.txt"); std::string temp_program_receiver_path = std::string(TCOIN_PROG_ACT_PATH) + std::string(receiver_username) + std::string("/_MESSAGES") + random_string + std::string(".txt"); std::string really_temp_program_receiver_path = temp_program_receiver_path + std::string("_tmp"); //create program receiver's _MESSAGES file if none exists { std::ifstream fin(program_receiver_path.c_str()); std::ifstream fin2(temp_program_receiver_path.c_str()); if((!fin || file_is_empty(fin)) && (!fin2)) { fin.close(); std::ofstream fout(program_receiver_path.c_str(), std::fstream::trunc); fout << "\n"; fout.close(); } else fin.close(); fin2.close(); chmod(program_receiver_path.c_str(), (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); } while(1) { if(!std::rename(program_receiver_path.c_str(), temp_program_receiver_path.c_str())) { std::ifstream fin(temp_program_receiver_path.c_str()); std::ofstream fout(really_temp_program_receiver_path.c_str()); if(!file_is_empty(fin)) fout << fin.rdbuf(); fin.close(); //now, sender_username, receiver_username, amount_sent char sender_formatted_string[26]; char receiver_formatted_string[26]; std::snprintf(sender_formatted_string, 26, "%s", sender_username); std::snprintf(receiver_formatted_string, 26, "%s", receiver_username); std::string amount_sent_str = formatted_amount(amount_sent); fout << now << "\t" << sender_formatted_string << "\t" << receiver_formatted_string << "\t" << amount_sent_str; if(!strcmp(message, "")) //if message is empty fout << "\n"; else { fout << "\t" << sender_formatted_string << "\t"; for(int i=0; message[i]!='\0'; ++i) { if(message[i] == '\n') fout << ""; //return-key pressed else if(message[i] == '\v') fout << ""; //vertical-tab symbol entered else fout << message[i]; } fout << "\n"; } fout.close(); chmod(really_temp_program_receiver_path.c_str(), (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); if(!fout) //error { std::cerr << "Fatal error " << ERR_SEND_MESSAGE_PROGRAM_RECEIVER_MSG_FILE_UNABLE_TO_BE_UPDATED_FATAL << ": the receiver program_message file was unable to be updated. Please contact " << TCOIN_ERR_CONTACT_EMAIL << " to report this error (because it requires manual recovery)."; exit_program(ERR_SEND_MESSAGE_PROGRAM_RECEIVER_MSG_FILE_UNABLE_TO_BE_UPDATED_FATAL); } else { std::remove(temp_program_receiver_path.c_str()); } while(1) { if(!std::rename(really_temp_program_receiver_path.c_str(), temp_program_receiver_path.c_str())) break; } // unlock_receiver_messages while(1) { if(!std::rename(temp_program_receiver_path.c_str(), program_receiver_path.c_str())) break; } chmod(program_receiver_path.c_str(), (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); break; }//if statement with !std::rename for receiver's program accounting _messages file }//while loop for program accounting receiver's _messages file }//receiver is program account //locking sender_messages_after_receiver_messages_unlocked random_string = std::to_string(rand()); char *sender_path = new char[strlen(sender_username) + sizeof(TCOIN_MSG_PATH) + 13]; //sizeof() includes '\0' char *temp_sender_path = new char[strlen(sender_username) + strlen(random_string.c_str()) + sizeof(TCOIN_MSG_PATH) + 13]; std::strcpy(sender_path, TCOIN_MSG_PATH); std::strcat(sender_path, sender_username); std::strcat(sender_path, "_messages.txt"); // length = 13 std:strcpy(temp_sender_path, TCOIN_MSG_PATH); std::strcat(temp_sender_path, sender_username); std::strcat(temp_sender_path, random_string.c_str()); std::strcat(temp_sender_path, "_messages.txt"); // length = 13 while(1) { if(!std::rename(sender_path, temp_sender_path)) { char *really_temp_sender_path = new char[strlen(temp_sender_path) + 5]; std::strcpy(really_temp_sender_path, temp_sender_path); std::strcat(really_temp_sender_path, "_tmp"); // length = 4 fin.open(temp_sender_path); fout.open(really_temp_sender_path); chmod(really_temp_sender_path, (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); fout << fin.rdbuf(); now = std::time(NULL); #ifndef TILDEINSTITUTE std::strftime(dt, 26, "%a %b %_d %T %Y", std::gmtime(&now)); #else std::strftime(dt, 26, "%a %b %e %T %Y", std::gmtime(&now)); #endif char sender_formatted_string_right_aligned[26]; char receiver_arrow_formatted_string[47]; char receiver_arrow_string[47]; std::snprintf(receiver_formatted_string, 26, "%25s", receiver_username); std::snprintf(sender_formatted_string, 26, "%-25s", sender_username); std::snprintf(sender_formatted_string_right_aligned, 26, "%25s", sender_username); int receiver_username_length = std::strlen(receiver_username); int number_of_chars = 26 >= receiver_username_length ? 26 : receiver_username_length; std::strncpy(receiver_arrow_string, receiver_username, number_of_chars); receiver_arrow_string[number_of_chars] = '\0'; std::strcat(receiver_arrow_string, " <---"); std::string amount_sent_str = formatted_amount(amount_sent); std::strncat(receiver_arrow_string, amount_sent_str.c_str(), 10); std::strcat(receiver_arrow_string, "----- "); std::snprintf(receiver_arrow_formatted_string, 47, "%46s", receiver_arrow_string); fout << dt << ": " << receiver_arrow_formatted_string << sender_username; if(!strcmp(message, "")) //if message is empty fout << "\n"; else { fout << "\n \\_ " << sender_username << " said: "; for(int i=0; message[i]!='\0'; ++i) { if(message[i] == '\n') fout << ""; //return-key pressed else if(message[i] == '\v') fout << ""; //vertical-tab symbol entered else fout << message[i]; } fout << "\n\n"; } fin.close(); fout.close(); if(!fout) //error { std::cerr << "Fatal error " << ERR_SEND_MESSAGE_SENDER_MSG_FILE_UNABLE_TO_BE_UPDATED_FATAL << ": the sender message file was unable to be updated. Please contact " << TCOIN_ERR_CONTACT_EMAIL << " to report this error (because it requires manual recovery)."; exit_program(ERR_SEND_MESSAGE_SENDER_MSG_FILE_UNABLE_TO_BE_UPDATED_FATAL); } else { std::remove(temp_sender_path); } while(1) { if(!std::rename(really_temp_sender_path, temp_sender_path)) break; } while(1) { if(!std::rename(temp_sender_path, sender_path)) break; } delete[] really_temp_sender_path; delete[] temp_sender_path; delete[] sender_path; break; } } break; } } //finally, everything ran well and we can send the message to stdout if verbose is turned on if(!strcmp(option, "verbose") && strcmp(message, "")) //message should not be empty { //since the message to stdout from send() ends with "\n\n", we have commented this below line out //std::cout << "\n"; std::cout << "In addition, the transaction message \"" << message << "\" was "; std::cout << "sent from `" << sender_username << "` to `" << receiver_username << "` successfully."; std::cout << "\n\n"; } return 0; } bool user_is_locked(const char* username) { std::ifstream fin((std::string(TCOIN_PATH_W_SLASH) + std::string(username) + std::string("_locked.txt")).c_str()); if(!fin) return false; return true; } int send(const char* sender_username, const char* receiver_username, const long long int &amount_to_send, const long long int &base_amount, const char* option) { int final_return_value = 0; //receiver usrname check if(username_exists(receiver_username)) { if(!strcmp(sender_username, receiver_username)) { std::cout << "\nSorry, you cannot send tildecoins to yourself.\n\n"; return ERR_TCOIN_TO_SELF; } if(user_is_locked(receiver_username)) { if(!strcmp(option, "verbose")) std::cout << "\nSorry, `" << receiver_username << "` does not wish to receive any tildecoins at this time.\n\n"; return ERR_RECEIVER_BLOCKED; } if(amount_to_send <= 0) { if(!strcmp(option, "verbose")) std::cout << "\nSorry, that amount is not valid. The amount should be a positive decimal number when truncated to two decimal places.\n\n"; return ERR_NEGATIVE_SEND_AMOUNT; } else { std::string random_string = std::to_string(rand()); int return_value = -1; // sizeof() includes the NULL character at the end of the string char* temp_sender_path = new char[strlen(sender_username) + strlen(random_string.c_str()) + sizeof(TCOIN_PATH_W_SLASH) + 4]; char* sender_path = new char[strlen(sender_username) + sizeof(TCOIN_PATH_W_SLASH) + 4]; char* temp_sender_username = new char[strlen(sender_username) + strlen(random_string.c_str()) + 1]; std::strcpy(temp_sender_username, sender_username); std::strcat(temp_sender_username, random_string.c_str()); std::strcpy(temp_sender_path, TCOIN_PATH_W_SLASH); std::strcat(temp_sender_path, temp_sender_username); std::strcat(temp_sender_path, ".txt"); std::strcpy(sender_path, TCOIN_PATH_W_SLASH); std::strcat(sender_path, sender_username); std::strcat(sender_path, ".txt"); while(1) { if(!std::rename(sender_path, temp_sender_path)) { //Insufficient funds check is in add_file_value() //Returns 1 if insufficient funds, otherwise returns 0 return_value = add_file_value(temp_sender_username, -1 * amount_to_send, base_amount); if(return_value == 0) // Funds sucessfully deducted from sender_username { random_string = std::to_string(rand()); // sizeof() includes the NULL character at the end of the string char *temp_receiver_path = new char[strlen(receiver_username) + strlen(random_string.c_str()) + sizeof(TCOIN_PATH_W_SLASH) + 4]; char *receiver_path = new char[strlen(receiver_username) + sizeof(TCOIN_PATH_W_SLASH) + 4]; char *temp_receiver_username = new char[strlen(receiver_username) + strlen(random_string.c_str()) + 1]; std::strcpy(temp_receiver_username, receiver_username); std::strcat(temp_receiver_username, random_string.c_str()); std::strcpy(temp_receiver_path, TCOIN_PATH_W_SLASH); std::strcat(temp_receiver_path, temp_receiver_username); std::strcat(temp_receiver_path, ".txt"); std::strcpy(receiver_path, TCOIN_PATH_W_SLASH); std::strcat(receiver_path, receiver_username); std::strcat(receiver_path, ".txt"); //create receiver's balance file if none exists //the balance will be included in the receiver's //account when she/he initialises her/his account //at a later time char *receiver_salt_path = new char[strlen(receiver_username) + sizeof(TCOIN_SALT_PATH) + 9]; // sizeof() includes the NULL character at the end of the string char *receiver_salt_logged_in_path = new char[strlen(receiver_username) + sizeof(TCOIN_SALT_PATH) + 19]; std::strcpy(receiver_salt_path, TCOIN_SALT_PATH); std::strcat(receiver_salt_path, receiver_username); std::strcpy(receiver_salt_logged_in_path, receiver_salt_path); std::strcat(receiver_salt_path, "_salt.txt"); std::strcat(receiver_salt_logged_in_path, "_salt_logged_in.txt"); std::ifstream fin(receiver_path); std::ifstream fin2(receiver_salt_path); std::ifstream fin3(receiver_salt_logged_in_path); if((!fin || file_is_empty(fin)) && ((!program_exists(receiver_username)) || ((!fin2 || file_is_empty(fin2)) && (!fin3 || file_is_empty(fin3))))) { fin.close(); std::ofstream fout(receiver_path, std::fstream::trunc); fout << "0\n"; fout.close(); } else fin.close(); fin2.close(); fin3.close(); chmod(receiver_path, (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); delete[] receiver_salt_path; delete[] receiver_salt_logged_in_path; while(1) { if(!std::rename(receiver_path, temp_receiver_path)) { //Insufficient funds check in add_file_value() itself add_file_value(temp_receiver_username, amount_to_send, base_amount); //additional place to write if sending to a program: if(program_exists(receiver_username)) { random_string = std::string("rand"); std::string program_receiver_path = std::string(TCOIN_PROG_ACT_PATH) + std::string(receiver_username) + std::string("/") + std::string(sender_username) + std::string(".txt"); std::string temp_program_receiver_path = std::string(TCOIN_PROG_ACT_PATH) + std::string(receiver_username) + std::string("/") + std::string(sender_username) + random_string + std::string(".txt"); std::string temp_program_receiver_username = std::string(PROG_ACT_W_SLASH) + std::string(receiver_username) + std::string("/") + std::string(sender_username) + random_string; //create program receiver's balance file if none exists { std::ifstream fin(program_receiver_path.c_str()); std::ifstream fin2(temp_program_receiver_path.c_str()); if((!fin || file_is_empty(fin)) && (!fin2)) { fin.close(); std::ofstream fout(program_receiver_path.c_str(), std::fstream::trunc); fout << "0\n"; fout.close(); } else fin.close(); fin2.close(); chmod(program_receiver_path.c_str(), (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); } while(1) { if(!std::rename(program_receiver_path.c_str(), temp_program_receiver_path.c_str())) { //Insufficient funds check in add_file_value() itself add_file_value(temp_program_receiver_username.c_str(), amount_to_send, base_amount); //Value must also be added to a _total.txt file { random_string = std::string("rand"); std::string program_receiver_total_path = std::string(TCOIN_PROG_ACT_PATH) + std::string(receiver_username) + std::string("/_TOTAL.txt"); std::string temp_program_receiver_total_path = std::string(TCOIN_PROG_ACT_PATH) + std::string(receiver_username) + std::string("/_TOTAL") + random_string + std::string(".txt"); std::string temp_program_receiver_total_username = std::string(PROG_ACT_W_SLASH) + std::string(receiver_username) + std::string("/_TOTAL") + random_string; //create program receiver's "total balance file" if none exists { std::ifstream fin(program_receiver_total_path.c_str()); std::ifstream fin2(temp_program_receiver_total_path.c_str()); if((!fin || file_is_empty(fin)) && (!fin2)) { fin.close(); std::ofstream fout(program_receiver_total_path.c_str(), std::fstream::trunc); fout << "0\n"; fout.close(); } else fin.close(); fin2.close(); chmod(program_receiver_total_path.c_str(), (S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO); } while(1) { if(!std::rename(program_receiver_total_path.c_str(), temp_program_receiver_total_path.c_str())) { //Insufficient funds check in add_file_value() itself add_file_value(temp_program_receiver_total_username.c_str(), amount_to_send, base_amount); while(1) { if(!std::rename(temp_program_receiver_total_path.c_str(), program_receiver_total_path.c_str())) break; } break; } } }//value must also be added to a _total.txt file while(1) { if(!std::rename(temp_program_receiver_path.c_str(), program_receiver_path.c_str())) break; } break; }//if statement with !std::rename for receiver's program accounting receiver balance file }//while loop for program accounting receiver's balance file }//receiver is program account while(1) { if(!std::rename(temp_receiver_path, receiver_path)) break; } delete[] temp_receiver_path; delete[] receiver_path; delete[] temp_receiver_username; break; }//if statement with !std::rename for receiver_username.txt }//while loop for receiver_username.txt if(!strcmp(option, "verbose")) { std::cout << "\n"; cout_formatted_amount(amount_to_send, " tildecoins were ", " tildecoin was "); std::cout << "sent from `" << sender_username << "` to `" << receiver_username << "`."; std::cout << "\n\n"; } } else if(return_value == 1) { if(!strcmp(option, "verbose")) { long long int amount_of_funds = base_amount + get_file_value(temp_sender_username); std::cout << "\nSorry, you do not have sufficient funds to execute this transaction. "; std::cout << "Your current balance is "; cout_formatted_amount(amount_of_funds, " tildecoins.\n\n", " tildecoin.\n\n"); } final_return_value = ERR_INSUFFICIENT_FUNDS; //we don't simply "return 3" here because we want temp_sender_path to get renamed again } while(1) { if(!std::rename(temp_sender_path, sender_path)) break; } delete[] temp_sender_path; delete[] sender_path; delete[] temp_sender_username; break; }//if statement with !std::rename }//while loop for sender }//amount sent is more than 0 }//username exists else { if(!strcmp(option, "verbose")) std::cout << "\nSorry, no user with the username `" << receiver_username << "` was found.\n\n"; return ERR_RECEIVER_NOT_FOUND; } return final_return_value; } void help(long long int &base_amount) { std::cout << "\n - tildecoin (also called tcoin) is " << TCOIN_HOST_NAME << "'s very own digital (non-crypto) currency."; std::cout << "\n - to participate in " << TCOIN_HOST_NAME << "'s internal economy by creating your own tcoin account, run `tcoin init`."; std::cout << "\n - all users get "; cout_formatted_amount(base_amount); std::cout << " coins to start, and can send and receive coins from other users (and programs)."; std::cout << "\n - more information about tildecoin is available at \"https://tilde.town/~login/tcoin.html\".\n"; std::cout << "\n`tcoin`: check your balance and the 10 latest lines of messages"; std::cout << "\n`tcoin on`: log on to tildecoin"; std::cout << "\n`tcoin off`: out out of tildecoin"; std::cout << "\n`tcoin init`: initialise tcoin (you'll need to do this before using anything else)"; std::cout << "\n`tcoin balance` or `tcoin -b`: print the number representing your balance"; std::cout << "\n`tcoin breakdown` or `tcoin -bd`: print a breakdown of how your balance was determined"; std::cout << "\n`tcoin messages` or `tcoin -m`: print all messages"; std::cout << "\n`tcoin messages ` or `tcoin -m `: print the last messages"; std::cout << "\n`tcoin clear_messages` or `tcoin -cm`: clear all messages"; std::cout << "\n`tcoin send ` or `tcoin -s `: send tildecoins to "; std::cout << "\n`tcoin send \"\"` or `tcoin -s \"\"`: optionally, include a message to be sent to "; std::cout << "\n`tcoin silentsend [\"\"]`, `tcoin send -s [\"\"]` or `tcoin -ss [\"\"]`: send tildecoins to with an optional (as indicated by [ and ], which should not be included in the actual comment) message included without printing anything"; std::cout << "\nIn the commands with ` `, switching the two arguments around (i.e., from ` ` to ` `) will also work"; std::cout << "\n`tcoin --help`, `tcoin help` or `tcoin -h`: print this help text"; std::cout << "\nSend an email to " << TCOIN_PASS_RESET_CONTACT_EMAIL << " to request a passphrase reset.\n\n"; } bool is_number(const char* test_string) { char* p; std::strtod(test_string, &p); return *p == 0; } bool message_is_long(const char* test_string) { for(int i=0; i < TCOIN_MSG_LENGTH_LIMIT+1; ++i) if(test_string[i] == '\0') { return false; //message is shorter than TCOIN_MSG_LENGTH_LIMIT characters } return true; //message is longer than TCOIN_MSG_LENGTH_LIMIT characters } int main(int argc, char *argv[]) { //sneaky scrypt magic (process overlaying to maintain suid) { std::ifstream codefin(TCOIN_CODEZ_PATH); char code1[513], code2[513], code3[513], codecheck[514]; codefin >> code1; codefin >> code2; codefin >> code3; codefin.close(); if(argc==2 && !strctcmp(argv[1], code1)) { std::string salt_file = std::string(TCOIN_SALT_PATH) + get_username() + std::string("_salt.txt"); std::string password_file = std::string(TCOIN_PASS_PATH) + get_username() + std::string("_password.txt"); execl(TCOIN_SCRYPT_PATH, "scrypt", "enc", "-m", "0.125", "-t", "5", salt_file.c_str(), password_file.c_str(), NULL); } if(argc==2 && !strctcmp(argv[1], code2)) { std::string decrypted_password_file = std::string(TCOIN_PASS_PATH) + get_username() + std::string("_decrypted_password.txt"); std::string password_file = std::string(TCOIN_PASS_PATH) + get_username() + std::string("_password.txt"); execl(TCOIN_SCRYPT_PATH, "scrypt", "dec", "-t", "15", password_file.c_str(), decrypted_password_file.c_str(), NULL); } if(argc==2 && !strctcmp(argv[1], "pcoin_list")) { std::fgets(codecheck, 514, stdin); codecheck[513] = codecheck[514] = '\0'; if(!strctcmp(codecheck, code3)) execl(LS_PATH, "ls", PCOIN_KEY_PATH, NULL); } } //If ^C is sent while doing `tcoin on`, _dercrypted_password.txt gets left behind //this might cause the program to interpret the salt and password to be corrupted, and might //ask to create a new passphrase. To prevent this, we cleanup _decrypted_password.txt on every //start of tcoin { std::string decrypted_password_file = std::string(TCOIN_PASS_PATH) + get_username() + std::string("_decrypted_password.txt"); remove(decrypted_password_file.c_str()); } base_amount = get_file_value("base/base"); long long int unaltered_base_amount = base_amount; //adding tildebot scores from krowbar to base amount #ifndef KROWBAR_OFF if(!user_is_locked(get_username().c_str())) { std::string line; const std::string username = get_username(); const int username_length = username.length(); std::string score_file_path; std::ifstream fin; for(int j=0; j<2; ++j) { if(j == 0) score_file_path.assign(KROWBAR_SCORE_PATH); else if(j == 1) score_file_path.assign(JU_SCORE_PATH); fin.open(score_file_path); while(std::getline(fin, line)) { char* line_c_string = new char[line.length()+1]; std::strcpy(line_c_string, line.c_str()); const int irc_username_length = username_length > USERNAME_LENGTH_LIMIT ? USERNAME_LENGTH_LIMIT : username_length; if(!std::strncmp(username.c_str(), line_c_string, irc_username_length)) { char number_of_tildes[21]; number_of_tildes[0] = '0'; //just in case the loop below doesn't detect any digits number_of_tildes[1] = '\0'; for(int i=0; i < 20; ++i) { if(std::isdigit(line_c_string[irc_username_length+3+i]) || line_c_string[irc_username_length+3+i] == '-' || line_c_string[irc_username_length+3+i] == '.') number_of_tildes[i] = line_c_string[irc_username_length+3+i]; else { number_of_tildes[i] = '\0'; //manually terminating the string break; } } number_of_tildes[20] = '\0'; //incase the number overflows 20 characters krowbar_amount[j] = strtol100(number_of_tildes); base_amount += krowbar_amount[j]; //multiplied by 100 inside strtol100() to convert tildecoins to centitildecoins, which //is the unit used throughout the program (and converted appropriately when displayed) break; } delete[] line_c_string; } fin.close(); } } #endif #ifndef DA_OFF //adding daily-adventure scores from troido to base amount if(!user_is_locked(get_username().c_str())) { std::string number_of_tildes = exec(TROIDO_DACOINS_CMD); number_of_tildes.pop_back(); //to get rid of the newline at the end if(is_number(number_of_tildes.c_str())) da_amount += strtol100(number_of_tildes.c_str()); base_amount += da_amount; //multiplied by 100 to convert tildecoins to centitildecoins, which //is the unit used throughout the program (and converted appropriately when displayed) } #endif #ifndef MINERCOIN_OFF if(!user_is_locked(get_username().c_str())) { std::string minercoin_cmd_pre_username, command_to_exec, number_of_tildes, minercoin_cmd_post_username(MINERCOIN_CMD_POST_USERNAME); //adding minercoin scores from minerobber to base amount (from "~username" and "username" entries in minerbot) for(int i=0; i < 2; ++i) { if(i == 0) minercoin_cmd_pre_username.assign(MINERCOIN_CMD_PRE_USERNAME); else if(i == 1) minercoin_cmd_pre_username.assign(MINERCOIN_CMD_PRE_USERNAME2); command_to_exec = minercoin_cmd_pre_username + get_username() + minercoin_cmd_post_username; number_of_tildes = exec(command_to_exec.c_str()); number_of_tildes.pop_back(); //to get rid of the newline at the end if(is_number(number_of_tildes.c_str())) { minercoin_amount[i] = strtol100(number_of_tildes.c_str()); base_amount += minercoin_amount[i]; //multiplied by 100 to convert tildecoins to centitildecoins, which //is the unit used throughout the program (and converted appropriately when displayed) } } } #endif srand((long int)(std::time(NULL)) + strtol_fast(exec(BIN_ECHO_CMD).c_str())); if(argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "help") || !strcmp(argv[1], "-h"))) { help(unaltered_base_amount); return 0; } if(argc > 1 && !strcmp(argv[1], "init")) { initialise_user(get_username().c_str(), base_amount); return 0; } if(!user_has_initialised(get_username().c_str())) { std::cout << "\nSorry, tcoin has not been initialised. Please execute `tcoin init` to complete initialisation or `tcoin help` for help.\n\n"; return ERR_NO_INIT; } if(argc > 1 && !strcmp(argv[1], "on")) { return log_on(get_username().c_str()); //return codes are inside the log_on function } if(argc > 1 && !strcmp(argv[1], "off")) { return log_off(get_username().c_str()); //return codes are inside the log_off function } if(!is_logged_on(get_username().c_str())) { std::cout << "\nSorry, you have not logged on to tildecoin yet. Please execute `tcoin on` to log in or `tcoin help` for help.\n\n"; return ERR_NOT_LOGGED_IN; } user_amount = get_file_value(get_username().c_str()); long long int total_amount = base_amount + user_amount; if(argc < 2) { //show last 10 messages show_messages_tail(get_username().c_str(), 10); show_balance(get_username().c_str(), total_amount, unaltered_base_amount, "base amount", user_amount, "transfers", krowbar_amount[0], "tilde game", krowbar_amount[1], "ju game", da_amount, "daily-adventure game", minercoin_amount[0]+minercoin_amount[1], "MinerCoin game"); } else if(!strcmp(argv[1], "breakdown") || !strcmp(argv[1], "-bd")) { std::cout << "Total balance: "; cout_formatted_amount(total_amount, " tildecoins\n", " tildecoin\n"); show_breakdown(unaltered_base_amount, "base amount", user_amount, "transfers", krowbar_amount[0], "tilde game", krowbar_amount[1], "ju game", da_amount, "daily-adventure game", minercoin_amount[0]+minercoin_amount[1], "MinerCoin game"); } else if(!strcmp(argv[1], "balance") || !strcmp(argv[1], "-b")) cout_formatted_amount(total_amount, "\n"); else if(!strcmp(argv[1], "messages") || !strcmp(argv[1], "-m")) { double number_of_messages = 0.0; bool number_of_messages_is_specified = argc > 2 && is_number(argv[2]); //number of messages specified if(number_of_messages_is_specified) number_of_messages = std::strtod(argv[2], NULL); if(number_of_messages >= 1.0) //number of messages specified is a valid number show_messages_tail(get_username().c_str(), (long long int)(number_of_messages)); else //show all messages show_messages(get_username().c_str()); } else if(!strcmp(argv[1], "clear_messages") || !strcmp(argv[1], "-cm")) { clear_messages(get_username().c_str()); } else if(!strcmp(argv[1], "send") || !strcmp(argv[1], "-s")) { if(argc == 5) { if(!strcmp(argv[2], "-s")) //silent send with no message { int return_value; long long int amount = 0; char *receiver = NULL; if(is_number(argv[3])) { amount = strtol100(argv[3]); receiver = argv[4]; } else { amount = strtol100(argv[4]); receiver = argv[3]; } return_value = send(get_username().c_str(), receiver, amount, base_amount, "silent"); if(!return_value) //send was successful send_message(get_username().c_str(), receiver, "", amount, "silent"); } else //argument count is 5 because a custom message was included { int return_value; long long int amount = 0; char *receiver = NULL; return_value = message_is_long(argv[4]); if(return_value) //message is too long { std::cout << "\nSorry, the message was longer than " << TCOIN_MSG_LENGTH_LIMIT << " characters. Please keep messages at or below this limit.\n\n"; return ERR_MAIN_MSG_TOO_LONG; } if(is_number(argv[2])) { amount = strtol100(argv[2]); receiver = argv[3]; } else { amount = strtol100(argv[3]); receiver = argv[2]; } return_value = send(get_username().c_str(), receiver, amount, base_amount, "verbose"); if(!return_value) //send was successful send_message(get_username().c_str(), receiver, argv[4], amount, "verbose"); } } else if(argc == 6) { if(!strcmp(argv[2], "-s")) { //argument count is 6 because of silent send with custom message included int return_value; long long int amount = 0; char *receiver = NULL; return_value = message_is_long(argv[5]); if(return_value) //message is too long { std::cout << "\nSorry, the message was longer than " << TCOIN_MSG_LENGTH_LIMIT << " characters. Please keep messages at or below this limit.\n\n"; return ERR_MAIN_MSG_TOO_LONG; } if(is_number(argv[3])) { amount = strtol100(argv[3]); receiver = argv[4]; } else { amount = strtol100(argv[4]); receiver = argv[3]; } return_value = send(get_username().c_str(), receiver, amount, base_amount, "silent"); if(!return_value) //send was successful send_message(get_username().c_str(), receiver, argv[5], amount, "silent"); } else { //too many command-line arguments were passed (6 args) (probably a message was intended) std::cout << "\nSorry, too many command-line arguments were passed. The correct format is `tcoin send \"\"`.\n\n"; return ERR_MAIN_SEND_TOO_MANY_ARGS_MSG; } } else if(argc < 4) { std::cout << "\nSorry, too few command-line arguments were passed. The correct format is `tcoin send `.\n\n"; return ERR_MAIN_SEND_TOO_FEW_ARGS; } else if(argc > 4) { std::cout << "\nSorry, too many command-line arguments were passed. The correct format is `tcoin send `.\n\n"; return ERR_MAIN_SEND_TOO_MANY_ARGS; } else { int return_value; long long int amount = 0; char* receiver = NULL; if(is_number(argv[2])) { amount = strtol100(argv[2]); receiver = argv[3]; } else { amount = strtol100(argv[3]); receiver = argv[2]; } return_value = send(get_username().c_str(), receiver, amount, base_amount, "verbose"); if(!return_value) //send was successful send_message(get_username().c_str(), receiver, "", amount, "verbose"); } } else if(!strcmp(argv[1], "silentsend") || !strcmp(argv[1], "-ss")) { if(argc==4) { int return_value; long long int amount = 0; char* receiver = NULL; if(is_number(argv[2])) { amount = strtol100(argv[2]); receiver = argv[3]; } else { amount = strtol100(argv[3]); receiver = argv[2]; } return_value = send(get_username().c_str(), receiver, amount, base_amount, "silent"); if(!return_value) //send was successful send_message(get_username().c_str(), receiver, "", amount, "silent"); } else if(argc==5) //custom message included { int return_value; long long int amount = 0; char* receiver = NULL; return_value = message_is_long(argv[4]); if(return_value) //message is too long { std::cout << "\nSorry, the message was longer than " << TCOIN_MSG_LENGTH_LIMIT << " characters. Please keep messages at or below this limit.\n\n"; return ERR_MAIN_MSG_TOO_LONG; } if(is_number(argv[2])) { amount = strtol100(argv[2]); receiver = argv[3]; } else { amount = strtol100(argv[3]); receiver = argv[2]; } return_value = send(get_username().c_str(), receiver, amount, base_amount, "silent"); if(!return_value) //send was successful send_message(get_username().c_str(), receiver, argv[4], amount, "silent"); } else return ERR_SILENTSEND; } else { std::cout << "\nSorry, an unknown command-line argument was received. `tcoin help` will print the help text.\n\n"; return ERR_UNKNOWN_ARG; } return 0; }