selfpaste-win.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. /**
  2. * This program is free software: you can redistribute it and/or modify
  3. * it under the terms of the COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
  4. * You should have received a copy of the
  5. * COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
  6. * along with this program. If not, see http://www.sun.com/cddl/cddl.html
  7. *
  8. * 2019 - 2020 https://://www.bananas-playground.net/projekt/selfpaste
  9. */
  10. /**
  11. * !WARNING!
  12. * This is a very simple, with limited experience written, windows C program.
  13. * Use at own risk and feel free to improve.
  14. *
  15. * for requirements and how to build it, read the README
  16. */
  17. #include <stdio.h>
  18. #include <stdlib.h>
  19. #include <string.h>
  20. #include <direct.h>
  21. #include <dirent.h>
  22. #include <errno.h>
  23. /* https://www.argtable.org */
  24. #include <argtable3.h>
  25. /* https://github.com/curl/curl-for-win curl+openssl */
  26. #include <curl/curl.h>
  27. /* https://github.com/DaveGamble/cJSON */
  28. #include <cJSON.h>
  29. /**
  30. * global arg_xxx structs
  31. * https://www.argtable.org/
  32. */
  33. struct arg_lit *verbose, *quiet, *help, *createConfigFile;
  34. struct arg_file *fileToPaste;
  35. struct arg_end *end;
  36. const char *program_version = "1.1";
  37. const char *program_bug_address = "https://://www.bananas-playground.net/projekt/selfpaste";
  38. struct cmdArguments {
  39. int quiet, verbose, create_config_file;
  40. char *file_to_paste;
  41. };
  42. /**
  43. * Simple random string generation
  44. */
  45. const char availableChars[] = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
  46. int intN(int n) { return rand() % n; }
  47. char *randomString(int len) {
  48. char *rstr = malloc((len + 1) * sizeof(char));
  49. int i;
  50. for (i = 0; i < len; i++) {
  51. rstr[i] = availableChars[intN(strlen(availableChars))];
  52. }
  53. rstr[len] = '\0';
  54. return rstr;
  55. }
  56. /**
  57. * struct to hold the config options loaded from config file
  58. * Extend if the options file changes.
  59. */
  60. struct configOptions {
  61. char *secret;
  62. char *endpoint;
  63. };
  64. /**
  65. * struct to hold the returned data from the http post call
  66. * done with curl
  67. * see: https://curl.haxx.se/libcurl/c/getinmemory.html
  68. */
  69. struct MemoryStruct {
  70. char *memory;
  71. size_t size;
  72. };
  73. /**
  74. * callback function from the curl call
  75. * see: https://curl.haxx.se/libcurl/c/getinmemory.html
  76. */
  77. static size_t
  78. WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
  79. struct MemoryStruct *mem = (struct MemoryStruct *)userp;
  80. size_t realsize = size * nmemb;
  81. char *ptr = realloc(mem->memory, mem->size + realsize + 1);
  82. if(ptr == NULL) {
  83. /* out of memory! */
  84. printf("not enough memory (realloc returned NULL)\n");
  85. return 0;
  86. }
  87. mem->memory = ptr;
  88. memcpy(&(mem->memory[mem->size]), contents, realsize);
  89. mem->size += realsize;
  90. mem->memory[mem->size] = 0;
  91. return realsize;
  92. }
  93. /**
  94. * make a post curl call to upload the given file
  95. * and receive the URL as a answer
  96. * see: https://curl.haxx.se/libcurl/c/getinmemory.html
  97. */
  98. int uploadCall(struct configOptions cfgo, struct cmdArguments arguments) {
  99. CURL *curl_handle;
  100. CURLcode res;
  101. struct MemoryStruct chunk;
  102. chunk.memory = malloc(1); /* will be grown as needed by the realloc above */
  103. chunk.size = 0; /* no data at this point */
  104. res = curl_global_init(CURL_GLOBAL_ALL);
  105. if(res != CURLE_OK) {
  106. printf("ERROR: curl_global_init() failed: %s\n", curl_easy_strerror(res));
  107. return 1;
  108. }
  109. /* init the curl session */
  110. curl_handle = curl_easy_init();
  111. /* specify URL to get */
  112. curl_easy_setopt(curl_handle, CURLOPT_URL, cfgo.endpoint);
  113. /* send all data to this function */
  114. curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
  115. /* we pass our 'chunk' struct to the callback function */
  116. curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
  117. /* some servers don't like requests that are made without a user-agent */
  118. /* field, so we provide one */
  119. curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "selfpaseCurlAgent/1.0");
  120. /* add the POST data */
  121. /* https://curl.haxx.se/libcurl/c/postit2.html */
  122. curl_mime *form = NULL;
  123. curl_mimepart *field = NULL;
  124. form = curl_mime_init(curl_handle);
  125. field = curl_mime_addpart(form);
  126. curl_mime_name(field, "pasty");
  127. curl_mime_filedata(field, arguments.file_to_paste);
  128. field = curl_mime_addpart(form);
  129. curl_mime_name(field, "dl");
  130. curl_mime_data(field, cfgo.secret, CURL_ZERO_TERMINATED);
  131. curl_easy_setopt(curl_handle, CURLOPT_MIMEPOST, form);
  132. /* execute it! */
  133. res = curl_easy_perform(curl_handle);
  134. /* check for errors */
  135. if(res != CURLE_OK || chunk.size < 1) {
  136. printf("ERROR: curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
  137. exit(1);
  138. }
  139. if (chunk.memory != NULL) {
  140. if(arguments.verbose) printf("%lu bytes retrieved\n", (unsigned long)chunk.size);
  141. if(arguments.verbose) printf("CURL returned:\n%s\n", chunk.memory);
  142. /* https://spacesciencesoftware.wordpress.com/2013/09/10/a-good-way-to-read-json-with-c/ */
  143. cJSON *json_root = cJSON_Parse(chunk.memory);
  144. if (json_root != NULL) {
  145. cJSON *json_result_status = cJSON_GetObjectItem(json_root, "status");
  146. if (json_result_status != NULL) {
  147. printf("Status: %s\n", json_result_status->valuestring);
  148. } else {
  149. printf("ERROR: Invalid payload returned. Missing 'status'\n%s\n", chunk.memory);
  150. }
  151. cJSON *json_result_message = cJSON_GetObjectItem(json_root, "message");
  152. if (json_result_message != NULL) {
  153. printf("Message: %s\n", json_result_message->valuestring);
  154. } else {
  155. printf("ERROR: Invalid payload returned. Missing 'message'\n%s\n", chunk.memory);
  156. }
  157. }
  158. }
  159. /* cleanup curl stuff */
  160. curl_easy_cleanup(curl_handle);
  161. curl_mime_free(form);
  162. free(chunk.memory);
  163. curl_global_cleanup();
  164. return 0;
  165. }
  166. /**
  167. * the main part starts here
  168. */
  169. int main(int argc, char *argv[]) {
  170. srand(time(NULL));
  171. /**
  172. * command line argument default values
  173. */
  174. struct cmdArguments arguments;
  175. arguments.quiet = 0;
  176. arguments.verbose = 0;
  177. arguments.create_config_file = 0;
  178. arguments.file_to_paste = "-";
  179. /**
  180. * https://www.argtable.org/
  181. */
  182. void *argtable[] = {
  183. help = arg_litn(NULL, "help", 0, 1, "Display this help and exit"),
  184. quiet = arg_litn("q", "quiet", 0, 1, "Don't produce any output"),
  185. verbose = arg_litn("v", "verbose", 0, 1, "Verbose output"),
  186. createConfigFile = arg_litn("c", "create-config-file", 0, 1, "Create default config file"),
  187. fileToPaste = arg_filen(NULL, NULL, "<file>", 0, 1, "File to paste"),
  188. end = arg_end(20),
  189. };
  190. /* argtable parsing */
  191. int nerrors;
  192. nerrors = arg_parse(argc,argv,argtable);
  193. /* special case: '--help' takes precedence over error reporting */
  194. if (help->count > 0) {
  195. printf("Usage: selfpaste.exe");
  196. arg_print_syntax(stdout, argtable, "\n");
  197. arg_print_glossary(stdout, argtable, " %-25s %s\n");
  198. arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
  199. return(1);
  200. }
  201. /* If the parser returned any errors then display them and exit */
  202. if (nerrors > 0) {
  203. /* Display the error details contained in the arg_end struct.*/
  204. arg_print_errors(stdout, end, "selfpaste.exe");
  205. printf("Try '%s --help' for more information.\n", "selfpaste.exe");
  206. arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
  207. return(1);
  208. }
  209. else {
  210. arguments.quiet = quiet->count;
  211. arguments.verbose = verbose->count;
  212. arguments.create_config_file = createConfigFile->count;
  213. arguments.file_to_paste = fileToPaste->filename[0];
  214. }
  215. if(arguments.verbose) {
  216. printf ("File = %s\n"
  217. "Verbose = %s\n"
  218. "Quiet = %s\n"
  219. "Create config file = %s\n",
  220. arguments.file_to_paste,
  221. arguments.verbose ? "yes" : "no",
  222. arguments.quiet ? "yes" : "no",
  223. arguments.create_config_file ? "yes" : "no"
  224. );
  225. }
  226. /**
  227. * Config file check.
  228. * Also create if not is available and command line option
  229. * to create it is set.
  230. */
  231. char* homedir = getenv("USERPROFILE");
  232. if(homedir == NULL) {
  233. printf("ERROR: USERPROFILE directory not found?\n");
  234. return(1);
  235. }
  236. if(arguments.verbose) printf("Homedir: '%s'\n", homedir);
  237. char selfpasteSaveDir[12] = "\\selfpaste";
  238. char storagePath[strlen(homedir) + strlen(selfpasteSaveDir)];
  239. strcpy(storagePath, homedir);
  240. strcat(storagePath, selfpasteSaveDir);
  241. DIR* checkStoragePathDir = opendir(storagePath);
  242. if(checkStoragePathDir) {
  243. if(arguments.verbose) printf("Storage directory exists: '%s'\n", storagePath);
  244. closedir(checkStoragePathDir);
  245. } else if (ENOENT == errno) {
  246. if(_mkdir(storagePath) == 0) {
  247. if(arguments.verbose) printf("Storage directory created: '%s'\n", storagePath);
  248. }
  249. else {
  250. printf("ERROR: Storage directory '%s' could not created\n", storagePath);
  251. return(1);
  252. }
  253. }
  254. else {
  255. printf("ERROR: Storage directory '%s' could not validated.\n", storagePath);
  256. return(1);
  257. }
  258. char configFileName[16] = "\\selfpaste.cfg";
  259. char configFilePath[strlen(storagePath) + strlen(configFileName)];
  260. strcpy(configFilePath, storagePath);
  261. strcat(configFilePath, configFileName);
  262. if(arguments.verbose) printf("Configfilepath: '%s'\n", configFilePath);
  263. if(access(configFilePath, F_OK) != -1) {
  264. if(arguments.verbose) printf("Using configfile: '%s'\n", configFilePath);
  265. if(arguments.create_config_file == 1) {
  266. printf("INFO: Re creating configfile by manually deleting it.\n");
  267. return(1);
  268. }
  269. } else {
  270. printf("ERROR: Configfile '%s' not found.\n",configFilePath);
  271. if(arguments.create_config_file == 1) {
  272. printf("Creating configfile: '%s'\n", configFilePath);
  273. FILE *fp = fopen(configFilePath, "w");
  274. if (fp) {
  275. fputs("# selfpaste config file.\n", fp);
  276. fprintf(fp, "# See %s for more details.\n", program_bug_address);
  277. fprintf(fp, "# Version: %s\n", program_version);
  278. fprintf(fp, "SELFPASTE_UPLOAD_SECRET=%s\n", randomString(50));
  279. fputs("ENDPOINT=http://you-seflpaste-endpoi.nt\n", fp);
  280. fclose(fp);
  281. printf("Config file '%s' created.\nPlease update your settings!\n", configFilePath);
  282. return(0);
  283. }
  284. else {
  285. printf("ERROR: Configfile '%s' could not be written.\n",configFilePath);
  286. }
  287. }
  288. return(1);
  289. }
  290. /**
  291. * Reading and parsing the config file.
  292. * populate configOptions struct
  293. *
  294. * https://rosettacode.org/wiki/Read_a_configuration_file#C
  295. * https://github.com/welljsjs/Config-Parser-C
  296. * https://hyperrealm.github.io/libconfig/
  297. * https://www.gnu.org/software/libc/manual/html_node/Finding-Tokens-in-a-String.html
  298. */
  299. struct configOptions configOptions;
  300. FILE* fp;
  301. if ((fp = fopen(configFilePath, "r")) == NULL) {
  302. printf("ERROR: Configfile '%s' could not be opened.\n",configFilePath);
  303. exit(1);
  304. }
  305. if(arguments.verbose) printf("Reading configfile: '%s'\n", configFilePath);
  306. char line[128];
  307. char *optKey,*optValue, *workwith;
  308. while (fgets(line, sizeof line, fp) != NULL ) {
  309. if(arguments.verbose) printf("- Line: %s", line);
  310. if (line[0] == '#') continue;
  311. /* important. strok modifies the string it works with */
  312. workwith = strdup(line);
  313. optKey = strtok(workwith, "=");
  314. if(arguments.verbose) printf("Option: %s\n", optKey);
  315. optValue = strtok(NULL, "\n\r");
  316. if(arguments.verbose) printf("Value: %s\n", optValue);
  317. if(strcmp("ENDPOINT",optKey) == 0) {
  318. configOptions.endpoint = optValue;
  319. }
  320. if(strcmp("SELFPASTE_UPLOAD_SECRET",optKey) == 0) {
  321. configOptions.secret = optValue;
  322. }
  323. }
  324. fclose(fp);
  325. if(arguments.verbose) {
  326. printf("Using\n- Secret: %s\n- Endpoint: %s\n- File: %s\n",
  327. configOptions.secret,configOptions.endpoint,
  328. arguments.file_to_paste);
  329. }
  330. /* do the upload */
  331. uploadCall(configOptions, arguments);
  332. return(0);
  333. }