diff --git a/README.md b/README.md new file mode 100644 index 0000000..84cbce8 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +PBinCLI-c++ +===== + +WIP +----- diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..a031a90 --- /dev/null +++ b/main.cpp @@ -0,0 +1,530 @@ +#include +#include + +#include +#include +#include + +#include +#include + +#include "main.h" + +CURL *curl; + +bool opt_debug = false; +bool opt_notext = false; +bool opt_nocertcheck = false; + +const char *opt_server = "https://paste.i2pd.xyz/"; +char *opt_proxy; +long opt_proxy_type; +static json_t *opt_config; + +struct upload_buffer { + const void *buf; + size_t len; + size_t pos; +}; + +enum paste_command { + PASTE_SEND = 1, + PASTE_GET, + PASTE_DELETE +}; + +int paste_mode = 0; + +std::string paste_message; +char *paste_filepath; +char *paste_password; + +const char *paste_expire = "1day"; +bool paste_burn = false; +bool paste_discus = false; + +const char *paste_format = "plaintext"; + +char *paste_id; +char *paste_token; + +static char const usage[] = "\ +Usage: " PROGRAM_NAME " [OPTIONS] [paste_id | paste_id#paste_key]\n\ +Available commands:\n\ + send Send paste\n\ + get Receive paste\n\ + delete Delete paste\n\ +\n\ +Global options:\n\ + -s, --server=[PROTOCOL://]HOST[:PORT]/[PATH/]\n\ + Change used server address\n\ + -x, --proxy=[PROTOCOL://]HOST[:PORT]\n\ + connect through a proxy\n\ + --no-check-certificate\n\ + do not verify server certificate\n\ + -d, --debug enable debugging output\n\ + -c, --config=FILE load a JSON-format configuration file\n\ + -V, --version display version information and exit\n\ + -h, --help display this help text and exit\n\ +\n\ +'send' command options:\n\ +" PROGRAM_NAME " [-m text] [...] send\n\ + -m, --message= text to add in paste written in quotes.\n\ + If not set, text will be read from stdin\n\ + -f, --file= path to file to attach to paste\n\ + example: /home/user/document.pdf\n\ + -p --password=\n\ + password for encrypting paste\n\ + -E --expire=<5min|10min|1hour|1day|1week|1month|1year|never>\n\ + paste lifetime (default: 1day)\n\ + -B --burn burn sent paste after reading\n\ + -D --discus open discussion for sent paste\n\ + -F --format=\n\ + format of text (default: plaintext)\n\ + -q --notext disable text store for paste. If used,\n\ + --file will be required to be selected!\n\ + -C --compression=\n\ + paste compression mode (default: zlib)\n\ +\n\ +'get ' command options:\n\ +" PROGRAM_NAME " [-p password] get \n\ + -p --password=pass password for decrypting paste\n\ + paste_id#paste_key id and key pair of paste\n\ +\n\ +'delete' command options:\n\ +" PROGRAM_NAME " <-t token> delete \n\ + -t --token= password for decrypting paste\n\ + paste_id id and key pair of paste\n\ +"; + +static char const short_options[] = + "m:f:p:E:BDF:qC:t:s:x:dc:Vh"; + +static struct option options[] = { + { "message", required_argument, NULL, 'm' }, + { "file", required_argument, NULL, 'f' }, + { "password", required_argument, NULL, 'p' }, + { "expire", required_argument, NULL, 'E' }, + { "burn", no_argument, NULL, 'B' }, + { "discus", no_argument, NULL, 'D' }, + { "format", required_argument, NULL, 'F' }, + { "notext", no_argument, NULL, 'q' }, + { "compression", required_argument, NULL, 'C' }, + { "token", required_argument, NULL, 't' }, + { "paste", required_argument, NULL, 1000 }, + { "no-check-certificate", no_argument, NULL, 1001 }, + { "host", required_argument, NULL, 's' }, + { "proxy", required_argument, NULL, 'x' }, + { "debug", no_argument, NULL, 'd' }, + { "config", required_argument, NULL, 'c' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } +}; + +static void show_version_and_exit(void) +{ + printf("%s v%s\n" + "%s\n", + PACKAGE_NAME, PACKAGE_VERSION, + curl_version()); + exit(EXIT_SUCCESS); +} + +static void show_usage_and_exit(int status) +{ + if (status) + fprintf(stderr, "Try `" PROGRAM_NAME " --help' for more information.\n"); + else + printf(usage); + + exit(status); +} + +void parse_arg(int key, char *arg) +{ + switch(key) { + case 'm': + paste_message = strdup(arg); + break; + + case 'f': + free(paste_filepath); + paste_filepath = strdup(arg); + break; + + case 'p': + free(paste_password); + paste_password = strdup(arg); + break; + + case 'E': + // here provided only default values + if (!strcasecmp(arg, "5min") || !strcasecmp(arg, "10min") || !strcasecmp(arg, "1hour") || !strcasecmp(arg, "1day") || + !strcasecmp(arg, "1week") || !strcasecmp(arg, "1month") || !strcasecmp(arg, "1year") || !strcasecmp(arg, "never")) + paste_expire = strdup(arg); + break; + + case 'B': + paste_burn = true; + break; + + case 'D': + paste_discus = true; + break; + + case 'F': + // here provided only default values + if (!strcasecmp(arg, "plaintext") || !strcasecmp(arg, "syntaxhighlighting") || !strcasecmp(arg, "markdown")) + paste_format = strdup(arg); + break; + + case 'q': + opt_notext = true; + break; + + case 1000: + free(paste_id); + paste_id = strdup(arg); + break; + + case 't': + free(paste_token); + paste_token = strdup(arg); + break; + + case 1001: + opt_nocertcheck = true; + break; + + case 's': + opt_server = strdup(arg); + break; + + case 'x': + if (!strncasecmp(arg, "socks4://", 9)) + opt_proxy_type = CURLPROXY_SOCKS4; + else if (!strncasecmp(arg, "socks5://", 9)) + opt_proxy_type = CURLPROXY_SOCKS5; +#if LIBCURL_VERSION_NUM >= 0x071200 + else if (!strncasecmp(arg, "socks4a://", 10)) + opt_proxy_type = CURLPROXY_SOCKS4A; + else if (!strncasecmp(arg, "socks5h://", 10)) + opt_proxy_type = CURLPROXY_SOCKS5_HOSTNAME; +#endif + else + opt_proxy_type = CURLPROXY_HTTP; + free(opt_proxy); + opt_proxy = strdup(arg); + break; + + case 'c': + json_error_t err; + if (opt_config) { + json_decref(opt_config); + opt_config = NULL; + } + + opt_config = JSON_LOADF(arg, &err); + + if (!json_is_object(opt_config)) { + fprintf(stderr, "JSON decode of %s failed", arg); + exit(EXIT_FAILURE); + } + break; + + case 'd': + opt_debug = true; + break; + + case 'V': + show_version_and_exit(); + break; + + case 'h': + show_usage_and_exit(EXIT_SUCCESS); + break; + + default: + show_usage_and_exit(EXIT_FAILURE); + break; + } +} + +void parse_config(json_t* json_obj) +{ + int i; + json_t *val; + + if (!json_is_object(json_obj)) + return; + + for (i = 0; i < ARRAY_SIZE(options); i++) { + + if (!options[i].name) + break; + + if (!strcasecmp(options[i].name, "config")) + continue; + + val = json_object_get(json_obj, options[i].name); + if (!val) + continue; + + if (options[i].has_arg && json_is_string(val)) { + char *s = strdup(json_string_value(val)); + if (!s) + continue; + parse_arg(options[i].val, s); + free(s); + } + else if (options[i].has_arg && json_is_integer(val)) { + char buf[16]; + sprintf(buf, "%d", (int) json_integer_value(val)); + parse_arg(options[i].val, buf); + } + else if (options[i].has_arg && json_is_real(val)) { + char buf[16]; + sprintf(buf, "%f", json_real_value(val)); + parse_arg(options[i].val, buf); + } + else if (!options[i].has_arg) { + if (json_is_true(val)) + parse_arg(options[i].val, (char*) ""); + } + else + fprintf(stderr, "JSON option %s invalid", options[i].name); + } +} + +size_t all_data_cb(char *contents, size_t size, size_t nmemb, void *userp) +{ + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; +} + +static json_t *json_server_call(CURL *curl, const char *url, const char *req, int *curl_err) +{ + json_t *val, *err_val, *res_val; + int rc; + std::string all_data; + struct upload_buffer upload_data; + json_error_t err; + struct curl_slist *headers = NULL; + char *httpdata; + char len_hdr[64]; + char curl_err_str[CURL_ERROR_SIZE] = { 0 }; + + /* it is assumed that 'curl' is freshly [re]initialized at this pt */ + + curl_easy_setopt(curl, CURLOPT_URL, url); + if (opt_nocertcheck) { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + } + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 0); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, all_data_cb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &all_data); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_err_str); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + if (opt_proxy) { + curl_easy_setopt(curl, CURLOPT_PROXY, opt_proxy); + curl_easy_setopt(curl, CURLOPT_PROXYTYPE, opt_proxy_type); + } + + switch (paste_mode) { + case PASTE_SEND: + case PASTE_DELETE: + curl_easy_setopt(curl, CURLOPT_READDATA, &upload_data); + curl_easy_setopt(curl, CURLOPT_POST, 1); + upload_data.buf = req; + upload_data.len = strlen(req); + upload_data.pos = 0; + sprintf(len_hdr, "Content-Length: %lu", (unsigned long) upload_data.len); + headers = curl_slist_append(headers, "Content-Type: application/json"); + headers = curl_slist_append(headers, len_hdr); + break; + } + + headers = curl_slist_append(headers, "X-Requested-With: JSONHttpRequest"); + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + rc = curl_easy_perform(curl); + if (curl_err != NULL) + *curl_err = rc; + if (rc != CURLE_OK) { + if (rc != CURLE_OPERATION_TIMEDOUT) { + fprintf(stderr, "HTTP request failed: %s", curl_err_str); + goto err_out; + } + } + + if (!all_data.length()) { + fprintf(stderr, "Empty data received in json_rpc_call."); + goto err_out; + } + + httpdata = (char*) all_data.c_str(); + + std::cout << httpdata << std::endl; + + val = JSON_LOADS(httpdata, &err); + if (!val) { + fprintf(stderr, "JSON decode failed(%d): %s", err.line, err.text); + goto err_out; + } + + curl_slist_free_all(headers); + curl_easy_reset(curl); + return val; + +err_out: + curl_slist_free_all(headers); + curl_easy_reset(curl); + return NULL; +} + +static void parse_cmdline(int argc, char *argv[]) +{ + int key; + + while ((key = getopt_long(argc, argv, short_options, options, NULL)) != -1) { + parse_arg(key, optarg); + } + + parse_config(opt_config); +} + +static void initialize_curl() +{ + long flags; + + // cURL initialization + flags = strncmp(opt_server, "https:", 6) + ? (CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL) + : CURL_GLOBAL_ALL; + if (curl_global_init(flags)) { + fprintf(stderr, "CURL initialization failed"); + exit(EXIT_FAILURE); + } else { + curl = curl_easy_init(); + if (unlikely(!curl)) { + fprintf(stderr, "CURL initialization failed"); + exit(EXIT_FAILURE); + } + } +} + +// ### main ### + +int main (int argc, char *argv[]) +{ + json_t *resp = NULL; + int err = 0; + + parse_cmdline(argc, argv); + + if (optind >= argc) { + fprintf(stderr, "%s: command not specified! (see --help)\n", PROGRAM_NAME); + return EXIT_FAILURE; + } + + // Program mode changer + if (!strcasecmp(argv[optind], "send")) + paste_mode = PASTE_SEND; + else if (!strcasecmp(argv[optind], "get")) + paste_mode = PASTE_GET; + else if (!strcasecmp(argv[optind], "delete")) + paste_mode = PASTE_DELETE; + else { + fprintf(stderr, "%s: unknown command specified! (see --help)\n", PROGRAM_NAME); + return EXIT_FAILURE; + } + // increase options index for additional info + optind++; + + printf("===== %s =====\n", PROGRAM_NAME); + + if (paste_mode == PASTE_SEND) { + if(paste_message.length()) { + printf("Received text: %s\n", paste_message.c_str()); + } else if(!opt_notext) { + printf("No text is received, so reading it from stdin...\n"); + while (std::getline(std::cin, paste_message)){ } + printf("Received text: %s\n", paste_message.c_str()); + } + + printf("Format: %s", paste_format); + + } else if (paste_mode == PASTE_GET) { + CURLU *h; + CURLUcode uc; + + std::string pasteData; + std::string pasteID; + std::string pasteKey; + std::string pasteUrl; + + // reading paste id and key from argv + if (optind >= argc) { // if paste info is not specified + fprintf(stderr, "%s: paste information is not provided! (see --help)\n", PROGRAM_NAME); + return EXIT_FAILURE; + } else + pasteData = strdup(argv[optind]); + + // validate that received value is not full URL + h = curl_url(); /* get a handle to work with */ + if(!h) + return EXIT_FAILURE; + + /* parse a full URL */ + uc = curl_url_set(h, CURLUPART_URL, pasteData.c_str(), 0); + if(uc) // if that is not URL + { + std::cout << "That is not URL" << std::endl; + + size_t pos = pasteData.find('#'); + if (pos != std::string::npos) { + pasteID = pasteData.substr(0, pos++); + pasteKey = pasteData.substr(pos); + } else { + fprintf(stderr, "%s: received paste information is not looks like needed! (see --help)\n", PROGRAM_NAME); + return EXIT_FAILURE; + } + + pasteUrl = std::string(opt_server) + "?" + std::string(pasteID); + std::cout << "At the end we have URL \"" << pasteUrl << "\" with key " << pasteKey << std::endl; + + } else { + std::cout << "This is URL" << std::endl; + + size_t pos = pasteData.find('#'); + if (pos != std::string::npos) { + pasteUrl = pasteData.substr(0, pos++); + pasteKey = pasteData.substr(pos); + + size_t pos = pasteData.find('?'); + if (pos != std::string::npos) { + opt_server = pasteData.substr(0, pos++).c_str(); + } else { + fprintf(stderr, "%s: can't find a request option separator \"?\" in URL! (see --help)\n", PROGRAM_NAME); + return EXIT_FAILURE; + } + } else { + fprintf(stderr, "%s: can't find a \"#\" delimiter in URL! (see --help)\n", PROGRAM_NAME); + return EXIT_FAILURE; + } + } + curl_url_cleanup(h); + + initialize_curl(); + + std::cout << "Requesting data from " << pasteUrl << "...\n"; + resp = json_server_call(curl, pasteUrl.c_str(), NULL, &err); + std::cout << resp; + } else if (paste_mode == PASTE_DELETE) { + // todo + } +} \ No newline at end of file diff --git a/main.h b/main.h new file mode 100644 index 0000000..1ae2d12 --- /dev/null +++ b/main.h @@ -0,0 +1,33 @@ +#ifndef MAIN_H__ +#define MAIN_H__ + +#define PROGRAM_NAME "pbincli" +#define PACKAGE "pbincli" +#define PACKAGE_BUGREPORT "" +#define PACKAGE_NAME "pbincli" +#define PACKAGE_URL "https://github.com/r4sas/pbincli-c++" +#define PACKAGE_VERSION "0.1" + +#if JANSSON_MAJOR_VERSION >= 2 +#define JSON_LOADS(str, err_ptr) json_loads((str), 0, (err_ptr)) +#define JSON_LOADF(str, err_ptr) json_load_file((str), 0, (err_ptr)) +#else +#define JSON_LOADS(str, err_ptr) json_loads((str), (err_ptr)) +#define JSON_LOADF(str, err_ptr) json_load_file((str), (err_ptr)) +#endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif + +#undef unlikely +#undef likely +#if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__) +#define unlikely(expr) (__builtin_expect(!!(expr), 0)) +#define likely(expr) (__builtin_expect(!!(expr), 1)) +#else +#define unlikely(expr) (expr) +#define likely(expr) (expr) +#endif + +#endif \ No newline at end of file