selfpaste.c 11 KB

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