selfpaste.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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, c program.
  13. * Use at own risk and feel free to improve
  14. */
  15. #include <stdio.h>
  16. #include <string.h>
  17. #include <stdlib.h>
  18. #include <argp.h>
  19. #include <unistd.h>
  20. #include <pwd.h>
  21. #include <time.h>
  22. // https://curl.haxx.se
  23. #include <curl/curl.h>
  24. // https://github.com/json-c/json-c
  25. #include <json-c/json.h>
  26. /*
  27. * Commandline arguments
  28. * see: https://www.gnu.org/software/libc/manual/html_node/Argp-Example-3.html#Argp-Example-3
  29. */
  30. const char *argp_program_version = "1.0";
  31. const char *argp_program_bug_address = "https://://www.bananas-playground.net/projekt/selfpaste";
  32. static char doc[] = "selfpaste. Upload given file to your selfpaste installation.";
  33. static char args_doc[] = "file";
  34. // The options we understand.
  35. static struct argp_option options[] = {
  36. {"verbose",'v', 0, 0, "Produce verbose output" },
  37. {"quiet", 'q', 0, 0, "Don't produce any output" },
  38. {"output", 'o', "FILE", 0, "Output to FILE instead of standard output" },
  39. {"create-config-file", 'c', 0, 0, "Create default config file" },
  40. { 0 }
  41. };
  42. struct cmdArguments {
  43. char *args[1];
  44. int quiet, verbose, create_config_file;
  45. char *output_file;
  46. };
  47. // Parse a single option.
  48. static error_t
  49. parse_opt (int key, char *arg, struct argp_state *state) {
  50. struct cmdArguments *arguments = state->input;
  51. switch (key) {
  52. case 'q':
  53. arguments->quiet = 1;
  54. break;
  55. case 'v':
  56. arguments->verbose = 1;
  57. break;
  58. case 'o':
  59. arguments->output_file = arg;
  60. break;
  61. case 'c':
  62. arguments->create_config_file = 1;
  63. break;
  64. case ARGP_KEY_ARG:
  65. if (state->arg_num >= 1)
  66. // Too many arguments.
  67. argp_usage (state);
  68. arguments->args[state->arg_num] = arg;
  69. break;
  70. case ARGP_KEY_END:
  71. if (state->arg_num < 1 && arguments->create_config_file == 0)
  72. // Not enough arguments.
  73. argp_usage (state);
  74. break;
  75. default:
  76. return ARGP_ERR_UNKNOWN;
  77. }
  78. return 0;
  79. }
  80. static struct argp argp = { options, parse_opt, args_doc, doc };
  81. /*
  82. * Simple random string generation
  83. */
  84. const char availableChars[] = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
  85. int intN(int n) { return rand() % n; }
  86. char *randomString(int len) {
  87. char *rstr = malloc((len + 1) * sizeof(char));
  88. int i;
  89. for (i = 0; i < len; i++) {
  90. rstr[i] = availableChars[intN(strlen(availableChars))];
  91. }
  92. rstr[len] = '\0';
  93. return rstr;
  94. }
  95. /*
  96. * struct to hold the config options loaded from config file
  97. * Extend if the options file changes.
  98. */
  99. struct configOptions {
  100. char *secret;
  101. char *endpoint;
  102. };
  103. /*
  104. * struct to hold the returned data from the http post call
  105. * done with curl
  106. * see: https://curl.haxx.se/libcurl/c/getinmemory.html
  107. */
  108. struct MemoryStruct {
  109. char *memory;
  110. size_t size;
  111. };
  112. /*
  113. * callback function from the curl call
  114. * see: https://curl.haxx.se/libcurl/c/getinmemory.html
  115. */
  116. static size_t
  117. WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
  118. struct MemoryStruct *mem = (struct MemoryStruct *)userp;
  119. size_t realsize = size * nmemb;
  120. char *ptr = realloc(mem->memory, mem->size + realsize + 1);
  121. if(ptr == NULL) {
  122. /* out of memory! */
  123. printf("not enough memory (realloc returned NULL)\n");
  124. return 0;
  125. }
  126. mem->memory = ptr;
  127. memcpy(&(mem->memory[mem->size]), contents, realsize);
  128. mem->size += realsize;
  129. mem->memory[mem->size] = 0;
  130. return realsize;
  131. }
  132. /*
  133. * make a post curl call to upload the given file
  134. * and receive the URL as a answer
  135. * see: https://curl.haxx.se/libcurl/c/getinmemory.html
  136. */
  137. int uploadCall(struct configOptions cfgo, struct cmdArguments arguments) {
  138. CURL *curl_handle;
  139. CURLcode res;
  140. struct MemoryStruct chunk;
  141. chunk.memory = malloc(1); // will be grown as needed by the realloc above
  142. chunk.size = 0; // no data at this point
  143. curl_global_init(CURL_GLOBAL_ALL);
  144. // init the curl session
  145. curl_handle = curl_easy_init();
  146. // specify URL to get
  147. curl_easy_setopt(curl_handle, CURLOPT_URL, cfgo.endpoint);
  148. // send all data to this function
  149. curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
  150. // we pass our 'chunk' struct to the callback function
  151. curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
  152. // some servers don't like requests that are made without a user-agent
  153. // field, so we provide one
  154. curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "selfpaseCurlAgent/1.0");
  155. // add the POST data
  156. // // https://curl.haxx.se/libcurl/c/postit2.html
  157. curl_mime *form = NULL;
  158. curl_mimepart *field = NULL;
  159. form = curl_mime_init(curl_handle);
  160. field = curl_mime_addpart(form);
  161. curl_mime_name(field, "pasty");
  162. curl_mime_filedata(field, arguments.args[0]);
  163. field = curl_mime_addpart(form);
  164. curl_mime_name(field, "dl");
  165. curl_mime_data(field, cfgo.secret, CURL_ZERO_TERMINATED);
  166. curl_easy_setopt(curl_handle, CURLOPT_MIMEPOST, form);
  167. // execute it!
  168. res = curl_easy_perform(curl_handle);
  169. // check for errors
  170. if(res != CURLE_OK || chunk.size < 1) {
  171. printf("ERROR: curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
  172. exit(1);
  173. }
  174. json_object *json, *jsonWork;
  175. enum json_tokener_error jerr = json_tokener_success;
  176. if (chunk.memory != NULL) {
  177. if(arguments.verbose) printf("%lu bytes retrieved\n", (unsigned long)chunk.size);
  178. if(arguments.verbose) printf("CURL returned:\n%s\n", chunk.memory);
  179. // https://gist.github.com/leprechau/e6b8fef41a153218e1f4
  180. json = json_tokener_parse_verbose(chunk.memory, &jerr);
  181. if (jerr == json_tokener_success) {
  182. jsonWork = json_object_object_get(json, "status");
  183. printf("Status: %s\n", json_object_get_string(jsonWork));
  184. jsonWork = json_object_object_get(json, "message");
  185. printf("selfpastelink: %s\n", json_object_get_string(jsonWork));
  186. }
  187. else {
  188. printf("ERROR: Invalid payload returned. Check your config:\n%s\n", chunk.memory);
  189. }
  190. }
  191. // cleanup curl stuff
  192. curl_easy_cleanup(curl_handle);
  193. curl_mime_free(form);
  194. free(chunk.memory);
  195. curl_global_cleanup();
  196. return 0;
  197. }
  198. /*
  199. * main routine
  200. */
  201. int main(int argc, char *argv[]) {
  202. srand(time(NULL));
  203. /*
  204. * command line argument parsing and default values
  205. */
  206. struct cmdArguments arguments;
  207. arguments.quiet = 0;
  208. arguments.verbose = 0;
  209. arguments.output_file = "-";
  210. arguments.create_config_file = 0;
  211. argp_parse (&argp, argc, argv, 0, 0, &arguments);
  212. if(arguments.verbose) {
  213. printf ("File = %s\nOutputfile = %s\n"
  214. "Verbose = %s\nQuiet = %s\n",
  215. arguments.args[0],
  216. arguments.output_file,
  217. arguments.verbose ? "yes" : "no",
  218. arguments.quiet ? "yes" : "no",
  219. arguments.create_config_file ? "yes" : "no"
  220. );
  221. }
  222. /*
  223. * Config file check.
  224. * Also create if non is available and command line option
  225. * to create it is set.
  226. */
  227. char* homedir = getenv("HOME");
  228. if ( homedir == NULL ) {
  229. homedir = getpwuid(getuid())->pw_dir;
  230. }
  231. if(homedir[0] == '\0') {
  232. printf("ERROR: $HOME directory not found?\n");
  233. return(1);
  234. }
  235. if(arguments.verbose) printf("Homedir: %s\n", homedir);
  236. char configFileName[16] = "/.selfpaste.cfg";
  237. if(arguments.verbose) printf("Config file name: '%s'\n", configFileName);
  238. char configFilePath[strlen(homedir) + strlen(configFileName)];
  239. strcpy(configFilePath, homedir);
  240. strcat(configFilePath, configFileName);
  241. if(arguments.verbose) printf("Configfilepath: '%s'\n", configFilePath);
  242. if(access(configFilePath, F_OK) != -1) {
  243. if(arguments.verbose) printf("Using configfile: '%s'\n", configFilePath);
  244. if(arguments.create_config_file == 1) {
  245. printf("INFO: Re creating configfile by manually deleting it.\n");
  246. exit(1);
  247. }
  248. } else {
  249. printf("ERROR: Configfile '%s' not found.\n",configFilePath);
  250. if(arguments.create_config_file == 1) {
  251. printf("Creating configfile: '%s'\n", configFilePath);
  252. FILE *fp = fopen(configFilePath, "w");
  253. if (fp) {
  254. fputs("# selfpaste config file.\n", fp);
  255. fprintf(fp, "# See %s for more details.\n", argp_program_bug_address);
  256. fprintf(fp, "# Version: %s\n", argp_program_version);
  257. fprintf(fp, "SELFPASTE_UPLOAD_SECRET=%s\n", randomString(50));
  258. fputs("ENDPOINT=http://you-seflpaste-endpoi.nt\n", fp);
  259. fclose(fp);
  260. printf("Config file '%s' created.\nPlease update your settings!\n", configFilePath);
  261. exit(0);
  262. }
  263. else {
  264. printf("ERROR: Configfile '%s' could not be written.\n",configFilePath);
  265. }
  266. }
  267. exit(1);
  268. }
  269. /*
  270. * Reading and parsing the config file.
  271. * populate configOptions struct
  272. *
  273. * https://rosettacode.org/wiki/Read_a_configuration_file#C
  274. * https://github.com/welljsjs/Config-Parser-C
  275. * https://hyperrealm.github.io/libconfig/
  276. * https://www.gnu.org/software/libc/manual/html_node/Finding-Tokens-in-a-String.html
  277. */
  278. struct configOptions configOptions;
  279. FILE* fp;
  280. if ((fp = fopen(configFilePath, "r")) == NULL) {
  281. printf("ERROR: Configfile '%s' could not be opened.\n",configFilePath);
  282. exit(1);
  283. }
  284. if(arguments.verbose) printf("Reading configfile: '%s'\n", configFilePath);
  285. char line[128];
  286. char *optKey,*optValue, *workwith;
  287. while (fgets(line, sizeof line, fp) != NULL ) {
  288. if(arguments.verbose) printf("- Line: %s", line);
  289. if (line[0] == '#') continue;
  290. // important. strok modifies the string it works with
  291. workwith = strdup(line);
  292. optKey = strtok(workwith, "=");
  293. if(arguments.verbose) printf("Option: %s\n", optKey);
  294. optValue = strtok(NULL, "\n\r");
  295. if(arguments.verbose) printf("Value: %s\n", optValue);
  296. if(strcmp("ENDPOINT",optKey) == 0) {
  297. configOptions.endpoint = optValue;
  298. }
  299. if(strcmp("SELFPASTE_UPLOAD_SECRET",optKey) == 0) {
  300. configOptions.secret = optValue;
  301. }
  302. }
  303. fclose(fp);
  304. if(arguments.verbose) {
  305. printf("Using\n- Secret: %s\n- Endpoint: %s\n- File: %s\n",
  306. configOptions.secret,configOptions.endpoint,
  307. arguments.args[0]);
  308. }
  309. // do the upload
  310. uploadCall(configOptions, arguments);
  311. return(0);
  312. }