diff options
-rw-r--r-- | description | 1 | ||||
-rw-r--r-- | pcoin.cpp | 2294 | ||||
-rw-r--r-- | popen2.c | 134 | ||||
-rw-r--r-- | popen2.h | 17 | ||||
-rw-r--r-- | tcoin.cpp | 2151 |
5 files changed, 4597 insertions, 0 deletions
diff --git a/description b/description new file mode 100644 index 0000000..be7b8bb --- /dev/null +++ b/description @@ -0,0 +1 @@ +play money for tilde town and other tildeboxes diff --git a/pcoin.cpp b/pcoin.cpp index 242dc21..92d7eec 100644 --- a/pcoin.cpp +++ b/pcoin.cpp @@ -0,0 +1,2294 @@ +#include <iostream> +#include <fstream> +#include <cstdlib> +#include <cstdio> +#include <sstream> +#include <cstring> +#include <memory> +#include <stdexcept> +#include <array> +#include <vector> +#include <string> +#include <algorithm> +#include <iterator> +#include <sys/stat.h> +#include <ctime> +#include <unistd.h> +#include "popen2.h" +//set to 1 to enable some debug std::cout statements +#define DEBUG 0 + +#include "pcoin_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 PCOIN_NEW_KEY_CMD "/bin/cat /dev/urandom | /usr/bin/base64 | /usr/bin/head -c 64 | /usr/bin/tr '+' '-' | /usr/bin/tr '/' '_'" +#define LS_PCOIN_KEY_CMD "/bin/ls /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 PCOIN_BIN_PATH "/home/login/bin/pcoin" +#define PCOIN_BIN_PATH_W_SPACE "/home/login/bin/pcoin " +#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 $$" +#define CHMOD_PERMISSIONS ((S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO) +#define CHMOD_PROGRAM_KEY_PERMISSIONS (S_IRUSR & ~S_IWUSR & ~S_IXUSR & ~S_IRWXG & ~S_IRWXO) +#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 +#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_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_SILENTSEND 2 +#define ERR_MAIN_MSG_TOO_LONG 19 +#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_PCOIN_KEY_REFRESH_FAILED 20 + +#define ERR_IN_GET_INTERNAL_BALANCE -1 +#define ERR_IN_ADD_INTERNAL_BALANCE_GET_INTERNAL_TOTAL_OWED_FAILED -3 +#define ERR_ADD_INTERNAL_BALANCE_VALUE_TO_ADD_UNFULFILLABLE_USING_OWN_CURRENT_FUNDS -1 +#define ERR_ADD_INTERNAL_BALANCE_USERNAME_DOESNT_EXIST -2 +#define ERR_ADD_INTERNAL_BALANCE_ADD_TO_SELF -4 +#define ERR_ADD_FILE_VALUE_INSUFFICIENT_FUNDS 1 +#define ERR_ADD_FILE_VALUE_FATAL 999 +#define ERR_IN_GET_INTERNAL_TOTAL_OWED_SELF_PROGRAM_DOESNT_EXIST -1 +#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 +#define ERR_SEND_MESSAGE_SENDER_PROGRAM_MSG_FILE_UNABLE_TO_BE_UPDATED_FATAL 104 + +#define ERR_KEY_EMPTY 22 +#define ERR_KEY_NOT_IN_USE 9 +#define ERR_NO_ARGS 8 +#define ERR_IN_MAIN_GET_INTERNAL_TOTAL_OWED_FAILED 18 +#define ERR_INTERNAL_BALANCE_USERNAME_NOT_FOUND 17 +#define ERR_INTERNAL_BALANCE_NO_USERNAME_SUPPLIED 10 +#define ERR_INTERNAL_BALANCE_TOO_MANY_ARGS 11 +#define ERR_ADD_INTERNAL_BALANCE_TOO_FEW_ARGS 12 +#define ERR_ADD_INTERNAL_BALANCE_TOO_MANY_ARGS 13 +#define ERR_IN_MAIN_ADD_INTERNAL_BALANCE_AMOUNT_LARGER_THAN_COVERABLE_BY_UNOWED_BALANCE 14 +#define ERR_IN_MAIN_ADD_INTERNAL_BALANCE_AMOUNT_MAKING_USER_INTERNAL_BALANCE_NEGATIVE 16 +#define ERR_IN_MAIN_ADD_INTERNAL_BALANCE_NO_SUCH_USERNAME_FOUND 15 +#define ERR_IN_MAIN_ADD_INTERNAL_BALANCE_ADD_TO_SELF 21 + +void exit_program(const int error_number) +{ + // Cleanup to do before exiting the program + + // 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 (start with a NULL character) + 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<char, 128> buffer; + std::string result; + try + { + std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose); + if (!pipe) + { + ++i; + std::cout << "popen() failed - " << i << std::endl; + 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 <char> 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(); + + 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 ERR_ADD_FILE_VALUE_INSUFFICIENT_FUNDS; + } + + 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, CHMOD_PERMISSIONS); + + 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, CHMOD_PERMISSIONS); + break; + } + } + } + + delete[] file_path; + delete[] temp_file_path; + return 0; +} + +std::string global_username; +std::string get_username() +{ + return global_username; +} +int set_username(const std::string &username) +{ + global_username.assign(username); + return 0; +} + +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 tilde game amount +long long int minercoin_amount[2]; //minerobber's !minercoin game amount (tilded ~username entry and non-tilded username entry, both) + +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) + { + if(a0) + { + std::cout << amount0_source << ","; + cout_formatted_amount(amount0); + if(a1 || a2 || a3 || a4 || a5) + { + std::cout << ";"; + } + } + if(a1) + { + std::cout << amount1_source << ","; + cout_formatted_amount(amount1); + if(a2 || a3 || a4 || a5) + { + std::cout << ";"; + } + } + if(a2) + { + std::cout << amount2_source << ","; + cout_formatted_amount(amount2); + if(a3 || a4 || a5) + { + std::cout << ";"; + } + } + if(a3) + { + std::cout << amount3_source << ","; + cout_formatted_amount(amount3); + if(a4 || a5) + { + std::cout << ";"; + } + } + if(a4) + { + std::cout << amount4_source << ","; + cout_formatted_amount(amount4); + if(a5) + { + std::cout << ";"; + } + } + if(a5) + { + std::cout << amount5_source << ","; + cout_formatted_amount(amount5); + } + std::cout << "\n"; + } +} + +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()); + 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_tsv_messages(const char* username) //tab-separated-value messages (tsv) +{ + std::string messages_path = std::string(TCOIN_PROG_ACT_PATH) + std::string(username) + std::string("/_MESSAGES.txt"); + std::ifstream fin(messages_path.c_str()); + std::cout << fin.rdbuf(); + std::cout << "\n"; + 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<size_t>(source.tellg()); + std::vector<char> 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<std::streamoff>(buffer.size()), std::ios_base::end); + source.read(buffer.data(), buffer.size()); + newlineCount = std::count(buffer.begin(), buffer.end(), '\n'); + for(std::vector<char>::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: "<stuff>\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<char>::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 "<stuff>\n \_message>\n\n" is one message + --newlineCount; + } + std::cout << "Last " << lineCount << " Messages:\n"; + std::vector<char>::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"; + } +} + +void show_tsv_messages_tail(const char* username, int lineCount) +{ + size_t const granularity = 100 * lineCount; + std::string messages_path = std::string(TCOIN_PROG_ACT_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<size_t>(source.tellg()); + std::vector<char> 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<std::streamoff>(buffer.size()), std::ios_base::end); + source.read(buffer.data(), buffer.size()); + newlineCount = std::count(buffer.begin(), buffer.end(), '\n'); + } + std::vector<char>::iterator start = buffer.begin(); + while(newlineCount > lineCount) + { + start = std::find(start, buffer.end(), '\n') + 1; + --newlineCount; + } + std::vector<char>::iterator end = remove(start, buffer.end(), '\r'); + std::cout << std::string(start, end); +} + +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; +} + +std::string refresh_pcoin_key() +{ + std::string new_key; + const std::string username = get_username(); + char* program_key_path = new char[username.length() + sizeof(PCOIN_KEY_PATH_W_SLASH) + 4]; //sizeof counts NULL char at the end too + char* temp_program_key_path = new char[username.length() + sizeof(PCOIN_KEY_PATH_W_SLASH) + 8]; //sizeof counts NULL char at the end too + char* temp2_program_key_path = new char[username.length() + sizeof(PCOIN_KEY_PATH_W_SLASH) + 9]; //sizeof counts NULL char at the end too + + std::strcpy(program_key_path, PCOIN_KEY_PATH_W_SLASH); + std::strcat(program_key_path, username.c_str()); + std::strcpy(temp_program_key_path, program_key_path); + std::strcpy(temp2_program_key_path, program_key_path); + std::strcat(program_key_path, ".txt"); + std::strcat(temp_program_key_path, "_tmp.txt"); + std::strcat(temp2_program_key_path, "_tmp2.txt"); + + std::ifstream fin(program_key_path); + + if(!fin) + { + delete[] program_key_path; + delete[] temp_program_key_path; + delete[] temp2_program_key_path; + return std::string("n/a"); + } + + fin.close(); + + if(!std::rename(program_key_path, temp_program_key_path)) + { + chmod(temp_program_key_path, CHMOD_PERMISSIONS); + std::ofstream fin2(temp2_program_key_path); + + if(!fin2) + { + fin2.close(); + while(1) + { + if(!std::rename(temp_program_key_path, program_key_path)) + { + chmod(program_key_path, CHMOD_PROGRAM_KEY_PERMISSIONS); + break; + } + } + delete[] temp2_program_key_path; + delete[] temp_program_key_path; + delete[] program_key_path; + return std::string("n/a"); + } + + new_key = exec(PCOIN_NEW_KEY_CMD); + fin2 << new_key << "\n"; + fin2.close(); + chmod(temp2_program_key_path, CHMOD_PROGRAM_KEY_PERMISSIONS); + + while(1) + { + if(!std::rename(temp2_program_key_path, program_key_path)) + { + chmod(program_key_path, CHMOD_PROGRAM_KEY_PERMISSIONS); + while(1) + { + if(!std::remove(temp_program_key_path)) + break; + } + break; + } + } + delete[] program_key_path; + delete[] temp_program_key_path; + delete[] temp2_program_key_path; + return new_key; + } + else + { + delete[] program_key_path; + delete[] temp_program_key_path; + delete[] temp2_program_key_path; + return std::string("n/a"); + } + + delete[] program_key_path; + delete[] temp_program_key_path; + delete[] temp2_program_key_path; + return std::string("n/a"); +} + +bool username_exists(const char* username) +{ + const static std::string all_usernames = exec(LS_HOME_CMD); + std::istringstream iss(all_usernames); + static std::vector<std::string> usernames{std::istream_iterator<std::string>{iss}, std::istream_iterator<std::string>{}}; + if(std::find(usernames.begin(), usernames.end(), username) != usernames.end()) + { + return true; + } + return program_exists(username); +} + +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)) //we need to go inside the loop body even if one of the reads fails, so we use || instead of && + { + 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. +} + +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); + 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"); // length = 13 + + //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]; + 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, CHMOD_PERMISSIONS); + + 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]; + std::strcpy(really_temp_receiver_path, temp_receiver_path); + std::strcat(really_temp_receiver_path, "_tmp"); + + 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 << "<new-line>"; //return-key pressed + else if(message[i] == '\v') + fout << "<vertical-tab>"; //vertical-tab symbol entered + else + fout << message[i]; + } + fout << "\n\n"; + } + fout.close(); + chmod(really_temp_receiver_path, CHMOD_PERMISSIONS); + + 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, CHMOD_PERMISSIONS); + + 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(), CHMOD_PERMISSIONS); + } + + 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 << "<new-line>"; //return-key pressed + else if(message[i] == '\v') + fout << "<vertical-tab>"; //vertical-tab symbol entered + else + fout << message[i]; + } + fout << "\n"; + } + fout.close(); + chmod(really_temp_program_receiver_path.c_str(), CHMOD_PERMISSIONS); + + 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_program_messages + while(1) + { + if(!std::rename(temp_program_receiver_path.c_str(), program_receiver_path.c_str())) + break; + } + + chmod(program_receiver_path.c_str(), CHMOD_PERMISSIONS); + 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]; + 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"); + + fin.open(temp_sender_path); + fout.open(really_temp_sender_path); + chmod(really_temp_sender_path, CHMOD_PERMISSIONS); + + 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 << "<new-line>"; //return-key pressed + else if(message[i] == '\v') + fout << "<vertical-tab>"; //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; + + //additional place to write if sending from a program: + if(program_exists(sender_username)) + { + random_string = std::string("rand"); + std::string program_sender_path = std::string(TCOIN_PROG_ACT_PATH) + std::string(sender_username) + std::string("/_MESSAGES.txt"); + std::string temp_program_sender_path = std::string(TCOIN_PROG_ACT_PATH) + std::string(sender_username) + std::string("/_MESSAGES") + random_string + std::string(".txt"); + std::string really_temp_program_sender_path = temp_program_sender_path + std::string("_tmp"); + + //create program sender's _MESSAGES file if none exists + { + std::ifstream fin(program_sender_path.c_str()); + std::ifstream fin2(temp_program_sender_path.c_str()); + if((!fin || file_is_empty(fin)) && (!fin2)) + { + fin.close(); + std::ofstream fout(program_sender_path.c_str(), std::fstream::trunc); + fout << "\n"; + fout.close(); + } + else + fin.close(); + fin2.close(); + chmod(program_sender_path.c_str(), CHMOD_PERMISSIONS); + } + + while(1) + { + if(!std::rename(program_sender_path.c_str(), temp_program_sender_path.c_str())) + { + std::ifstream fin(temp_program_sender_path.c_str()); + std::ofstream fout(really_temp_program_sender_path.c_str()); + + if(!file_is_empty(fin)) + fout << fin.rdbuf(); + fin.close(); + + //now (updated by code above to reflect "sending time"), 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(-1*amount_sent); + fout << now << "\t" << receiver_formatted_string << "\t" << sender_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 << "<new-line>"; //return-key pressed + else if(message[i] == '\v') + fout << "<vertical-tab>"; //vertical-tab symbol entered + else + fout << message[i]; + } + fout << "\n"; + } + fout.close(); + chmod(really_temp_program_sender_path.c_str(), CHMOD_PERMISSIONS); + + if(!fout) //error + { + std::cerr << "Fatal error " << ERR_SEND_MESSAGE_SENDER_PROGRAM_MSG_FILE_UNABLE_TO_BE_UPDATED_FATAL << ": the sender 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_SENDER_PROGRAM_MSG_FILE_UNABLE_TO_BE_UPDATED_FATAL); + } + else + { + std::remove(temp_program_sender_path.c_str()); + } + + while(1) + { + if(!std::rename(really_temp_program_sender_path.c_str(), temp_program_sender_path.c_str())) + break; + } + + // unlock_sender_program_messages + while(1) + { + if(!std::rename(temp_program_sender_path.c_str(), program_sender_path.c_str())) + break; + } + + chmod(program_sender_path.c_str(), CHMOD_PERMISSIONS); + break; + }//if statement with !std::rename for sender's program accounting _messages file + }//while loop for program accounting sender's _messages file + }//sender is program account + + 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; + + //additional place to deduct from if the sender is a program (which is + //always the case when `pcoin` is used, but we'll check anyway + //we do this before the "tcoin send" part so that if program tries to send + //more than it owes receiver, it's checked before checking "tcoin send's" + //conditions + if(program_exists(sender_username)) + { + random_string = std::string("rand"); //not really random + std::string program_sender_path = std::string(TCOIN_PROG_ACT_PATH) + std::string(sender_username) + std::string("/") + std::string(receiver_username) + std::string(".txt"); + std::string temp_program_sender_path = std::string(TCOIN_PROG_ACT_PATH) + std::string(sender_username) + std::string("/") + std::string(receiver_username) + random_string + std::string(".txt"); + std::string temp_program_sender_username = std::string(PROG_ACT_W_SLASH) + std::string(sender_username) + std::string("/") + std::string(receiver_username) + random_string; + + //create program sender's "receiver's balance file" if none exists + { + std::ifstream fin(program_sender_path.c_str()); + std::ifstream fin2(temp_program_sender_path.c_str()); + #if DEBUG + std::cout << program_sender_path << "," << temp_program_sender_path << "," << !fin << "," << file_is_empty(fin) << "," << !fin2 << std::endl; + #endif + if((!fin || file_is_empty(fin)) && (!fin2)) + { + fin.close(); + std::ofstream fout(program_sender_path.c_str(), std::fstream::trunc); + fout << "0\n"; + fout.close(); + #if DEBUG + char dummy; + std::cout << "Press enter to continue"; std::cin >> dummy; + #endif + } + else + fin.close(); + fin2.close(); + chmod(program_sender_path.c_str(), CHMOD_PERMISSIONS); + } + + while(1) + { + if(!std::rename(program_sender_path.c_str(), temp_program_sender_path.c_str())) + { + //Insufficient funds check in add_file_value() itself + //third argument, base_amount, is 0 because program + //should not be allowed to send more than what is owed + //to the receiver + return_value = add_file_value(temp_program_sender_username.c_str(), -1 * amount_to_send, 0); + + //The same amount must also be deducted from the "_total.txt" file + //which records the total amount owed to others + if(return_value == 0) + { + random_string = std::string("rand"); + std::string program_sender_total_path = std::string(TCOIN_PROG_ACT_PATH) + std::string(sender_username) + std::string("/_TOTAL.txt"); + std::string temp_program_sender_total_path = std::string(TCOIN_PROG_ACT_PATH) + std::string(sender_username) + std::string("/_TOTAL") + random_string + std::string(".txt"); + std::string temp_program_sender_total_username = std::string(PROG_ACT_W_SLASH) + std::string(sender_username) + std::string("/_TOTAL") + random_string; + + //create program sender's "total balance file" if none exists + { + std::ifstream fin(program_sender_total_path.c_str()); + std::ifstream fin2(temp_program_sender_total_path.c_str()); + if((!fin || file_is_empty(fin)) && (!fin2)) + { + fin.close(); + std::ofstream fout(program_sender_total_path.c_str(), std::fstream::trunc); + fout << "0\n"; + fout.close(); + } + else + fin.close(); + fin2.close(); + chmod(program_sender_total_path.c_str(), CHMOD_PERMISSIONS); + } + + while(1) + { + if(!std::rename(program_sender_total_path.c_str(), temp_program_sender_total_path.c_str())) + { + //Insufficient funds check in add_file_value() itself + //third argument, base_amount, is 0 because program + //should not be allowed to send more than what is owed + //to the receiver + add_file_value(temp_program_sender_total_username.c_str(), -1 * amount_to_send, 0); + + random_string = std::to_string(rand()); + + // sizeof() includes '\0' + 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"); // length = 4 + + std::strcpy(sender_path, TCOIN_PATH_W_SLASH); + std::strcat(sender_path, sender_username); + std::strcat(sender_path, ".txt"); // length = 4 + + while(1) + { + if(!std::rename(sender_path, temp_sender_path)) + { + //Insufficient funds check is in add_file_value() + 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::string("rand"); + + // sizeof() includes '\0' + 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 '\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 << "0\n"; + fout.close(); + } + else + fin.close(); + fin2.close(); + fin3.close(); + + chmod(receiver_path, CHMOD_PERMISSIONS); + + 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(), CHMOD_PERMISSIONS); + } + + 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(), CHMOD_PERMISSIONS); + } + + 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; + } + } + } + + while(1) + { + if(!std::rename(temp_program_receiver_path.c_str(), program_receiver_path.c_str())) + break; + } + break; + } + } + } + + 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"; + } + + while(1) + { + if(!std::rename(temp_receiver_path, receiver_path)) + break; + } + delete[] temp_receiver_path; + delete[] receiver_path; + delete[] temp_receiver_username; + break; + } + } + } + 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; + } + } + + while(1) + { + if(!std::rename(temp_program_sender_total_path.c_str(), program_sender_total_path.c_str())) + break; + } + break; + } + } + } + else if(return_value == 1) + { + long long int amount_owed = get_file_value(temp_program_sender_username.c_str()); + long long int amount_to_aib = (long long int)(amount_to_send) - amount_owed; + std::cout << "\nSorry, you only owe `" << receiver_username << "` "; + cout_formatted_amount(amount_owed, " tildecoins", " tildecoin"); + std::cout << ", not "; + cout_formatted_amount(amount_to_send, " tildecoins. ", " tildecoin. "); + std::cout << "Please run `" << PCOIN_BIN_PATH_W_SPACE << "add_internal_balance " << receiver_username << " "; + cout_formatted_amount(amount_to_aib); + std::cout << "` to sufficiently increase the amount owed to `" << receiver_username << "`.\n\n"; + + final_return_value = ERR_INSUFFICIENT_FUNDS; //we don't simply "return 3" here because we want temp_program_sender_path to get renamed again + } + + while(1) + { + if(!std::rename(temp_program_sender_path.c_str(), program_sender_path.c_str())) + break; + } + break; + } + } + } + } + } + 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() +{ + std::cout <<"\npcoin is meant for programs. After each of the following commands, you will have to input a valid key to stdin."; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "messages` or `" << PCOIN_BIN_PATH_W_SPACE << "-m`: check your messages"; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "messages <num>` or `" << PCOIN_BIN_PATH_W_SPACE << "-m <num>`: print the last <num> messages"; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "messages_tsv` or `" << PCOIN_BIN_PATH_W_SPACE << "-mtsv`: check your messages in tab-separated-values format"; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "messages_tsv <num>` or `" << PCOIN_BIN_PATH_W_SPACE << "-mtsv <num>`: print the last <num> messages in tab-separated-values format"; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "balance` or `" << PCOIN_BIN_PATH_W_SPACE << "-b`: print the number representing your balance"; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "total_owed` or `" << PCOIN_BIN_PATH_W_SPACE << "-to`: print the total amount owed to others"; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "internal_balance <username>` or `" << PCOIN_BIN_PATH_W_SPACE << "-ib <username>`: print the amount you owe <username>"; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "add_internal_balance <username>` or `" << PCOIN_BIN_PATH_W_SPACE << "-aib <username> <amount>`: add <amount> to the amount you owe <username>"; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "send <username> <amount>` or `" << PCOIN_BIN_PATH_W_SPACE << "-s <username> <amount>`: send <amount> tildecoins to <username>"; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "send <username> <amount> \"<message>\"` or `" << PCOIN_BIN_PATH_W_SPACE << "-s <username> <amount> \"<message>\"`: optionally, include a message to be sent to <username>"; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "silentsend <username> <amount> [\"<message>\"]`, `" << PCOIN_BIN_PATH_W_SPACE << "send -s <username> <amount> [\"<message>\"]` or `" << PCOIN_BIN_PATH_W_SPACE << "-ss <username> <amount> [\"<message>\"]`: send <amount> tildecoins to <username> with an optional (as indicated by [ and ], which should not be included in the actual comment) message included without printing anything"; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "refresh_key` or `" << PCOIN_BIN_PATH_W_SPACE << "-rk`: generate a new key for your pcoin account and print it"; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "name` or `" << PCOIN_BIN_PATH_W_SPACE << "-n`: print the name on the account"; + std::cout << "\nIn the commands with `<username> <amount>`, switching the two arguments around (i.e., from `<username> <amount>` to `<amount> <username>`) will also work"; + std::cout << "\n`" << PCOIN_BIN_PATH_W_SPACE << "--help`, `" << PCOIN_BIN_PATH_W_SPACE << "help` or `" << PCOIN_BIN_PATH_W_SPACE << "-h`: print this help text"; + std::cout << "\nSend an email to " << TCOIN_PASS_RESET_CONTACT_EMAIL << " to report any errors or request a key for your program.\n\n"; +} + +bool is_number(const char* test_string) +{ + char* p; + strtod(test_string, &p); + return *p == 0; +} + +std::string get_username_from_key(std::string &key) +{ + std::ifstream codefin(TCOIN_CODEZ_PATH); + char code1[513], code2[513], code3[513]; + codefin >> code1; + codefin >> code2; + codefin >> code3; + codefin.close(); + const static std::string all_usernames_dot_txt = exec2((std::string(TCOIN_BIN_PATH_W_SPACE) + std::string("pcoin_list")).c_str(), std::string(code3)); + + std::istringstream iss(all_usernames_dot_txt); + + std::string word1, word2, program_name("n/a"); + //first word is program username with .txt on the end, second word is key + while(iss >> word1) + { + char *program_key_path = new char[strlen(word1.c_str()) + sizeof(PCOIN_KEY_PATH_W_SLASH)]; //sizeof counts NULL char at the end too + std::strcpy(program_key_path, PCOIN_KEY_PATH_W_SLASH); + std::strcat(program_key_path, word1.c_str()); + + std::ifstream fin(program_key_path); + + fin >> word2; + + if(!strctcmp(word2.c_str(), key.c_str())) + { + fin.close(); + delete[] program_key_path; + program_name = word1.substr(0, word1.size()-4); //removing .txt from the username returned + } + else + { + fin.close(); + delete[] program_key_path; + } + } + return program_name; +} + +long long int get_internal_balance(const char* username) +{ + //sometimes, it just helps to double-check things + //this makes the security of this function decoupled + //from the 'deny access' mechanism in the "main" + //function when an incorrect key is entered + if(program_exists(get_username().c_str()) && username_exists(username)) + { + std::string internal_path = std::string(TCOIN_PROG_ACT_PATH) + get_username() + std::string("/") + std::string(username) + std::string(".txt"); + //create internal balance file if none exists + { + std::ifstream fin(internal_path.c_str()); + if(!fin || file_is_empty(fin)) + { + fin.close(); + std::ofstream fout(internal_path.c_str(), std::fstream::trunc); + fout << "0\n"; + fout.close(); + } + else + fin.close(); + chmod(internal_path.c_str(), CHMOD_PERMISSIONS); + } + + std::string internal_username = std::string(PROG_ACT_W_SLASH) + get_username() + std::string("/") + std::string(username); + return get_file_value(internal_username.c_str()); + } + return ERR_IN_GET_INTERNAL_BALANCE; +} + +long long int get_internal_total_owed() +{ + if(program_exists(get_username().c_str())) + { + std::string internal_total_path = std::string(TCOIN_PROG_ACT_PATH) + get_username() + std::string("/_TOTAL.txt"); + //create internal total file if none exists + { + std::ifstream fin(internal_total_path.c_str()); + if(!fin || file_is_empty(fin)) + { + fin.close(); + std::ofstream fout(internal_total_path.c_str(), std::fstream::trunc); + fout << "0\n"; + fout.close(); + } + else + fin.close(); + chmod(internal_total_path.c_str(), CHMOD_PERMISSIONS); + } + + std::string internal_total_username = std::string(PROG_ACT_W_SLASH) + get_username() + std::string("/_TOTAL"); + return get_file_value(internal_total_username.c_str()); + } + return ERR_IN_GET_INTERNAL_TOTAL_OWED_SELF_PROGRAM_DOESNT_EXIST; +} + +int add_internal_balance(const char* username, const long long int value_to_add) +{ + if(!strcmp(get_username().c_str(), username)) + { + return ERR_ADD_INTERNAL_BALANCE_ADD_TO_SELF; + } + if(program_exists(get_username().c_str()) && username_exists(username)) + { + std::string random_string = std::string("rand"); + std::string internal_username = std::string(PROG_ACT_W_SLASH) + get_username() + std::string("/") + std::string(username); + std::string temp_internal_username = std::string(PROG_ACT_W_SLASH) + get_username() + std::string("/") + std::string(username) + random_string; + + long long int internal_total_owed = get_internal_total_owed(); + if(internal_total_owed == ERR_IN_GET_INTERNAL_TOTAL_OWED_SELF_PROGRAM_DOESNT_EXIST) + { + std::cerr << "\nError in add_internal_balance()! get_internal_total_owed() failed!\n\n"; + return ERR_IN_ADD_INTERNAL_BALANCE_GET_INTERNAL_TOTAL_OWED_FAILED; + } + + if((value_to_add > 0) && (value_to_add > (base_amount + user_amount - internal_total_owed))) + { + return ERR_ADD_INTERNAL_BALANCE_VALUE_TO_ADD_UNFULFILLABLE_USING_OWN_CURRENT_FUNDS; //value_to_add is more than what the program can fulfil using its own current funds + } + + std::string internal_path = std::string(TCOIN_PROG_ACT_PATH) + get_username() + std::string("/") + std::string(username) + std::string(".txt"); + std::string temp_internal_path = std::string(TCOIN_PROG_ACT_PATH) + get_username() + std::string("/") + std::string(username) + random_string + std::string(".txt"); + //create internal file if none exists + { + std::ifstream fin(internal_path.c_str()); + std::ifstream fin2(temp_internal_path.c_str()); + if((!fin || file_is_empty(fin)) && (!fin2)) + { + fin.close(); + std::ofstream fout(internal_path.c_str(), std::fstream::trunc); + fout << "0\n"; + fout.close(); + } + else + fin.close(); + fin2.close(); + chmod(internal_path.c_str(), CHMOD_PERMISSIONS); + } + + int return_value; + int final_return_value = 0; + while(1) + { + if(!std::rename(internal_path.c_str(), temp_internal_path.c_str())) + { + return_value = add_file_value(temp_internal_username.c_str(), value_to_add, 0); //cannot make user's internal balance negative, so base_amount is 0 + + if(return_value) //if return value is non-zero + { + final_return_value = return_value; //we don't simply "return return_value" because we want files to get renamed back to their original filenames + } + else //return value is zero, i.e., add_file_value succeeded + //also need to update _total.txt + { + random_string = std::string("rand"); + std::string internal_total_username = std::string(PROG_ACT_W_SLASH) + get_username() + std::string("/_TOTAL"); + std::string temp_internal_total_username = std::string(PROG_ACT_W_SLASH) + get_username() + std::string("/_TOTAL") + random_string; + std::string internal_total_path = std::string(TCOIN_PROG_ACT_PATH) + get_username() + std::string("/_TOTAL.txt"); + std::string temp_internal_total_path = std::string(TCOIN_PROG_ACT_PATH) + get_username() + std::string("/_TOTAL") + random_string + std::string(".txt"); + + //create _total.txt file if none exists + { + std::ifstream fin(internal_total_path.c_str()); + std::ifstream fin2(temp_internal_total_path.c_str()); + if((!fin || file_is_empty(fin)) && (!fin2)) + { + fin.close(); + std::ofstream fout(internal_total_path.c_str(), std::fstream::trunc); + fout << "0\n"; + fout.close(); + } + else + fin.close(); + fin2.close(); + chmod(internal_total_path.c_str(), CHMOD_PERMISSIONS); + } + + while(1) + { + if(!std::rename(internal_total_path.c_str(), temp_internal_total_path.c_str())) + { + return_value = add_file_value(temp_internal_total_username.c_str(), value_to_add, 0); + while(1) + { + if(!std::rename(temp_internal_total_path.c_str(), internal_total_path.c_str())) + break; + } + break; + } + } + if(return_value) //if return value is non-zero + final_return_value = return_value; //we don't simply "return return_value" because we want files to get renamed back to their original filenames + } + + while(1) + { + if(!std::rename(temp_internal_path.c_str(), internal_path.c_str())) + break; + } + break; + } + } + return final_return_value; + } + return ERR_ADD_INTERNAL_BALANCE_USERNAME_DOESNT_EXIST; +} + +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[]) +{ + if(argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "help") || !strcmp(argv[1], "-h"))) + { + help(); + return 0; + } + + { + std::string key; + std::string program_username; + std::getline(std::cin, key); + + if(!key.compare("")) + { + std::cout << "\nSorry, you incorrectly specified an empty key.\n\n"; + return ERR_KEY_EMPTY; + } + + program_username.assign(get_username_from_key(key)); + + if(!program_username.compare("n/a")) + { + std::cout << "\nSorry, the key you specified is not in use.\n\n"; + return ERR_KEY_NOT_IN_USE; + } + set_username(program_username); + } + + base_amount = 0; + long long int unaltered_base_amount = base_amount; + user_amount = 0; + krowbar_amount[0] = krowbar_amount[1] = 0; + minercoin_amount[0] = minercoin_amount[1] = 0; + + #ifndef KROWBAR_OFF + //adding tildebot scores from krowbar to base amount + { + std::string line; + const std::string username = get_username(); + const int username_length = username.length(); + + std::string score_file_path; + + 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); + + std::ifstream fin(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(!strncasecmp(username.c_str(), line_c_string, irc_username_length)) //username starts with capital letter, but name in database does not + { + 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) + } + delete[] line_c_string; + } + } + } + #endif + + #if DEBUG + std::string debug_string(""); + #endif + + #ifndef MINERCOIN_OFF + { + std::string command_to_exec, minercoin_cmd_pre_username, number_of_tildes, username(get_username()); + username.at(0) = username.at(0)+'a'-'A'; + const std::string minercoin_cmd_post_username(MINERCOIN_CMD_POST_USERNAME), lowercase_username(username); + //adding minercoin scores from minerobber to base amount (from "~username" and "username", both, in minerbot) + #if DEBUG + debug_string += lowercase_username; + #endif + 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 + lowercase_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 + + user_amount = get_file_value(get_username().c_str()); + + srand((long int)(std::time(NULL)) + strtol_fast(exec(BIN_ECHO_CMD).c_str())); + + long long int total_amount = base_amount + user_amount; + + if(argc < 2) + { + std::cout << "\nSorry, `" << PCOIN_BIN_PATH << "` doesn't work. Please use `" << PCOIN_BIN_PATH_W_SPACE << "-m` for messages or `" << PCOIN_BIN_PATH_W_SPACE << "-b` to check your balance. `" << PCOIN_BIN_PATH_W_SPACE << "--help` prints the help text.\n\n"; + return ERR_NO_ARGS; + } + else if(!strcmp(argv[1], "breakdown") || !strcmp(argv[1], "-bd")) + { + std::cout << "total,"; + cout_formatted_amount(total_amount, ";", ";"); + show_breakdown(unaltered_base_amount, "baseamount", user_amount, "transfers", krowbar_amount[0], "tildegame", krowbar_amount[1], "jugame", minercoin_amount[0]+minercoin_amount[1], "minercoingame"); + } + #if DEBUG + else if(!strcmp(argv[1], "debug_string")) + { + std::cout << debug_string; + } + #endif + 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], "messages_tsv") || !strcmp(argv[1], "-mtsv")) + { + 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_tsv_messages_tail(get_username().c_str(), (long long int)(number_of_messages)); + else //show all messages + show_tsv_messages(get_username().c_str()); + } + else if(!strcmp(argv[1], "balance") || !strcmp(argv[1], "-b")) + cout_formatted_amount(total_amount, "\n"); + else if(!strcmp(argv[1], "total_owed") || !strcmp(argv[1], "-to")) + { + long long int total_owed = get_internal_total_owed(); + if(total_owed == ERR_IN_GET_INTERNAL_TOTAL_OWED_SELF_PROGRAM_DOESNT_EXIST) + { + std::cerr << "\nError in main()! get_internal_total_owed() failed!\n\n"; + return ERR_IN_MAIN_GET_INTERNAL_TOTAL_OWED_FAILED; + } + cout_formatted_amount(total_owed, "\n"); + } + else if(!strcmp(argv[1], "internal_balance") || !strcmp(argv[1], "-ib")) + { + if(argc == 3) //second argument (the one right after "-ib") is the username + { + long long int internal_balance = get_internal_balance(argv[2]); + if(internal_balance == ERR_IN_GET_INTERNAL_BALANCE) //username check doesn't pass + { + std::cout << "\nSorry, no user with the username `" << argv[2] << "` was found.\n\n"; + return ERR_INTERNAL_BALANCE_USERNAME_NOT_FOUND; + } + cout_formatted_amount(internal_balance, "\n"); + } + else if(argc == 2) //no username supplied (too few arguments supplied) + { + std::cout << "\nSorry, too few command-line arguments were passed. The correct format is `" << PCOIN_BIN_PATH_W_SPACE << "internal_balance <username>`.\n\n"; + return ERR_INTERNAL_BALANCE_NO_USERNAME_SUPPLIED; + } + else if(argc > 3) //too many arguments supplied + { + std::cout << "\nSorry, too many command-line arguments were passed. The correct format is `" << PCOIN_BIN_PATH_W_SPACE << "internal_balance <username>`.\n\n"; + return ERR_INTERNAL_BALANCE_TOO_MANY_ARGS; + } + } + else if(!strcmp(argv[1], "add_internal_balance") || !strcmp(argv[1], "-aib")) + { + if(argc < 4) + { + std::cout << "\nSorry, too few command-line arguments were passed. The correct format is `" << PCOIN_BIN_PATH_W_SPACE << "add_internal_balance <username> <amount>`.\n\n"; + return ERR_ADD_INTERNAL_BALANCE_TOO_FEW_ARGS; + } + else if(argc > 4) + { + std::cout << "\nSorry, too many command-line arguments were passed. The correct format is `" << PCOIN_BIN_PATH_W_SPACE << "add_internal_balance <username> <amount>`.\n\n"; + return ERR_ADD_INTERNAL_BALANCE_TOO_MANY_ARGS; + } + // number of arguments is exactly 3 + { + int return_value = ERR_IN_ADD_INTERNAL_BALANCE_GET_INTERNAL_TOTAL_OWED_FAILED, return_value2 = ERR_IN_ADD_INTERNAL_BALANCE_GET_INTERNAL_TOTAL_OWED_FAILED; + if(is_number(argv[3])) + return_value = add_internal_balance(argv[2], strtol100(argv[3])); + else + return_value2 = add_internal_balance(argv[3], strtol100(argv[2])); + + if(return_value == ERR_ADD_INTERNAL_BALANCE_ADD_TO_SELF || return_value2 == ERR_ADD_INTERNAL_BALANCE_ADD_TO_SELF) //cannot add to self internal balance + { + std::cout << "\nSorry, you cannot add to your own internal balance.\n\n"; + return ERR_ADD_INTERNAL_BALANCE_ADD_TO_SELF; + } + if(return_value == ERR_ADD_INTERNAL_BALANCE_VALUE_TO_ADD_UNFULFILLABLE_USING_OWN_CURRENT_FUNDS || return_value2 == ERR_ADD_INTERNAL_BALANCE_VALUE_TO_ADD_UNFULFILLABLE_USING_OWN_CURRENT_FUNDS) //value_to_add was too large + { + std::cout << "\nSorry, the amount was larger than what the program's current unowed balance could cover.\n\n"; + return ERR_IN_MAIN_ADD_INTERNAL_BALANCE_AMOUNT_LARGER_THAN_COVERABLE_BY_UNOWED_BALANCE; + } + if(return_value == ERR_ADD_FILE_VALUE_INSUFFICIENT_FUNDS) //value_to_add was too negative + { + std::cout << "\nSorry, the amount was more negative than what `" << argv[2] << "` could cover.\n\n"; + return ERR_IN_MAIN_ADD_INTERNAL_BALANCE_AMOUNT_MAKING_USER_INTERNAL_BALANCE_NEGATIVE; + } + if(return_value2 == ERR_ADD_FILE_VALUE_INSUFFICIENT_FUNDS) //value_to_add was too negative + { + std::cout << "\nSorry, the amount was more negative than what `" << argv[3] << "` could cover.\n\n"; + return ERR_IN_MAIN_ADD_INTERNAL_BALANCE_AMOUNT_MAKING_USER_INTERNAL_BALANCE_NEGATIVE; + } + if(return_value == ERR_ADD_INTERNAL_BALANCE_USERNAME_DOESNT_EXIST) //username check doesn't pass + { + std::cout << "\nSorry, no user with the username `" << argv[2] << "` was found.\n\n"; + return ERR_IN_MAIN_ADD_INTERNAL_BALANCE_NO_SUCH_USERNAME_FOUND; + } + if(return_value2 == ERR_ADD_INTERNAL_BALANCE_USERNAME_DOESNT_EXIST) //username check doesn't pass + { + std::cout << "\nSorry, no user with the username `" << argv[3] << "` was found.\n\n"; + return ERR_IN_MAIN_ADD_INTERNAL_BALANCE_NO_SUCH_USERNAME_FOUND; + } + } + } + else if(!strcmp(argv[1], "send") || !strcmp(argv[1], "-s")) + { + if(argc == 5) + { + if(!strcmp(argv[2], "-s")) + { + 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 <username> <amount> \"<message>\"`.\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 `" << PCOIN_BIN_PATH_W_SPACE << "send <username> <amount>`.\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 `" << PCOIN_BIN_PATH_W_SPACE << "send <username> <amount>`.\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"); + } + 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 if(!strcmp(argv[1], "refresh_key") || !strcmp(argv[1], "-rk")) + { + std::string new_key; + + new_key.assign(refresh_pcoin_key()); + + if(!new_key.compare("n/a") || new_key.length() != 64) + { + std::cout << "\nSorry, key was not refreshed.\n\n"; //make sure this is less than 64 characters + return ERR_PCOIN_KEY_REFRESH_FAILED; + } + else + std::cout << new_key << "\n"; + } + else if(!strcmp(argv[1], "name") || !strcmp(argv[1], "-n")) + { + std::cout << get_username() << "\n"; + } + else + { + std::cout << "\nSorry, an unknown command-line argument was received. `" << PCOIN_BIN_PATH_W_SPACE << "help` will print the help text.\n\n"; + return ERR_UNKNOWN_ARG; + } + + return 0; +} diff --git a/popen2.c b/popen2.c index 3434d51..403c324 100644 --- a/popen2.c +++ b/popen2.c @@ -0,0 +1,134 @@ +#include"popen2.h" +#include<errno.h> +#include<stdlib.h> +#include<sys/wait.h> +#include<unistd.h> + +#define CLEANUP_PIPE(pipe) close((pipe)[0]); close((pipe)[1]) + +//https://github.com/iximiuz/popen2 +//https://iximiuz.com/en/posts/how-to-on-processes/ + +typedef struct files_t files_t; + +struct files_chain_t { + files_t files; + pid_t pid; + struct files_chain_t *next; +}; +typedef struct files_chain_t files_chain_t; + +static files_chain_t *files_chain; + +static int _do_popen2(files_chain_t *link, const char *command) +{ + int child_in[2]; + int child_out[2]; + if (0 != pipe(child_in)) { + return -1; + } + if (0 != pipe(child_out)) { + CLEANUP_PIPE(child_in); + return -1; + } + + pid_t cpid = link->pid = fork(); + if (0 > cpid) { + CLEANUP_PIPE(child_in); + CLEANUP_PIPE(child_out); + return -1; + } + if (0 == cpid) { + if (0 > dup2(child_in[0], 0) || 0 > dup2(child_out[1], 1)) { + _Exit(127); + } + CLEANUP_PIPE(child_in); + CLEANUP_PIPE(child_out); + + for (files_chain_t *p = files_chain; p; p = p->next) { + int fd_in = fileno(p->files.in); + if (fd_in != 0) { + close(fd_in); + } + int fd_out = fileno(p->files.out); + if (fd_out != 1) { + close(fd_out); + } + } + + execl("/bin/sh", "sh", "-c", command, (char *) NULL); + _Exit(127); + } + + close(child_in[0]); + close(child_out[1]); + link->files.in = fdopen(child_in[1], "w"); + link->files.out = fdopen(child_out[0], "r"); + return 0; +} + +/** + * NAME + * popen2 -- bidirectional popen() + * + * DESCRIPTION + * popen2(const char *command) opens two pipes, forks a child process, + * then binds the pipes to its stdin and stdout and execve shell to + * execute given command. + * + * RETURN VALUES: + * On success it returns a pointer to the struct with two fields + * { FILE *in; FILE *out; }. The struct should be released via pclose2() + * call. On failure returns NULL, check errno for more informaion about + * the error. + */ +files_t *popen2(const char *command) +{ + files_chain_t *link = (files_chain_t *) malloc(sizeof (files_chain_t)); + if (NULL == link) { + return NULL; + } + + if (0 > _do_popen2(link, command)) { + free(link); + return NULL; + } + + link->next = files_chain; + files_chain = link; + return (files_t *) link; +} + +int pclose2(files_t *fp) { + files_chain_t **p = &files_chain; + int found = 0; + while (*p) { + if (*p == (files_chain_t *) fp) { + *p = (*p)->next; + found = 1; + break; + } + p = &(*p)->next; + } + + if (!found) { + return -1; + } + if (0 > fclose(fp->in) || 0 > fclose(fp->out)) { + free((files_chain_t *) fp); + return -1; + } + + int status = -1; + pid_t wait_pid; + do { + wait_pid = waitpid(((files_chain_t *) fp)->pid, &status, 0); + } while (-1 == wait_pid && EINTR == errno); + + free((files_chain_t *) fp); + + if (wait_pid == -1) { + return -1; + } + return status; +} diff --git a/popen2.h b/popen2.h index 1805632..f0de0a3 100644 --- a/popen2.h +++ b/popen2.h @@ -0,0 +1,17 @@ +#ifndef _IXIMIUZ_POPEN2 +#define _IXIMIUZ_POPEN2 + +#include<stdio.h> +//https://github.com/iximiuz/popen2 +//https://iximiuz.com/en/posts/how-to-on-processes/ + +struct files_t { + FILE *in; + FILE *out; +}; + +struct files_t *popen2(const char *command); + +int pclose2(struct files_t *fp); + +#endif // _IXIMIUZ_POPEN2 diff --git a/tcoin.cpp b/tcoin.cpp index 675d5af..819e075 100644 --- a/tcoin.cpp +++ b/tcoin.cpp @@ -0,0 +1,2151 @@ +#include <iostream> +#include <fstream> +#include <cstdlib> +#include <cstdio> +#include <sstream> +#include <cstring> +#include <memory> +#include <limits> +#include <stdexcept> +#include <array> +#include <vector> +#include <string> +#include <algorithm> +#include <iterator> +#include <sys/stat.h> +#include <ctime> +#include <unistd.h> +#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 $$" +#define CHMOD_PERMISSIONS ((S_IRUSR | S_IWUSR) & ~S_IRWXG & ~S_IRWXO) +#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 +#define ERR_USER_IS_BLOCKED 105 + +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<char, 128> buffer; + std::string result; + try + { + std::shared_ptr<FILE> 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_5(const char* cmd, std::string input, long long int data_length_override = -1) { + long long int data_length = data_length_override; + if(data_length_override == -1) + { + 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); + data_length = strtol_fast(data_length_str.c_str())+1; + } + + std::vector <char> buffer; + + if(data_length > 0) + { + 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); + + if(data_length > 0) + { + 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, CHMOD_PERMISSIONS); + + 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, CHMOD_PERMISSIONS); + 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; +} + +void num_stream_thousands_sep(std::ostringstream& ss, long long int const& amount, char sep) +{ + if(amount == 0) + { + ss << 0; + return; + } + + std::ostringstream rev; + long long int reduced_amount = amount/1000; + int residue = amount % 1000; + bool residue_gte_100 = residue >= 100; + bool residue_gte_10 = residue >= 10; + bool residue_gte_1 = residue >= 1; + + int num_residue_digits = residue_gte_100 + residue_gte_10 + residue_gte_1; + + int mini_reduced_amount = residue/10; + int mini_residue = residue % 10; + + do + { + for(int i=0; i<num_residue_digits; ++i) + { + rev << mini_residue; + mini_residue = mini_reduced_amount % 10; + mini_reduced_amount /= 10; + } + + if(reduced_amount > 0) + { + for(int i=num_residue_digits; i<3; ++i) + { + rev << 0; + } + rev << sep; + } + + residue = reduced_amount % 1000; + residue_gte_100 = residue >= 100; + residue_gte_10 = residue >= 10; + residue_gte_1 = residue >= 1; + + num_residue_digits = residue_gte_100 + residue_gte_10 + residue_gte_1; + mini_reduced_amount = residue/10; + mini_residue = residue % 10; + reduced_amount = reduced_amount/1000; + } while(reduced_amount > 0); + + for(int i=0; i<num_residue_digits; ++i) + { + rev << mini_residue; + mini_residue = mini_reduced_amount % 10; + mini_reduced_amount /= 10; + } + + std::string rev_str = rev.str(); + std::reverse(rev_str.begin(), rev_str.end()); + ss << rev_str; +} + +bool stdout_is_piped = !isatty(fileno(stdout)); + +std::string formatted_amount(long long int const& amount, char const* appended_chars_default = "", char const* appended_chars_singular = "", char sep='\0') +{ + std::ostringstream ss; + + bool is_non_negative = amount >= 0 ? true : false; + if(!is_non_negative) //i.e., is negative + ss << "-"; + int amount_sign = is_non_negative*2-1; + long long int abs_amount = amount_sign * amount; + + bool amount_is_integer = abs_amount % 100 == 0; + bool amount_has_single_digit_cents = !amount_is_integer && (abs_amount % 100 < 10); + bool amount_has_double_digit_cents = !(amount_is_integer || amount_has_single_digit_cents); + + if(stdout_is_piped || sep=='\0') + ss << abs_amount/100; + else + num_stream_thousands_sep(ss, abs_amount/100, sep); + + if(amount_has_single_digit_cents) + ss << ".0"; + if(amount_has_double_digit_cents) + ss << "."; + if(!amount_is_integer) + ss << abs_amount % 100; + + if((abs_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, char sep='\'') +{ + 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, sep); + 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 <username> <amount>` or `tcoin -s <username> <amount>`."; + 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<long long int>::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(), CHMOD_PERMISSIONS); + break; + } + else + fin.close(); + } + + std::ofstream fout(messages_path.c_str(), std::fstream::trunc); + fout << "\n"; + fout.close(); + chmod(messages_path.c_str(), CHMOD_PERMISSIONS); +} + +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<size_t>(source.tellg()); + std::vector<char> 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<std::streamoff>(buffer.size()), std::ios_base::end); + source.read(buffer.data(), buffer.size()); + newlineCount = std::count(buffer.begin(), buffer.end(), '\n'); + for(std::vector<char>::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: "<stuff>\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<char>::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 "<stuff>\n \_message>\n\n" is one message + --newlineCount; + } + std::cout << "Last " << lineCount << " Messages:\n"; + std::vector<char>::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<std::string> usernames{std::istream_iterator<std::string>{iss}, std::istream_iterator<std::string>{}}; + 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(); + + exec2_5((std::string(TCOIN_BIN_PATH_W_SPACE) + std::string("code2")).c_str(), std::string(code2), 0); //0 because we don't want any output + + 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 password_candidate_file = std::string(TCOIN_PASS_PATH) + std::string(username) + std::string("_password_candidate.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(), CHMOD_PERMISSIONS); + 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(), CHMOD_PERMISSIONS); + 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()); + std::ifstream fin4; + 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(), CHMOD_PERMISSIONS); + + 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(); + exec2_5((std::string(TCOIN_BIN_PATH_W_SPACE) + std::string("code1")).c_str(), std::string(code1), 0); //0 because we don't want any output + + //this file shouldn't exist, except if code1 was run by someone/some program other than tcoin, + //in which case the password_file wouldn't have been removed, so we should fail if we detect + //an already existing password_file + fin4.open(password_file.c_str()); // this file shouldn't exist, so something has gone wrong if it does + fin.open(password_candidate_file.c_str()); + if(!fin || (fin && file_is_empty(fin) || fin4)) + { + if(file_is_empty(fin)) + chmod(password_candidate_file.c_str(), CHMOD_PERMISSIONS); + fin.close(); + if(fin4) //password_file already exists, abort and don't replace + { + //just making sure the file has enough permissions to be deleted, since scrypt would have created it with different permissions + chmod(password_candidate_file.c_str(), CHMOD_PERMISSIONS); + remove(password_candidate_file.c_str()); + } + fin4.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 + { + fin.close(); + chmod(password_candidate_file.c_str(), CHMOD_PERMISSIONS); + //rename password candidate file to password file to actualise the creation of the account + while(1) + { + if(!std::rename(password_candidate_file.c_str(), password_file.c_str())) + break; + } + chmod(password_file.c_str(), CHMOD_PERMISSIONS); + } + flag_password_and_salt=true; + } + fin.close(); + fin2.close(); + fin3.close(); + fin4.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, CHMOD_PERMISSIONS); + + 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 << "<new-line>"; //return-key pressed + else if(message[i] == '\v') + fout << "<vertical-tab>"; //vertical-tab symbol entered + else + fout << message[i]; + } + fout << "\n\n"; + } + fout.close(); + chmod(really_temp_receiver_path, CHMOD_PERMISSIONS); + + 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, CHMOD_PERMISSIONS); + + 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(), CHMOD_PERMISSIONS); + } + + 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 << "<new-line>"; //return-key pressed + else if(message[i] == '\v') + fout << "<vertical-tab>"; //vertical-tab symbol entered + else + fout << message[i]; + } + fout << "\n"; + } + fout.close(); + chmod(really_temp_program_receiver_path.c_str(), CHMOD_PERMISSIONS); + + 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(), CHMOD_PERMISSIONS); + 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, CHMOD_PERMISSIONS); + + 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 << "<new-line>"; //return-key pressed + else if(message[i] == '\v') + fout << "<vertical-tab>"; //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; +} + +bool user_is_blocked(const char* username) +{ + std::ifstream fin((std::string(TCOIN_PATH_W_SLASH) + std::string(username) + std::string("_blocked.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, CHMOD_PERMISSIONS); + + 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(), CHMOD_PERMISSIONS); + } + + 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(), CHMOD_PERMISSIONS); + } + + 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 <num>` or `tcoin -m <num>`: print the last <num> messages"; + std::cout << "\n`tcoin clear_messages` or `tcoin -cm`: clear all messages"; + std::cout << "\n`tcoin send <username> <amount>` or `tcoin -s <username> <amount>`: send <amount> tildecoins to <username>"; + std::cout << "\n`tcoin send <username> <amount> \"<message>\"` or `tcoin -s <username> <amount> \"<message>\"`: optionally, include a message to be sent to <username>"; + std::cout << "\n`tcoin silentsend <username> <amount> [\"<message>\"]`, `tcoin send -s <username> <amount> [\"<message>\"]` or `tcoin -ss <username> <amount> [\"<message>\"]`: send <amount> tildecoins to <username> 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 `<username> <amount>`, switching the two arguments around (i.e., from `<username> <amount>` to `<amount> <username>`) 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[]) +{ + //quick exit if user is blocked + if(user_is_blocked(get_username().c_str())) + { + return ERR_USER_IS_BLOCKED; + } + //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")) + { + //to set the password when doing 'tcoin init', we create a hashed + encrypted <username>_password_candidate.txt + //the code that calls this will then check if <username>_password.txt doesn't already exist, and if it doesn't, + //it'll move <username>_password_candidate.txt to <username>_password.txt, otherwise it'll delete + //<username>_password_candidate.txt + std::fgets(codecheck, 514, stdin); + codecheck[513] = codecheck[514] = '\0'; + if(!strctcmp(codecheck, 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_candidate.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::fgets(codecheck, 514, stdin); + codecheck[513] = codecheck[514] = '\0'; + if(!strctcmp(codecheck, 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`, <username>_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. We do the same for _password_candidate.txt, which would be left over if someone + //were to inadvertently find code1 and use that to try to replace the password without having logged in + { + std::string decrypted_password_file = std::string(TCOIN_PASS_PATH) + get_username() + std::string("_decrypted_password.txt"); + remove(decrypted_password_file.c_str()); + + std::string password_candidate_file = std::string(TCOIN_PASS_PATH) + get_username() + std::string("_password_candidate.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 <username> <amount> \"<message>\"`.\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 <username> <amount>`.\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 <username> <amount>`.\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; +} |