text: /*
  Fiche - Command line pastebin for sharing terminal output.
  
  -------------------------------------------------------------------------------
  
  License: MIT (http://www.opensource.org/licenses/mit-license.php)
  Repository: https://github.com/solusipse/fiche/
  Live example: http://termbin.com
  
  -------------------------------------------------------------------------------
  
  usage: fiche [-DepbsdolBuw].
               [-D] [-e] [-d domain] [-p port] [-s slug size]
               [-o output directory] [-B buffer size] [-u user name]
               [-l log file] [-b banlist] [-w whitelist]
  -D option is for daemonizing fiche
  -e option is for using an extended character set for the URL
  
  Compile with Makefile or manually with -O2 and -pthread flags.
  
  To install use `make install` command.
  
  Use netcat to push text - example:
  $ cat fiche.c | nc localhost 9999
  
  -------------------------------------------------------------------------------
  */
  
  #include "fiche.h"
  
  #include <stdio.h>
  #include <stdarg.h>
  #include <stdlib.h>
  #include <string.h>
  
  #include <pwd.h>
  #include <time.h>
  #include <unistd.h>
  #include <pthread.h>
  
  #include <fcntl.h>
  #include <netdb.h>
  #include <sys/time.h>
  #include <sys/stat.h>
  #include <sys/types.h>
  #include <arpa/inet.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <netinet/in.h>
  
  #include <zlib.h>
  
  /******************************************************************************
   * Various declarations
   */
  const char *Fiche_Symbols = "abcdefghijklmnopqrstuvwxyz0123456789";
  
  
  /******************************************************************************
   * Inner structs
   */
  
  struct fiche_connection {
      int socket;
      struct sockaddr_in address;
  
      Fiche_Settings *settings;
  };
  
  
  /******************************************************************************
   * Static function declarations
   */
  
  // Settings-related
  
  /**
   * @brief Sets domain name
   * @warning settings.domain has to be freed after using this function!
   */
  static int set_domain_name(Fiche_Settings *settings);
  
  /**
   * @brief Changes user running this program to requested one
   * @warning Application has to be run as root to use this function
   */
  static int perform_user_change(const Fiche_Settings *settings);
  
  
  // Server-related
  
  /**
   * @brief Starts server with settings provided in Fiche_Settings struct
   */
  static int start_server(Fiche_Settings *settings);
  
  /**
   * @brief Dispatches incoming connections by spawning threads
   */
  static void dispatch_connection(int socket, Fiche_Settings *settings);
  
  /**
   * @brief Handles connections
   * @remarks Is being run by dispatch_connection in separate threads
   * @arg args Struct fiche_connection containing connection details
   */
  static void *handle_connection(void *args);
  
  // Server-related utils
  
  
  /**
   * @brief Generates a slug that will be used for paste creation
   * @warning output has to be freed after using!
   *
   * @arg output pointer to output string containing full path to directory
   * @arg length default or user-requested length of a slug
   * @arg extra_length additional length that was added to speed-up the
   *      generation process
   *
   * This function is used in connection with create_directory function
   * It generates strings that are used to create a directory for
   * user-provided data. If directory already exists, we ask this function
   * to generate another slug with increased size.
   */
  static void generate_slug(char **output, uint8_t length, uint8_t extra_length);
  
  
  /**
   * @brief Creates a directory at requested path using requested slug
   * @returns 0 if succeded, 1 if failed or dir already existed
   *
   * @arg output_dir root directory for all pastes
   * @arg slug directory name for a particular paste
   */
  static int create_directory(char *output_dir, char *slug);
  
  
  /**
   * @brief Saves data to file at requested path
   *
   * @arg data Buffer with data received from the user
   * @arg path Path at which file containing data from the buffer will be created
   */
  static int save_to_file(const Fiche_Settings *s, uint8_t *data, char *slug);
  
  
  // Logging-related
  
  /**
   * @brief Displays error messages
   */
  static void print_error(const char *format, ...);
  
  
  /**
   * @brief Displays status messages
   */
  static void print_status(const char *format, ...);
  
  
  /**
   * @brief Displays horizontal line
   */
  static void print_separator();
  
  
  /**
   * @brief Saves connection entry to the logfile
   */
  static void log_entry(const Fiche_Settings *s, const char *ip,
          const char *hostname, const char *slug);
  
  
  /**
   * @brief Returns string containing current date
   * @warning Output has to be freed!
   */
  static void get_date(char *buf);
  
  
  /**
   * @brief Time seed
   */
  unsigned int seed;
  
  /******************************************************************************
   * Public fiche functions
   */
  
  void fiche_init(Fiche_Settings *settings) {
  
      // Initialize everything to default values
      // or to NULL in case of pointers
  
      struct Fiche_Settings def = {
          // domain
          "example.com",
          // output dir
          "code",
          // port
          9999,
          // slug length
          4,
          // https
          false,
          // buffer length
          32768,
          // user name
          NULL,
          // path to log file
          NULL,
          // path to banlist
          NULL,
          // path to whitelist
          NULL,
  	// compress
  	false,
      };
  
      // Copy default settings to provided instance
      *settings = def;
  }
  
  int fiche_run(Fiche_Settings settings) {
  
      seed = time(NULL);
  
      // Display welcome message
      {
          char date[64];
          get_date(date);
          print_status("Starting fiche on %s...", date);
      }
  
      // Try to set requested user
      if ( perform_user_change(&settings) != 0) {
          print_error("Was not able to change the user!");
          return -1;
      }
  
      // Check if output directory is writable
      // - First we try to create it
      {
          mkdir(
              settings.output_dir_path,
              S_IRWXU | S_IRGRP | S_IROTH | S_IXOTH | S_IXGRP
          );
          // - Then we check if we can write there
          if ( access(settings.output_dir_path, W_OK) != 0 ) {
              print_error("Output directory not writable!");
              return -1;
          }
      }
  
      // Check if log file is writable (if set)
      if ( settings.log_file_path ) {
  
          // Create log file if it doesn't exist
          FILE *f = fopen(settings.log_file_path, "a+");
          fclose(f);
  
          // Then check if it's accessible
          if ( access(settings.log_file_path, W_OK) != 0 ) {
              print_error("Log file not writable!");
              return -1;
          }
  
      }
  
      // Try to set domain name
      if ( set_domain_name(&settings) != 0 ) {
          print_error("Was not able to set domain name!");
          return -1;
      }
  
      // Let people know that compression is enabled
      if (settings.compress) {
  		print_status("Compression enabled");
      }
  
      // Main loop in this method
      start_server(&settings);
  
      // Perform final cleanup
  
      // This is allways allocated on the heap
      free(settings.domain);
  
      return 0;
  
  }
  
  
  /******************************************************************************
   * Static functions below
   */
  
  static void print_error(const char *format, ...) {
      va_list args;
      va_start(args, format);
  
      printf("[Fiche][ERROR] ");
      vprintf(format, args);
      printf("\n");
  
      va_end(args);
  }
  
  
  static void print_status(const char *format, ...) {
      va_list args;
      va_start(args, format);
  
      printf("[Fiche][STATUS] ");
      vprintf(format, args);
      printf("\n");
  
      va_end(args);
  }
  
  
  static void print_separator() {
      printf("============================================================\n");
  }
  
  
  static void log_entry(const Fiche_Settings *s, const char *ip,
      const char *hostname, const char *slug)
  {
      // Logging to file not enabled, finish here
      if (!s->log_file_path) {
          return;
      }
  
      FILE *f = fopen(s->log_file_path, "a");
      if (!f) {
          print_status("Was not able to save entry to the log!");
          return;
      }
  
      char date[64];
      get_date(date);
  
      // Write entry to file
      fprintf(f, "%s -- %s -- %s (%s)\n", slug, date, ip, hostname);
      fclose(f);
  }
  
  
  static void get_date(char *buf) {
      struct tm curtime;
      time_t ltime;
  
      ltime=time(&ltime);
      localtime_r(&ltime, &curtime);
  
      // Save data to provided buffer
      if (asctime_r(&curtime, buf) == 0) {
          // Couldn't get date, setting first byte of the
          // buffer to zero so it won't be displayed
          buf[0] = 0;
          return;
      }
  
      // Remove newline char
      buf[strlen(buf)-1] = 0;
  }
  
  
  static int set_domain_name(Fiche_Settings *settings) {
  
      char *prefix = "";
      if (settings->https) {
          prefix = "https://";
      } else {
          prefix = "http://";
      }
      const int len = strlen(settings->domain) + strlen(prefix) + 1;
  
      char *b = malloc(len);
      if (!b) {
          return -1;
      }
  
      strcpy(b, prefix);
      strcat(b, settings->domain);
  
      settings->domain = b;
  
      print_status("Domain set to: %s.", settings->domain);
  
      return 0;
  }
  
  
  static int perform_user_change(const Fiche_Settings *settings) {
  
      // User change wasn't requested, finish here
      if (settings->user_name == NULL) {
          return 0;
      }
  
      // Check if root, if not - finish here
      if (getuid() != 0) {
          print_error("Run as root if you want to change the user!");
          return -1;
      }
  
      // Get user details
      const struct passwd *userdata = getpwnam(settings->user_name);
  
      const int uid = userdata->pw_uid;
      const int gid = userdata->pw_gid;
  
      if (uid == -1 || gid == -1) {
          print_error("Could find requested user: %s!", settings->user_name);
          return -1;
      }
  
      if (setgid(gid) != 0) {
          print_error("Couldn't switch to requested user: %s!", settings->user_name);
      }
  
      if (setuid(uid) != 0) {
          print_error("Couldn't switch to requested user: %s!", settings->user_name);
      }
  
      print_status("User changed to: %s.", settings->user_name);
  
      return 0;
  }
  
  
  static int start_server(Fiche_Settings *settings) {
  
      // Perform socket creation
      int s = socket(AF_INET, SOCK_STREAM, 0);
      if (s < 0) {
          print_error("Couldn't create a socket!");
          return -1;
      }
  
      // Set socket settings
      if ( setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 } , sizeof(int)) != 0 ) {
          print_error("Couldn't prepare the socket!");
          return -1;
      }
  
      // Prepare address and port handler
      struct sockaddr_in address;
      address.sin_family = AF_INET;
      address.sin_addr.s_addr = INADDR_ANY;
      address.sin_port = htons(settings->port);
  
      // Bind to port
      if ( bind(s, (struct sockaddr *) &address, sizeof(address)) != 0) {
          print_error("Couldn't bind to the port: %d!", settings->port);
          return -1;
      }
  
      // Start listening
      if ( listen(s, 128) != 0 ) {
          print_error("Couldn't start listening on the socket!");
          return -1;
      }
  
      print_status("Server started listening on port: %d.", settings->port);
      print_separator();
  
      // Run dispatching loop
      while (1) {
          dispatch_connection(s, settings);
      }
  
      // Give some time for all threads to finish
      // NOTE: this code is reached only in testing environment
      // There is currently no way to kill the main thread from any thread
      // Something like this can be done for testing purpouses:
      // int i = 0;
      // while (i < 3) {
      //     dispatch_connection(s, settings);
      //     i++;
      // }
  
      sleep(5);
  
      return 0;
  }
  
  
  static void dispatch_connection(int socket, Fiche_Settings *settings) {
  
      // Create address structs for this socket
      struct sockaddr_in address;
      socklen_t addlen = sizeof(address);
  
      // Accept a connection and get a new socket id
      const int s = accept(socket, (struct sockaddr *) &address, &addlen);
      if (s < 0 ) {
          print_error("Error on accepting connection!");
          return;
      }
  
      // Set timeout for accepted socket
      const struct timeval timeout = { 5, 0 };
  
      if ( setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0 ) {
          print_error("Couldn't set a timeout!");
      }
  
      if ( setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) != 0 ) {
          print_error("Couldn't set a timeout!");
      }
  
      // Create an argument for the thread function
      struct fiche_connection *c = malloc(sizeof(*c));
      if (!c) {
          print_error("Couldn't allocate memory!");
          return;
      }
      c->socket = s;
      c->address = address;
      c->settings = settings;
  
      // Spawn a new thread to handle this connection
      pthread_t id;
  
      if ( pthread_create(&id, NULL, &handle_connection, c) != 0 ) {
          print_error("Couldn't spawn a thread!");
          return;
      }
  
      // Detach thread if created succesfully
      // TODO: consider using pthread_tryjoin_np
      pthread_detach(id);
  
  }
  
  
  static void *handle_connection(void *args) {
  
      // Cast args to it's previous type
      struct fiche_connection *c = (struct fiche_connection *) args;
  
      // Get client's IP
      const char *ip = inet_ntoa(c->address.sin_addr);
  
      // Get client's hostname
      char hostname[1024];
  
      if (getnameinfo((struct sockaddr *)&c->address, sizeof(c->address),
              hostname, sizeof(hostname), NULL, 0, 0) != 0 ) {
  
          // Couldn't resolve a hostname
          strcpy(hostname, "n/a");
      }
  
      // Print status on this connection
      {
          char date[64];
          get_date(date);
          print_status("%s", date);
  
          print_status("Incoming connection from: %s (%s).", ip, hostname);
      }
  
      // Create a buffer
      uint8_t buffer[c->settings->buffer_len];
      memset(buffer, 0, c->settings->buffer_len);
  
      const int r = recv(c->socket, buffer, sizeof(buffer), MSG_WAITALL);
      if (r <= 0) {
          print_error("No data received from the client!");
          print_separator();
  
          // Close the socket
          close(c->socket);
  
          // Cleanup
          free(c);
          pthread_exit(NULL);
  
          return 0;
      }
  
      // - Check if request was performed with a known protocol
      // TODO
  
      // - Check if on whitelist
      // TODO
  
      // - Check if on banlist
      // TODO
  
      // Generate slug and use it to create an url
      char *slug;
      uint8_t extra = 0;
  
      do {
  
          // Generate slugs until it's possible to create a directory
          // with generated slug on disk
          generate_slug(&slug, c->settings->slug_len, extra);
  
          // Something went wrong in slug generation, break here
          if (!slug) {
              break;
          }
  
          // Increment counter for additional letters needed
          ++extra;
  
          // If i was incremented more than 128 times, something
          // for sure went wrong. We are closing connection and
          // killing this thread in such case
          if (extra > 128) {
              print_error("Couldn't generate a valid slug!");
              print_separator();
  
              // Cleanup
              free(c);
              free(slug);
              close(c->socket);
              pthread_exit(NULL);
              return NULL;
          }
  
      }
      while(create_directory(c->settings->output_dir_path, slug) != 0);
  
  
      // Slug generation failed, we have to finish here
      if (!slug) {
          print_error("Couldn't generate a slug!");
          print_separator();
  
          close(c->socket);
  
          // Cleanup
          free(c);
          pthread_exit(NULL);
          return NULL;
      }
  
  
      // Save to file failed, we have to finish here
      if ( save_to_file(c->settings, buffer, slug) != 0 ) {
          print_error("Couldn't save a file!");
          print_separator();
  
          close(c->socket);
  
          // Cleanup
          free(c);
          free(slug);
          pthread_exit(NULL);
          return NULL;
      }
  
      // Write a response to the user
      {
          // Create an url (additional byte for slash and one for new line)
          const size_t len = strlen(c->settings->domain) + strlen(slug) + 3;
  
          char url[len];
          snprintf(url, len, "%s%s%s%s", c->settings->domain, "/", slug, "\n");
  
          // Send the response
          write(c->socket, url, len);
      }
  
      print_status("Received %d bytes, saved to: %s.", r, slug);
      print_separator();
  
      // Log connection
      // TODO: log unsuccessful and rejected connections
      log_entry(c->settings, ip, hostname, slug);
  
      // Close the connection
      close(c->socket);
  
      // Perform cleanup of values used in this thread
      free(slug);
      free(c);
  
      pthread_exit(NULL);
  
      return NULL;
  }
  
  
  static void generate_slug(char **output, uint8_t length, uint8_t extra_length) {
  
      // Realloc buffer for slug when we want it to be bigger
      // This happens in case when directory with this name already
      // exists. To save time, we don't generate new slugs until
      // we spot an available one. We add another letter instead.
  
      if (extra_length > 0) {
          free(*output);
      }
  
      // Create a buffer for slug with extra_length if any
      *output = calloc(length + 1 + extra_length, sizeof(char));
  
      if (*output == NULL) {
          return;
      }
  
      // Take n-th symbol from symbol table and use it for slug generation
      for (int i = 0; i < length + extra_length; i++) {
          int n = rand_r(&seed) % strlen(Fiche_Symbols);
          *(output[0] + sizeof(char) * i) = Fiche_Symbols[n];
      }
  
  }
  
  
  static int create_directory(char *output_dir, char *slug) {
      if (!slug) {
          return -1;
      }
  
      // Additional byte is for the slash
      size_t len = strlen(output_dir) + strlen(slug) + 2;
  
      // Generate a path
      char *path = malloc(len);
      if (!path) {
          return -1;
      }
      snprintf(path, len, "%s%s%s", output_dir, "/", slug);
  
      // Create output directory, just in case
      mkdir(output_dir, S_IRWXU | S_IRGRP | S_IROTH | S_IXOTH | S_IXGRP);
  
      // Create slug directory
      const int r = mkdir(
          path,
          S_IRWXU | S_IRGRP | S_IROTH | S_IXOTH | S_IXGRP
      );
  
      free(path);
  
      return r;
  }
  
  
  static int save_to_file(const Fiche_Settings *s, uint8_t *data, char *slug) {
      char *file_name = "index.txt";
  
      // Additional 2 bytes are for 2 slashes
      size_t len = strlen(s->output_dir_path) + strlen(slug) + strlen(file_name) + 3;
  
      // Generate a path
      char *path = malloc(len);
      if (!path) {
          return -1;
      }
  
      snprintf(path,
              // If compression set, append .gz to file_name and add extra length
              (s->compress ? len+3 : len),
              (s->compress ? "%s%s%s%s%s.gz" : "%s%s%s%s%s"),
              s->output_dir_path, "/", slug, "/", file_name);
  
      // Attempt file saving
      FILE *f = fopen(path, "w");
      if (!f) {
          free(path);
          return -1;
      }
  
      // Null-terminate buffer if not null terminated already
      data[s->buffer_len - 1] = 0;
      
      if (s->compress) {
          // TODO: improve this
          // For now the chunk size will be eq to the received buffer length
          uint32_t chunk_size = s->buffer_len;
          unsigned char data_out[s->buffer_len];
          z_stream stream;
  
  #if (!defined(GZIP_COMPRESSION) || !defined(WINDOW_BITS)) 
  #define GZIP_COMPRESSION    16
  #define WINDOW_BITS    15
  #endif
          // Init zlib stream
          stream.zalloc = Z_NULL;
          stream.zfree = Z_NULL;
          stream.opaque = Z_NULL;
          if ((deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
                               WINDOW_BITS | GZIP_COMPRESSION, 8,
                               Z_DEFAULT_STRATEGY)) != Z_OK) {
              fclose(f);
              free(path);
              return Z_ERRNO;
          }
  
          stream.avail_in = strlen((const char *)data);
          stream.next_in = data;
          stream.avail_out = chunk_size;
          stream.next_out = data_out;
  
          if (deflate(&stream, Z_FINISH) == Z_STREAM_ERROR) {
              (void)deflateEnd(&stream);
              fclose(f);
              free(path);
              return Z_STREAM_ERROR;
          }
          fwrite(data_out, sizeof(char), (chunk_size - stream.avail_out), f);
          (void)deflateEnd(&stream);
      } else {
          if( fprintf(f, "%s", data) < 0 ) {
              fclose(f);
              free(path);
              return -1;
          }
      }
  
      fclose(f);
      free(path);
  
      return 0;
  }
  