fiche.c - fiche - A pastebin adjusted for gopher use
 (HTM) git clone git://vernunftzentrum.de/fiche.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) LICENSE
       ---
       fiche.c (19680B)
       ---
            1 /*
            2 Fiche - Command line pastebin for sharing terminal output.
            3 
            4 -------------------------------------------------------------------------------
            5 
            6 License: MIT (http://www.opensource.org/licenses/mit-license.php)
            7 Repository: https://github.com/solusipse/fiche/
            8 Live example: http://termbin.com
            9 
           10 -------------------------------------------------------------------------------
           11 
           12 usage: fiche [-DepbsdolBuw].
           13              [-D] [-e] [-d domain] [-p port] [-s slug size]
           14              [-o output directory] [-B buffer size] [-u user name]
           15              [-l log file] [-b banlist] [-w whitelist]
           16 -D option is for daemonizing fiche
           17 -e option is for using an extended character set for the URL
           18 
           19 Compile with Makefile or manually with -O2 and -pthread flags.
           20 
           21 To install use `make install` command.
           22 
           23 Use netcat to push text - example:
           24 $ cat fiche.c | nc localhost 9999
           25 
           26 -------------------------------------------------------------------------------
           27 */
           28 
           29 #include "fiche.h"
           30 
           31 #include <stdio.h>
           32 #include <stdarg.h>
           33 #include <stdlib.h>
           34 #include <string.h>
           35 
           36 #include <pwd.h>
           37 #include <time.h>
           38 #include <unistd.h>
           39 #include <pthread.h>
           40 
           41 #include <fcntl.h>
           42 #include <netdb.h>
           43 #include <sys/time.h>
           44 #include <sys/stat.h>
           45 #include <sys/types.h>
           46 #include <arpa/inet.h>
           47 #include <sys/socket.h>
           48 #include <netinet/in.h>
           49 #include <netinet/in.h>
           50 
           51 
           52 /******************************************************************************
           53  * Various declarations
           54  */
           55 const char *Fiche_Symbols = "abcdefghijklmnopqrstuvwxyz0123456789";
           56 
           57 /* File handle for the log file */
           58 static FILE *logfile_handle = NULL;
           59 
           60 
           61 /******************************************************************************
           62  * Inner structs
           63  */
           64 
           65 struct fiche_connection {
           66     int socket;
           67     struct sockaddr_in address;
           68 
           69     Fiche_Settings *settings;
           70 };
           71 
           72 
           73 /******************************************************************************
           74  * Static function declarations
           75  */
           76 
           77 // Settings-related
           78 
           79 /**
           80  * @brief Sets domain name
           81  * @warning settings.domain has to be freed after using this function!
           82  */
           83 static int set_domain_name(Fiche_Settings *settings);
           84 
           85 /**
           86  * @brief Changes user running this program to requested one
           87  * @warning Application has to be run as root to use this function
           88  */
           89 static int perform_user_change(const Fiche_Settings *settings);
           90 
           91 
           92 // Server-related
           93 
           94 /**
           95  * @brief Starts server with settings provided in Fiche_Settings struct
           96  */
           97 static int start_server(Fiche_Settings *settings);
           98 
           99 /**
          100  * @brief Dispatches incoming connections by spawning threads
          101  */
          102 static void dispatch_connection(int socket, Fiche_Settings *settings);
          103 
          104 /**
          105  * @brief Handles connections
          106  * @remarks Is being run by dispatch_connection in separate threads
          107  * @arg args Struct fiche_connection containing connection details
          108  */
          109 static void *handle_connection(void *args);
          110 
          111 // Server-related utils
          112 
          113 
          114 /**
          115  * @brief Generates a slug that will be used for paste creation
          116  * @warning output has to be freed after using!
          117  *
          118  * @arg output pointer to output string containing full path to directory
          119  * @arg length default or user-requested length of a slug
          120  * @arg extra_length additional length that was added to speed-up the
          121  *      generation process
          122  *
          123  * This function is used in connection with create_directory function
          124  * It generates strings that are used to create a directory for
          125  * user-provided data. If directory already exists, we ask this function
          126  * to generate another slug with increased size.
          127  */
          128 static void generate_slug(char **output, uint8_t length, uint8_t extra_length);
          129 
          130 
          131 /**
          132  * @brief Creates a directory at requested path using requested slug
          133  * @returns 0 if succeded, 1 if failed or dir already existed
          134  *
          135  * @arg output_dir root directory for all pastes
          136  * @arg slug directory name for a particular paste
          137  */
          138 static int create_directory(char *output_dir, char *slug);
          139 
          140 
          141 /**
          142  * @brief Saves data to file at requested path
          143  *
          144  * @arg data Buffer with data received from the user
          145  * @arg path Path at which file containing data from the buffer will be created
          146  */
          147 static int save_to_file(const Fiche_Settings *s, uint8_t *data, char *slug);
          148 
          149 
          150 // Logging-related
          151 
          152 /**
          153  * @brief Displays error messages
          154  */
          155 static void print_error(const char *format, ...);
          156 
          157 
          158 /**
          159  * @brief Displays status messages
          160  */
          161 static void print_status(const char *format, ...);
          162 
          163 
          164 /**
          165  * @brief Displays horizontal line
          166  */
          167 static void print_separator();
          168 
          169 
          170 /**
          171  * @brief Saves connection entry to the logfile
          172  */
          173 static void log_entry(const Fiche_Settings *s, const char *ip,
          174         const char *hostname, const char *slug);
          175 
          176 
          177 /**
          178  * @brief Returns string containing current date
          179  * @warning Output has to be freed!
          180  */
          181 static void get_date(char *buf);
          182 
          183 
          184 /******************************************************************************
          185  * Public fiche functions
          186  */
          187 
          188 void fiche_init(Fiche_Settings *settings) {
          189 
          190     // Initialize everything to default values
          191     // or to NULL in case of pointers
          192 
          193     struct Fiche_Settings def = {
          194         // domain
          195         "example.com",
          196         // output dir
          197         "code",
          198         // port
          199         9999,
          200         // slug length
          201         4,
          202         // protocol prefix
          203         "http",
          204         // buffer length
          205         32768,
          206         // user name
          207         NULL,
          208         // path to log file
          209         NULL,
          210         // path to banlist
          211         NULL,
          212         // path to whitelist
          213         NULL
          214     };
          215 
          216     // Copy default settings to provided instance
          217     *settings = def;
          218 }
          219 
          220 int fiche_run(Fiche_Settings settings) {
          221 
          222     // Check if log file is writable (if set)
          223     if ( settings.log_file_path ) {
          224 
          225         // Create log file if it doesn't exist
          226         FILE *f = fopen(settings.log_file_path, "a+");
          227         if (!f){
          228            print_error("Unable to create log file!");
          229            return -1;
          230         }
          231 
          232         // Then check if it's accessible
          233         if ( access(settings.log_file_path, W_OK) != 0 ) {
          234             print_error("Log file not writable!");
          235             fclose(f);
          236             return -1;
          237         }
          238 
          239         logfile_handle = f;
          240 
          241     }
          242 
          243     // Display welcome message
          244     {
          245         char date[64];
          246         get_date(date);
          247         print_status("Starting fiche on %s...", date);
          248     }
          249 
          250     // Try to set requested user
          251     if ( perform_user_change(&settings) != 0) {
          252         print_error("Was not able to change the user!");
          253         return -1;
          254     }
          255 
          256     // Check if output directory is writable
          257     // - First we try to create it
          258     {
          259         mkdir(
          260             settings.output_dir_path,
          261             S_IRWXU | S_IRGRP | S_IROTH | S_IXOTH | S_IXGRP
          262         );
          263         // - Then we check if we can write there
          264         if ( access(settings.output_dir_path, W_OK) != 0 ) {
          265             print_error("Output directory not writable!");
          266             return -1;
          267         }
          268     }
          269 
          270     // Try to set domain name
          271     if ( set_domain_name(&settings) != 0 ) {
          272         print_error("Was not able to set domain name!");
          273         if (logfile_handle) fclose(logfile_handle);
          274         return -1;
          275     }
          276 
          277     pid_t pid = fork();
          278     if (pid == -1){
          279         char *err = strerror(0);
          280         print_error("Unable to fork into background: %s", err);
          281         if (logfile_handle) fclose(logfile_handle);
          282         return -1;
          283     }
          284     if (pid > 0){
          285         //parent
          286         if (logfile_handle) fclose(logfile_handle);
          287         return 0;
          288     }
          289 
          290     if (setsid() == -1){
          291         char *err = strerror(0);
          292         print_error("Creating new session id: %s", err);
          293         if (logfile_handle) fclose(logfile_handle);
          294         return -1;
          295     }
          296 
          297     // We are detached so close those to avoid noise
          298     fclose(stdin);
          299     fclose(stdout);
          300     fclose(stderr);
          301 
          302     // Main loop in this method
          303     start_server(&settings);
          304 
          305     // Perform final cleanup
          306 
          307     // This is allways allocated on the heap
          308     free(settings.domain);
          309 
          310     if (logfile_handle) fclose(logfile_handle);
          311 
          312     return 0;
          313 
          314 }
          315 
          316 
          317 /******************************************************************************
          318  * Static functions below
          319  */
          320 
          321 static void print_error(const char *format, ...) {
          322     va_list args;
          323     FILE *fd = logfile_handle ? logfile_handle : stderr;
          324 
          325     va_start(args, format);
          326 
          327     fprintf(fd, "[Fiche][ERROR] ");
          328     vfprintf(fd, format, args);
          329     fprintf(fd, "\n");
          330     fflush(fd);
          331     va_end(args);
          332 }
          333 
          334 
          335 static void print_status(const char *format, ...) {
          336     va_list args;
          337     FILE *fd = logfile_handle ? logfile_handle : stderr;
          338 
          339     va_start(args, format);
          340 
          341     fprintf(fd, "[Fiche][STATUS] ");
          342     vfprintf(fd, format, args);
          343     fprintf(fd, "\n");
          344     fflush(fd);
          345     va_end(args);
          346 }
          347 
          348 
          349 static void print_separator() {
          350     FILE *fd = logfile_handle ? logfile_handle : stderr;
          351     fprintf(fd, "============================================================\n");
          352     fflush(fd);
          353 }
          354 
          355 
          356 static void log_entry(const Fiche_Settings *s, const char *ip,
          357     const char *hostname, const char *slug)
          358 {
          359     // Logging to file not enabled, finish here
          360     if (!s->log_file_path) {
          361         return;
          362     }
          363 
          364     if (!logfile_handle) {
          365         print_status("Was not able to save entry to the log!");
          366         return;
          367     }
          368 
          369     char date[64];
          370     get_date(date);
          371 
          372     // Write entry to file
          373     fprintf(logfile_handle, "%s -- %s -- %s (%s)\n", slug, date, ip, hostname);
          374 }
          375 
          376 
          377 static void get_date(char *buf) {
          378     struct tm curtime;
          379     time_t ltime;
          380 
          381     ltime=time(&ltime);
          382     localtime_r(&ltime, &curtime);
          383 
          384     // Save data to provided buffer
          385     if (asctime_r(&curtime, buf) == 0) {
          386         // Couldn't get date, setting first byte of the
          387         // buffer to zero so it won't be displayed
          388         buf[0] = 0;
          389         return;
          390     }
          391 
          392     // Remove newline char
          393     buf[strlen(buf)-1] = 0;
          394 }
          395 
          396 
          397 static int set_domain_name(Fiche_Settings *settings) {
          398 
          399     const int len = strlen(settings->domain) + strlen(settings->prefix) + 4;
          400 
          401     char *b = malloc(len);
          402     if (!b) {
          403         return -1;
          404     }
          405 
          406     strlcpy(b, settings->prefix, len);
          407     strlcat(b, "://", len);
          408     strlcat(b, settings->domain, len);
          409 
          410     settings->domain = b;
          411 
          412     print_status("Domain set to: %s.", settings->domain);
          413 
          414     return 0;
          415 }
          416 
          417 
          418 static int perform_user_change(const Fiche_Settings *settings) {
          419 
          420     // User change wasn't requested, finish here
          421     if (settings->user_name == NULL) {
          422         return 0;
          423     }
          424 
          425     // Check if root, if not - finish here
          426     if (getuid() != 0) {
          427         print_error("Run as root if you want to change the user!");
          428         return -1;
          429     }
          430 
          431     // Get user details
          432     const struct passwd *userdata = getpwnam(settings->user_name);
          433 
          434     const int uid = userdata->pw_uid;
          435     const int gid = userdata->pw_gid;
          436 
          437     if (uid == -1 || gid == -1) {
          438         print_error("Could find requested user: %s!", settings->user_name);
          439         return -1;
          440     }
          441 
          442     if (setgid(gid) != 0) {
          443         print_error("Couldn't switch to requested user: %s!", settings->user_name);
          444     }
          445 
          446     if (setuid(uid) != 0) {
          447         print_error("Couldn't switch to requested user: %s!", settings->user_name);
          448     }
          449 
          450     print_status("User changed to: %s.", settings->user_name);
          451 
          452     return 0;
          453 }
          454 
          455 
          456 static int start_server(Fiche_Settings *settings) {
          457 
          458     // Perform socket creation
          459     int s = socket(AF_INET, SOCK_STREAM, 0);
          460     if (s < 0) {
          461         print_error("Couldn't create a socket!");
          462         return -1;
          463     }
          464 
          465     // Set socket settings
          466     if ( setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 } , sizeof(int)) != 0 ) {
          467         print_error("Couldn't prepare the socket!");
          468         return -1;
          469     }
          470 
          471     // Prepare address and port handler
          472     struct sockaddr_in address;
          473     address.sin_family = AF_INET;
          474     address.sin_addr.s_addr = INADDR_ANY;
          475     address.sin_port = htons(settings->port);
          476 
          477     // Bind to port
          478     if ( bind(s, (struct sockaddr *) &address, sizeof(address)) != 0) {
          479         print_error("Couldn't bind to the port: %d!", settings->port);
          480         return -1;
          481     }
          482 
          483     // Start listening
          484     if ( listen(s, 128) != 0 ) {
          485         print_error("Couldn't start listening on the socket!");
          486         return -1;
          487     }
          488 
          489     print_status("Server started listening on port: %d.", settings->port);
          490     print_separator();
          491 
          492     // Run dispatching loop
          493     while (1) {
          494         dispatch_connection(s, settings);
          495     }
          496 
          497     // Give some time for all threads to finish
          498     // NOTE: this code is reached only in testing environment
          499     // There is currently no way to kill the main thread from any thread
          500     // Something like this can be done for testing purpouses:
          501     // int i = 0;
          502     // while (i < 3) {
          503     //     dispatch_connection(s, settings);
          504     //     i++;
          505     // }
          506 
          507     sleep(5);
          508 
          509     return 0;
          510 }
          511 
          512 
          513 static void dispatch_connection(int socket, Fiche_Settings *settings) {
          514 
          515     // Create address structs for this socket
          516     struct sockaddr_in address;
          517     socklen_t addlen = sizeof(address);
          518 
          519     // Accept a connection and get a new socket id
          520     const int s = accept(socket, (struct sockaddr *) &address, &addlen);
          521     if (s < 0 ) {
          522         print_error("Error on accepting connection!");
          523         return;
          524     }
          525 
          526     // Set timeout for accepted socket
          527     const struct timeval timeout = { 5, 0 };
          528 
          529     if ( setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0 ) {
          530         print_error("Couldn't set a timeout!");
          531     }
          532 
          533     if ( setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) != 0 ) {
          534         print_error("Couldn't set a timeout!");
          535     }
          536 
          537     // Create an argument for the thread function
          538     struct fiche_connection *c = malloc(sizeof(*c));
          539     if (!c) {
          540         print_error("Couldn't allocate memory!");
          541         return;
          542     }
          543     c->socket = s;
          544     c->address = address;
          545     c->settings = settings;
          546 
          547     // Spawn a new thread to handle this connection
          548     pthread_t id;
          549 
          550     if ( pthread_create(&id, NULL, &handle_connection, c) != 0 ) {
          551         print_error("Couldn't spawn a thread!");
          552         return;
          553     }
          554 
          555     // Detach thread if created succesfully
          556     // TODO: consider using pthread_tryjoin_np
          557     pthread_detach(id);
          558 
          559 }
          560 
          561 
          562 static void *handle_connection(void *args) {
          563 
          564     // Cast args to it's previous type
          565     struct fiche_connection *c = (struct fiche_connection *) args;
          566 
          567     // Get client's IP
          568     const char *ip = inet_ntoa(c->address.sin_addr);
          569 
          570     // Get client's hostname
          571     char hostname[1024];
          572 
          573     if (getnameinfo((struct sockaddr *)&c->address, sizeof(c->address),
          574             hostname, sizeof(hostname), NULL, 0, 0) != 0 ) {
          575 
          576         // Couldn't resolve a hostname
          577        strlcpy(hostname, "n/a", 1024);
          578     }
          579 
          580     // Print status on this connection
          581     {
          582         char date[64];
          583         get_date(date);
          584         print_status("%s", date);
          585 
          586         print_status("Incoming connection from: %s (%s).", ip, hostname);
          587     }
          588 
          589     // Create a buffer
          590     uint8_t buffer[c->settings->buffer_len];
          591     memset(buffer, 0, c->settings->buffer_len);
          592 
          593     const int r = recv(c->socket, buffer, sizeof(buffer), MSG_WAITALL);
          594     if (r <= 0) {
          595         print_error("No data received from the client!");
          596         print_separator();
          597 
          598         // Close the socket
          599         close(c->socket);
          600 
          601         // Cleanup
          602         free(c);
          603         pthread_exit(NULL);
          604 
          605         return 0;
          606     }
          607 
          608     // - Check if request was performed with a known protocol
          609     // TODO
          610 
          611     // - Check if on whitelist
          612     // TODO
          613 
          614     // - Check if on banlist
          615     // TODO
          616 
          617     // Generate slug and use it to create an url
          618     char *slug;
          619     uint8_t extra = 0;
          620 
          621     do {
          622 
          623         // Generate slugs until it's possible to create a directory
          624         // with generated slug on disk
          625         generate_slug(&slug, c->settings->slug_len, extra);
          626 
          627         // Something went wrong in slug generation, break here
          628         if (!slug) {
          629             break;
          630         }
          631 
          632         // Increment counter for additional letters needed
          633         ++extra;
          634 
          635         // If i was incremented more than 128 times, something
          636         // for sure went wrong. We are closing connection and
          637         // killing this thread in such case
          638         if (extra > 128) {
          639             print_error("Couldn't generate a valid slug!");
          640             print_separator();
          641 
          642             // Cleanup
          643             free(c);
          644             free(slug);
          645             close(c->socket);
          646             pthread_exit(NULL);
          647             return NULL;
          648         }
          649 
          650     }
          651     while(create_directory(c->settings->output_dir_path, slug) != 0);
          652 
          653 
          654     // Slug generation failed, we have to finish here
          655     if (!slug) {
          656         print_error("Couldn't generate a slug!");
          657         print_separator();
          658 
          659         close(c->socket);
          660 
          661         // Cleanup
          662         free(c);
          663         pthread_exit(NULL);
          664         return NULL;
          665     }
          666 
          667 
          668     // Save to file failed, we have to finish here
          669     if ( save_to_file(c->settings, buffer, slug) != 0 ) {
          670         print_error("Couldn't save a file!");
          671         print_separator();
          672 
          673         close(c->socket);
          674 
          675         // Cleanup
          676         free(c);
          677         free(slug);
          678         pthread_exit(NULL);
          679         return NULL;
          680     }
          681 
          682     // Write a response to the user
          683     {
          684         // Create an url (additional byte for slash and one for new line)
          685         const size_t len = strlen(c->settings->domain) + strlen(slug) + 3;
          686 
          687         char url[len];
          688         snprintf(url, len, "%s%s%s%s", c->settings->domain, "/", slug, "\n");
          689 
          690         // Send the response
          691         write(c->socket, url, len);
          692     }
          693 
          694     print_status("Received %d bytes, saved to: %s.", r, slug);
          695     print_separator();
          696 
          697     // Log connection
          698     // TODO: log unsuccessful and rejected connections
          699     log_entry(c->settings, ip, hostname, slug);
          700 
          701     // Close the connection
          702     close(c->socket);
          703 
          704     // Perform cleanup of values used in this thread
          705     free(slug);
          706     free(c);
          707 
          708     pthread_exit(NULL);
          709 
          710     return NULL;
          711 }
          712 
          713 
          714 static void generate_slug(char **output, uint8_t length, uint8_t extra_length) {
          715 
          716     // Realloc buffer for slug when we want it to be bigger
          717     // This happens in case when directory with this name already
          718     // exists. To save time, we don't generate new slugs until
          719     // we spot an available one. We add another letter instead.
          720 
          721     if (extra_length > 0) {
          722         free(*output);
          723     }
          724 
          725     // Create a buffer for slug with extra_length if any
          726     *output = calloc(length + 1 + extra_length, sizeof(char));
          727 
          728     if (*output == NULL) {
          729         return;
          730     }
          731 
          732     // Take n-th symbol from symbol table and use it for slug generation
          733     for (int i = 0; i < length + extra_length; i++) {
          734         int n = arc4random() % strlen(Fiche_Symbols);
          735         *(output[0] + sizeof(char) * i) = Fiche_Symbols[n];
          736     }
          737 
          738 }
          739 
          740 
          741 static int create_directory(char *output_dir, char *slug) {
          742     if (!slug) {
          743         return -1;
          744     }
          745 
          746     // Additional byte is for the slash
          747     size_t len = strlen(output_dir) + strlen(slug) + 2;
          748 
          749     // Generate a path
          750     char *path = malloc(len);
          751     if (!path) {
          752         return -1;
          753     }
          754     snprintf(path, len, "%s%s%s", output_dir, "/", slug);
          755 
          756     // Create output directory, just in case
          757     mkdir(output_dir, S_IRWXU | S_IRGRP | S_IROTH | S_IXOTH | S_IXGRP);
          758 
          759     // Create slug directory
          760     const int r = mkdir(
          761         path,
          762         S_IRWXU | S_IRGRP | S_IROTH | S_IXOTH | S_IXGRP
          763     );
          764 
          765     free(path);
          766 
          767     return r;
          768 }
          769 
          770 
          771 static int save_to_file(const Fiche_Settings *s, uint8_t *data, char *slug) {
          772     char *file_name = "paste.txt";
          773 
          774     // Additional 2 bytes are for 2 slashes
          775     size_t len = strlen(s->output_dir_path) + strlen(slug) + strlen(file_name) + 3;
          776 
          777     // Generate a path
          778     char *path = malloc(len);
          779     if (!path) {
          780         return -1;
          781     }
          782 
          783     snprintf(path, len, "%s%s%s%s%s", s->output_dir_path, "/", slug, "/", file_name);
          784 
          785     // Attempt file saving
          786     FILE *f = fopen(path, "w");
          787     if (!f) {
          788         free(path);
          789         return -1;
          790     }
          791 
          792     // Null-terminate buffer if not null terminated already
          793     data[s->buffer_len - 1] = 0;
          794 
          795     if ( fprintf(f, "%s", data) < 0 ) {
          796         fclose(f);
          797         free(path);
          798         return -1;
          799     }
          800 
          801     fclose(f);
          802     free(path);
          803 
          804     return 0;
          805 }