Entirely rewritten fiche - fiche - A pastebin adjusted for gopher use
 (HTM) git clone git://vernunftzentrum.de/fiche.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) LICENSE
       ---
 (DIR) commit 8ff08a04093e51dfdbde0b6a4f8f3e58dcbe43be
 (DIR) parent c054f8dc20c193c6354fe7e636255758b06fc6f7
 (HTM) Author: solusipse <solus1ps3@gmail.com>
       Date:   Sat,  2 Sep 2017 17:51:43 +0200
       
       Entirely rewritten fiche
       
       Diffstat:
         .gitignore                          |       6 ++++++
         Makefile                            |      12 ++++--------
         fiche.c                             |    1056 ++++++++++++++++++-------------
         fiche.h                             |     169 ++++++++++++++-----------------
         main.c                              |     133 +++++++++++++++++++++++++++++++
       
       5 files changed, 826 insertions(+), 550 deletions(-)
       ---
 (DIR) diff --git a/.gitignore b/.gitignore
       @@ -1,2 +1,8 @@
        # ignore binaries
        /fiche
       +
       +# ignore default outpit dir
       +code/
       +
       +# ignore log files
       +*.log
 (DIR) diff --git a/Makefile b/Makefile
       @@ -1,13 +1,9 @@
       -# -----------------------------------
       -# Fiche MAKEFILE
       -# https://github.com/solusipse/fiche
       -# solusipse.net
       -# -----------------------------------
       -
       -CFLAGS+=-pthread -O2
       +# for debug add -g -O0 to line below
       +CFLAGS+=-pthread -O2 -Wall -Wextra -Wpedantic -Wstrict-overflow -fno-strict-aliasing -std=gnu11 -g -O0
        prefix=/usr/local
        
       -all: fiche
       +all:
       +        ${CC} main.c fiche.c $(CFLAGS) -o fiche
        
        install: fiche
                install -m 0755 fiche $(prefix)/bin
 (DIR) diff --git a/fiche.c b/fiche.c
       @@ -5,7 +5,7 @@ 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://code.solusipse.net/
       +Live example: http://termbin.com
        
        -------------------------------------------------------------------------------
        
       @@ -13,566 +13,720 @@ 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 <sys/param.h>
        #include "fiche.h"
        
       -int main(int argc, char **argv)
       -{
       -    time_seed = time(0);
       +#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>
       +
       +
       +/******************************************************************************
       + * Various declarations
       + */
       +const char *Fiche_Symbols = "abcdefghijklmnopqrstuvwxyz0123456789";
        
       -    parse_parameters(argc, argv);
       -    set_domain_name();
        
       -    if (getuid() == 0)
       -    {
       -        if (UID == -1)
       -            error("user not set");
       -        if (setgid(GID) != 0)
       -            error("Unable to drop group privileges");
       -        if (setuid(UID) != 0)
       -            error("Unable to drop user privileges");
       -    }
       +/******************************************************************************
       + * 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);
        
       -    if (BASEDIR == NULL)
       -        set_basedir();
        
       -    startup_message();
       +// Server-related
        
       -    int listen_socket, optval = 1;
       -    struct sockaddr_in server_address;
       +/**
       + * @brief Starts server with settings provided in Fiche_Settings struct
       + */
       +static int start_server(Fiche_Settings *settings);
        
       -    listen_socket = create_socket();
       -    setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval , sizeof(int));
       +/**
       + * @brief Dispatches incoming connections by spawning threads
       + */
       +static void dispatch_connection(int socket, Fiche_Settings *settings);
        
       -#if (HAVE_INET6)
       -    struct sockaddr_in6 server_address6;
       -    if (IPv6)
       +/**
       + * @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(uint8_t *data, char *output_dir, char *path);
       +
       +
       +// 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 char *get_date();
       +
       +
       +/**
       + * @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,
       +        // buffer length
       +        32768,
       +        // user name
       +        NULL,
       +        // path to log file
       +        NULL,
       +        // path to banlist
       +        NULL,
       +        // path to whitelist
       +        NULL
       +    };
       +
       +    // Copy default settings to provided instance
       +    *settings = def;
       +}
       +
       +int fiche_run(Fiche_Settings settings) {
       +
       +    seed = time(NULL);
       +
       +    // Display welcome message
            {
       -        server_address6 = set_address6(server_address6);
       -        bind_to_port6(listen_socket, server_address6);
       +        char *date = get_date();
       +        print_status("Starting fiche on %s...", date);
       +        free(date);
            }
       -    else
       -    {
       -#else
       -    if (1) {
       -#endif
       -        server_address = set_address(server_address);
       -        bind_to_port(listen_socket, server_address);
       +
       +    // Try to set requested user
       +    if ( perform_user_change(&settings) != 0) {
       +        print_error("Was not able to change the user!");
       +        return -1;
            }
        
       -    if (DAEMON)
       +    // Check if output directory is writable
       +    // - First we try to create it
            {
       -        pid_t pid;
       +        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
       +        creat(settings.log_file_path, S_IRWXU);
       +
       +        // Then check if it's accessible
       +        if ( access(settings.log_file_path, W_OK) != 0 ) {
       +            print_error("Log file not writable!");
       +            return -1;
       +        }
        
       -        pid = fork();
       -        if (pid == -1)
       -            error("Failed to fork");
       -        if (pid == 0)
       -            while (1) perform_connection(listen_socket);
            }
       -    else
       -        while (1) perform_connection(listen_socket);
       +
       +    // Try to set domain name
       +    if ( set_domain_name(&settings) != 0 ) {
       +        print_error("Was not able to set domain name!");
       +        return -1;
       +    }
       +
       +    // 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);
        }
        
       -void *thread_connection(void *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)
        {
       -    int connection_socket = ((struct thread_arguments *) args ) -> connection_socket;
       -    struct sockaddr_in client_address;
       -    struct client_data data;
       -#if (HAVE_INET6)
       -    struct sockaddr_in6 client_address6;
       -    if (IPv6)
       -    {
       -        client_address6 = ((struct thread_arguments *) args ) -> client_address6;
       -        data = get_client_address6(client_address6);
       +    // Logging to file not enabled, finish here
       +    if (!s->log_file_path) {
       +        return;
            }
       -    else
       -    {
       -#else
       -    if (1) {
       -#endif
       -        client_address = ((struct thread_arguments *) args ) -> client_address;
       -        data = get_client_address(client_address);
       +
       +    FILE *f = fopen(s->log_file_path, "a");
       +    if (!f) {
       +        print_status("Was not able to save entry to the log!");
       +        return;
            }
        
       -    char buffer[BUFSIZE];
       -    bzero(buffer, BUFSIZE);
       -    int status = recv(connection_socket, buffer, BUFSIZE, MSG_WAITALL);
       +    char *date = get_date();
        
       -    if (WHITELIST != NULL && check_whitelist(data.ip_address) == NULL)
       -    {
       -        display_info(data, NULL, "Rejected connection from unknown user.");
       -        save_log(NULL, data.ip_address, data.hostname);
       -        if (write(connection_socket, "You are not whitelisted!\n", 26) < 0)
       -          printf("Error writing on stream socket\n");
       -        close(connection_socket);
       -        pthread_exit(NULL);
       -    }
       +    // Write entry to file
       +    fprintf(f, "%s -- %s -- %s (%s)\n", slug, date, ip, hostname);
       +    fclose(f);
        
       -    if (BANLIST != NULL && check_banlist(data.ip_address) != NULL)
       -    {
       -        display_info(data, NULL, "Rejected connection from banned user.");
       -        save_log(NULL, data.ip_address, data.hostname);
       -        if (write(connection_socket, "You are banned!\n", 17) < 0)
       -          printf("Error writing on stream socket\n");
       -        close(connection_socket);
       -        pthread_exit(NULL);
       -    }
       +    free(date);
       +}
        
       -    if (check_protocol(buffer) == 1)
       -        status = -1;
        
       -    if (status != -1)
       -    {
       -        char slug[SLUG_SIZE+8];
       -        generate_url(buffer, slug, SLUG_SIZE+8, data);
       -        save_log(slug, data.ip_address, data.hostname);
       -        char response[strlen(slug) + strlen(DOMAIN) + 2];
       -        snprintf(response, sizeof response, "%s%s\n", DOMAIN, slug);
       -        if (write(connection_socket, response, strlen(response)) < 0)
       -          printf("Error writing on stream socket\n");
       -    }
       -    else
       -    {
       -        display_info(data, NULL, "Invalid connection.");
       -        save_log(NULL, data.ip_address, data.hostname);
       -        if (write(connection_socket, "Use netcat.\n", 12) < 0)
       -          printf("Error writing on stream socket\n");
       -    }
       +static char *get_date() {
       +    struct tm curtime;
       +    time_t ltime;
        
       -    close(connection_socket);
       -    pthread_exit(NULL);
       +    ltime=time(&ltime);
       +    localtime_r(&ltime, &curtime);
       +
       +    // Much more than required, date string is usually about 25 chars
       +    char buf[128];
       +    asctime_r(&curtime, buf);
       +
       +    char *out = malloc(strlen(buf) + 1);
       +    strcpy(out, buf);
       +
       +    // Remove newline char
       +    out[strlen(buf)-1] = 0;
       +
       +    return out;
        }
        
       -void perform_connection(int listen_socket)
       -{
       -    pthread_t thread_id;
       -    struct sockaddr_in client_address;
        
       -    int address_length;
       -    int connection_socket;
       +static int set_domain_name(Fiche_Settings *settings) {
        
       -#if (HAVE_INET6)
       -    struct sockaddr_in6 client_address6;
       -    if (IPv6)
       -    {
       -        address_length = sizeof(client_address6);
       -        connection_socket = accept(listen_socket, (struct sockaddr *) &client_address6, (void *) &address_length);
       +    const char *prefix = "http://";
       +    const int len = strlen(settings->domain) + strlen(prefix) + 1;
       +
       +    char *b = malloc(len);
       +    if (b == NULL) {
       +        return -1;
            }
       -    else
       -    {
       -#else
       -    if (1) {
       -#endif
       -        address_length = sizeof(client_address);
       -        connection_socket = accept(listen_socket, (struct sockaddr *) &client_address, (void *) &address_length);
       -    }
       -
       -    struct timeval timeout;
       -    timeout.tv_sec = 5;
       -    timeout.tv_usec = 0;
       -
       -    if (setsockopt (connection_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) < 0)
       -        error("while setting setsockopt timeout");
       -    if (setsockopt (connection_socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)) < 0)
       -        error("while setting setsockopt timeout");
       -
       -    struct thread_arguments arguments;
       -    arguments.connection_socket = connection_socket;
       -#if (HAVE_INET6)
       -    if (IPv6)
       -        arguments.client_address6 = client_address6;
       -    else
       -#endif
       -        arguments.client_address = client_address;
       -
       -    if (pthread_create(&thread_id, NULL, &thread_connection, &arguments) != 0)
       -        error("on thread creation");
       -    else
       -        pthread_detach(thread_id);
       -}
        
       -char *get_date()
       -{
       -    time_t rawtime;
       -    struct tm *timeinfo;
       -    char *timechar;
       +    strcpy(b, prefix);
       +    strcat(b, settings->domain);
       +
       +    settings->domain = b;
        
       -    time(&rawtime);
       -    timeinfo = localtime(&rawtime);
       -    timechar = asctime(timeinfo);
       -    timechar[strlen(timechar)-1] = 0;
       +    print_status("Domain set to: %s.", settings->domain);
        
       -    return timechar;
       +    return 0;
        }
        
       -struct client_data get_client_address(struct sockaddr_in client_address)
       -{
       -    struct hostent *hostp;
       -    struct client_data data;
       -    char *hostaddrp;
        
       -    hostp = gethostbyaddr((const char *)&client_address.sin_addr.s_addr, sizeof(client_address.sin_addr.s_addr), AF_INET);
       -    if (hostp == NULL)
       -    {
       -        printf("WARNING: Couldn't obtain client's hostname\n");
       -        data.hostname = "n/a";
       +static int perform_user_change(const Fiche_Settings *settings) {
       +
       +    // User change wasn't requested, finish here
       +    if (settings->user_name == NULL) {
       +        return 0;
            }
       -    else
       -        data.hostname = hostp->h_name;
        
       -    hostaddrp = inet_ntoa(client_address.sin_addr);
       -    if (hostaddrp == NULL)
       -    {
       -        printf("WARNING: Couldn't obtain client's address\n");
       -        data.ip_address = "n/a";
       +    // Check if root, if not - finish here
       +    if (getuid() != 0) {
       +        print_error("Run as root if you want to change the user!");
       +        return -1;
            }
       -    else
       -        data.ip_address = hostaddrp;
        
       -    return data;
       -}
       +    // Get user details
       +    const struct passwd *userdata = getpwnam(settings->user_name);
        
       -#if (HAVE_INET6)
       -struct client_data get_client_address6(struct sockaddr_in6 client_address6)
       -{
       -    struct hostent *hostp;
       -    struct client_data data;
       -    static char hostaddrp[INET6_ADDRSTRLEN];
       +    const int uid = userdata->pw_uid;
       +    const int gid = userdata->pw_gid;
        
       -    hostp = gethostbyaddr((const char *)&client_address6.sin6_addr, sizeof(client_address6.sin6_addr), AF_INET6);
       -    if (hostp == NULL)
       -    {
       -        printf("WARNING: Couldn't obtain client's hostname\n");
       -        data.hostname = "n/a";
       +    if (uid == -1 || gid == -1) {
       +        print_error("Could find requested user: %s!", settings->user_name);
       +        return -1;
            }
       -    else
       -        data.hostname = hostp->h_name;
        
       -    inet_ntop(AF_INET6, &(client_address6.sin6_addr), hostaddrp,
       -              INET6_ADDRSTRLEN);
       -    if (hostaddrp == NULL)
       -    {
       -        printf("WARNING: Couldn't obtain client's address\n");
       -        data.ip_address = "n/a";
       +    if (setgid(gid) != 0) {
       +        print_error("Couldn't switch to requested user: %s!", settings->user_name);
            }
       -    else
       -        data.ip_address = hostaddrp;
        
       -    return data;
       +    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;
        }
       -#endif
        
       -void save_log(char *slug, char *hostaddrp, char *h_name)
       -{
       -    if (LOG != NULL)
       -    {
       -        char contents[256];
        
       -        if (slug != NULL)
       -            snprintf(contents, sizeof contents, "%s -- %s -- %s (%s)\n", slug, get_date(), hostaddrp, h_name);
       -        else
       -            snprintf(contents, sizeof contents, "%s -- %s -- %s (%s)\n", "rej", get_date(), hostaddrp, h_name);
       +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;
       +    }
        
       -        FILE *fp;
       -        fp = fopen(LOG, "a");
       -        fprintf(fp, "%s", contents);
       -        fclose(fp);
       +    // 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;
        }
        
       -void display_info(struct client_data data, char *slug, char *message)
       -{
       -    if (DAEMON)
       +
       +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;
       +    }
        
       -    if (slug == NULL)
       -        printf("%s\n", message);
       -    else
       -        printf("Saved to: %s\n", slug);
       +    // Set timeout for accepted socket
       +    const struct timeval timeout = { 5, 0 };
        
       -    printf("%s\n", get_date());
       -    printf("Client: %s (%s)\n", data.ip_address, data.hostname);
       -    printf("====================================\n");
       -}
       +    if ( setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0 ) {
       +        print_error("Couldn't set a timeout!");
       +    }
        
       -char *check_banlist(char *ip_address)
       -{
       -    load_list(BANFILE, 0);
       -    return strstr(BANLIST, ip_address);
       -}
       +    if ( setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) != 0 ) {
       +        print_error("Couldn't set a timeout!");
       +    }
        
       -char *check_whitelist(char *ip_address)
       -{
       -    load_list(WHITEFILE, 1);
       -    return strstr(WHITELIST, ip_address);
       -}
       +    // Create an argument for the thread function
       +    //struct fiche_connection c = { s, address, settings };
       +    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;
        
       -void load_list(char *file_path, int type)
       -{
       -    FILE *fp;
       -    
       -    if (( fp = fopen(file_path, "r")) == NULL )
       -      error("cannot load list");
       -    
       -    fseek(fp, 0, SEEK_END);
       -    long fsize = ftell(fp);
       -    fseek(fp, 0, SEEK_SET);
       -
       -    char *buffer = malloc(fsize + 1);
       -    if (fread(buffer, fsize, 1, fp) != fsize)
       -      error("reading list failed");
       -    fclose(fp);
       -
       -    buffer[fsize] = 0;
       -
       -    if (type == 0)
       -        BANLIST = buffer;
       -    else
       -        WHITELIST = buffer;
       -
       -    free(buffer);
       -}
       +    // Spawn a new thread to handle this connection
       +    pthread_t id;
        
       -int create_socket()
       -{
       -    int lsocket;
       -#if (HAVE_INET6)
       -    if (IPv6)
       -        lsocket = socket(AF_INET6, SOCK_STREAM, 0);
       -    else
       -#endif
       -        lsocket = socket(AF_INET, SOCK_STREAM, 0);
       -
       -    if (lsocket < 0)
       -        error("Couldn't open socket");
       -
       -    return lsocket;
       -}
       +    if ( pthread_create(&id, NULL, &handle_connection, c) != 0 ) {
       +        print_error("Couldn't spawn a thread!");
       +        return;
       +    }
        
       -struct sockaddr_in set_address(struct sockaddr_in server_address)
       -{
       -    bzero((char *) &server_address, sizeof(server_address));
       -    server_address.sin_family = AF_INET;
       -    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
       -    server_address.sin_port = htons((unsigned short)PORT);
       -    return server_address;
       -}
       +    // Detach thread if created succesfully
       +    // TODO: consider using pthread_tryjoin_np
       +    pthread_detach(id);
        
       -#if (HAVE_INET6)
       -struct sockaddr_in6 set_address6(struct sockaddr_in6 server_address6)
       -{
       -    bzero((char *) &server_address6, sizeof(server_address6));
       -    server_address6.sin6_family = AF_INET6;
       -    server_address6.sin6_addr = in6addr_any;
       -    server_address6.sin6_port = htons((unsigned short)PORT);
       -    return server_address6;
        }
       -#endif
        
       -void bind_to_port(int listen_socket, struct sockaddr_in server_address)
       -{
       -    if (bind(listen_socket, (struct sockaddr *) &server_address, sizeof(server_address)) < 0) 
       -        error("while binding to port");
       -    if (listen(listen_socket, QUEUE_SIZE) < 0)
       -        error("while starting listening");
       -}
        
       -#if (HAVE_INET6)
       -void bind_to_port6(int listen_socket, struct sockaddr_in6 server_address6)
       -{
       -    if (bind(listen_socket, (struct sockaddr *) &server_address6, sizeof(server_address6)) < 0) 
       -        error("while binding to port");
       -    if (listen(listen_socket, QUEUE_SIZE) < 0)
       -        error("while starting listening");
       -}
       -#endif
       +static void *handle_connection(void *args) {
        
       -void generate_url(char *buffer, char *slug, size_t slug_length, struct client_data data)
       -{
       -    int i;
       -    memset(slug, '\0', slug_length);
       +    // 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 ) {
        
       -    for (i = 0; i <= SLUG_SIZE - 1; i++)
       +        // Couldn't resolve a hostname
       +        strcpy(hostname, "n/a");
       +    }
       +
       +    // Print status on this connection
            {
       -#if defined(BSD)
       -        int symbol_id = arc4random() % strlen(symbols);
       -#else
       -        int symbol_id = rand_r(&time_seed) % strlen(symbols);
       -#endif
       -        slug[i] = symbols[symbol_id];
       +        char *date = get_date();
       +        print_status("%s", date);
       +        free(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);
       +
       +        // 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);
       +
       +    if ( save_to_file(buffer, c->settings->output_dir_path, slug) != 0 ) {
       +        print_error("Couldn't save a file!");
       +        print_separator();
       +
       +        // Cleanup
       +        free(c);
       +        free(slug);
       +        close(c->socket);
       +        pthread_exit(NULL);
       +        return NULL;
            }
        
       -    while (create_directory(slug) == -1)
       +    // Write a response to the user
            {
       -#if defined(BSD)
       -        int symbol_id = arc4random() % strlen(symbols);
       -#else
       -        int symbol_id = rand_r(&time_seed) % strlen(symbols);
       -#endif
       -        slug[strlen(slug)] = symbols[symbol_id];
       +        // 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);
            }
        
       -    save_to_file(slug, buffer, data);
       -}
       +    print_status("Received %d bytes, saved to: %s.", r, slug);
       +    print_separator();
        
       -int create_directory(char *slug)
       -{
       -    char *directory = malloc(strlen(BASEDIR) + strlen(slug) + sizeof(char) + 1);
       +    // Log connection
       +    // TODO: log unsuccessful and rejected connections
       +    log_entry(c->settings, ip, hostname, slug);
        
       -    snprintf(directory, strlen(BASEDIR) + strlen(slug) + sizeof(char) + 1, "%s%s%s", BASEDIR, "/", slug);
       +    // Close the connection
       +    close(c->socket);
        
       -    mkdir(BASEDIR, S_IRWXU | S_IRGRP | S_IROTH | S_IXOTH | S_IXGRP);
       -    int result = mkdir(directory, S_IRWXU | S_IRGRP | S_IROTH | S_IXOTH | S_IXGRP);
       +    // Perform cleanup of values used in this thread
       +    free(slug);
       +    free(c);
        
       -    free(directory);
       +    pthread_exit(NULL);
        
       -    return result;
       +    return NULL;
        }
        
       -void save_to_file(char *slug, char *buffer, struct client_data data)
       -{
       -    char *directory = malloc(strlen(BASEDIR) + strlen(slug) + 11 * sizeof(char) + 1 );
        
       -    snprintf(directory, strlen(BASEDIR) + strlen(slug) + 11 * sizeof(char) + 1, "%s%s%s%s", BASEDIR , "/", slug, "/index.txt");
       +static void generate_slug(char **output, uint8_t length, uint8_t extra_length) {
        
       -    FILE *fp;
       -    fp = fopen(directory, "w");
       -    fprintf(fp, "%s", buffer);
       -    fclose(fp);
       +    // 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.
        
       -    display_info(data, directory, "");
       +    if (extra_length > 0) {
       +        free(*output);
       +    }
        
       -    free(directory);
       -}
       +    // Create a buffer for slug with extra_length if any
       +    *output = calloc(length + 1 + extra_length, sizeof(char));
        
       -void set_uid_gid(char *username)
       -{
       -    struct passwd *userdata = getpwnam(username);
       -    if (userdata == NULL)
       -        error("Provided user doesn't exist");
       +    if (*output == NULL) {
       +        // TODO
       +    }
        
       -    UID = userdata->pw_uid;
       -    GID = userdata->pw_gid;
       -}
       +    // 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];
       +    }
        
       -int check_protocol(char *buffer)
       -{
       -    if (strlen(buffer) < 3)
       -        return 1;
       -    if ((strncmp(buffer, "GET", 3) == 0)||(strncmp(buffer, "POST", 4) == 0))
       -        if (strstr(buffer, "HTTP/1."))
       -            return 1;
       -    return 0;
        }
        
       -void set_basedir()
       -{
       -    BASEDIR = getenv("HOME");
       -    strncat(BASEDIR, "/code", 5 * sizeof(char));
       -}
        
       -void startup_message()
       -{
       -    if (DAEMON)
       -        return;
       +static int create_directory(char *output_dir, char *slug) {
       +    // Additional byte is for the slash
       +    size_t len = strlen(output_dir) + strlen(slug) + 2;
        
       -    printf("====================================\n");
       -    printf("Domain name: %s\n", DOMAIN);
       -    printf("Saving files to: %s\n", BASEDIR);
       -    printf("Fiche started listening on port %d.\n", PORT);
       -    printf("Buffer size set to: %d.\n", BUFSIZE);
       -    printf("Slug size set to: %d.\n", SLUG_SIZE);
       -    printf("Log file: %s\n", LOG);
       -    printf("====================================\n");
       -}
       +    // Generate a path
       +    char *path = malloc(len);
       +    snprintf(path, len, "%s%s%s", output_dir, "/", slug);
        
       -void error(char *buffer)
       -{
       -    printf("Error: %s\n", buffer);
       -    exit(1);
       -}
       +    // 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
       +    );
        
       -void set_domain_name() {
       -    char b[128];
       -    memcpy(b, DOMAIN, sizeof DOMAIN);
       +    free(path);
        
       -    if (HTTPS)
       -        snprintf(DOMAIN, sizeof DOMAIN, "%s%s", "https://", b);
       -    else
       -        snprintf(DOMAIN, sizeof DOMAIN, "%s%s", "http://", b);
       +    return r;
        }
        
       -void parse_parameters(int argc, char **argv)
       -{
       -    int c;
       -
       -    while ((c = getopt (argc, argv, "D6eSp:b:s:d:o:l:B:u:w:")) != -1)
       -        switch (c)
       -        {
       -            case 'D':
       -                DAEMON = 1;
       -                break;
       -            case '6':
       -                IPv6 = 1;
       -                break;
       -            case 'e':
       -                snprintf(symbols, sizeof symbols, "%s", "abcdefghijklmnopqrstuvwxyz0123456789-+_=.ABCDEFGHIJKLMNOPQRSTUVWXYZ");
       -                break;
       -            case 'S':
       -                HTTPS = 1;
       -                break;
       -            case 'd':
       -                snprintf(DOMAIN, sizeof DOMAIN, "%s%s", optarg, "/");
       -                break;
       -            case 'p':
       -                PORT = atoi(optarg);
       -                break;
       -            case 'B':
       -                BUFSIZE = atoi(optarg);
       -                break;
       -            case 'b':
       -                BANFILE = optarg;
       -                load_list(BANFILE, 0);
       -                break;
       -            case 's':
       -                SLUG_SIZE = atoi(optarg);
       -                break;
       -            case 'o':
       -                BASEDIR = optarg;
       -                break;
       -            case 'l':
       -                LOG = optarg;
       -                break;
       -            case 'u':
       -                set_uid_gid(optarg);
       -                break;
       -            case 'w':
       -                WHITEFILE = optarg;
       -                load_list(WHITEFILE, 1);
       -                break;
       -            default:
       -                printf("usage: fiche [-D6epbsdSolBuw].\n");
       -                printf("                     [-d domain] [-p port] [-s slug_size]\n");
       -                printf("                     [-o output directory] [-B buffer_size] [-u user name]\n");
       -                printf("                     [-l log file] [-b banlist] [-w whitelist]\n");
       -                exit(1);
       -        }
       +
       +static int save_to_file(uint8_t *data, char *output_dir, char *slug) {
       +    char *file_name = "index.txt";
       +
       +    // Additional 2 bytes are for 2 slashes
       +    size_t len = strlen(output_dir) + strlen(slug) + strlen(file_name) + 3;
       +
       +    // Generate a path
       +    char *path = malloc(len);
       +    snprintf(path, len, "%s%s%s%s%s", output_dir, "/", slug, "/", file_name);
       +
       +    // Attempt file saving
       +    FILE *f = fopen(path, "w");
       +    if (!f) {
       +        return -1;
       +    }
       +
       +    if ( fprintf(f, "%s", data) < 0 ) {
       +        return -1;
       +    }
       +
       +    fclose(f);
       +    free(path);
       +
       +    return 0;
        }
 (DIR) diff --git a/fiche.h b/fiche.h
       @@ -5,7 +5,7 @@ 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://code.solusipse.net/
       +Live example: http://termbin.com
        
        -------------------------------------------------------------------------------
        
       @@ -14,15 +14,7 @@ usage: fiche [-DepbsdolBuw].
                     [-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
        
        -------------------------------------------------------------------------------
       @@ -31,91 +23,86 @@ $ cat fiche.c | nc localhost 9999
        #ifndef FICHE_H
        #define FICHE_H
        
       -#ifndef HAVE_INET6
       -#define HAVE_INET6 1
       -#endif
       +#include <stdint.h>
        
       -#include <pwd.h>
       -#include <time.h>
       -#include <netdb.h>
       -#include <stdio.h>
       -#include <unistd.h>
       -#include <stdlib.h>
       -#include <string.h>
       -#include <pthread.h>
       -#include <sys/stat.h>
       -#include <sys/types.h>
       -#include <arpa/inet.h>
       -#include <sys/socket.h>
       -#include <netinet/in.h>
       -
       -int UID = -1;
       -int GID = -1;
       -char *LOG;
       -char *BASEDIR;
       -char *BANLIST;
       -char *BANFILE;
       -char *WHITEFILE;
       -char *WHITELIST;
       -int DAEMON = 0;
       -int HTTPS = 0;
       -int PORT = 9999;
       -int IPv6 = 0;
       -int SLUG_SIZE = 4;
       -int BUFSIZE = 32768;
       -int QUEUE_SIZE = 500;
       -char DOMAIN[128] = "localhost/";
       -char symbols[67] = "abcdefghijklmnopqrstuvwxyz0123456789";
       -
       -unsigned int time_seed;
       -
       -struct thread_arguments
       -{
       -        int connection_socket;
       -        struct sockaddr_in client_address;
       -#if (HAVE_INET6)
       -        struct sockaddr_in6 client_address6;
       -#endif
       -};
        
       -struct client_data
       -{
       -        char *ip_address;
       -        char *hostname;
       -};
       +/**
       + * @brief Used as a container for fiche settings. Create before
       + *        the initialization
       + *
       + */
       +typedef struct Fiche_Settings {
       +    /**
       +     * @brief Domain used in output links
       +     */
       +    char *domain;
        
       -int create_socket();
       -int create_directory(char *slug);
       -int check_protocol(char *buffer);
       +    /**
       +     * @brief Path to directory used for storing uploaded pastes
       +     */
       +    char *output_dir_path;
       +
       +    /**
       +     * @brief Port on which fiche is waiting for connections
       +     */
       +    uint16_t port;
       +
       +    /**
       +     * @brief Length of a paste's name
       +     */
       +    uint8_t slug_len;
       +
       +    /**
       +     * @brief Connection buffer length
       +     *
       +     * @remarks Length of this buffer limits max size of uploaded files
       +     */
       +    uint32_t buffer_len;
       +
       +    /**
       +     * @brief Name of the user that runs fiche process
       +     */
       +    char *user_name;
       +
       +    /**
       +     * @brief Path to the log file
       +     */
       +    char *log_file_path;
       +
       +    /**
       +     * @brief Path to the file with banned IPs
       +     */
       +    char *banlist_path;
       +
       +    /**
       +     * @brief Path to the file with whitelisted IPs
       +     */
       +    char *whitelist_path;
       +
       +
       +
       +} Fiche_Settings;
       +
       +
       +/**
       + *  @brief Initializes Fiche_Settings instance
       + */
       +void fiche_init(Fiche_Settings *settings);
       +
       +
       +/**
       + *  @brief Runs fiche server
       + *
       + *  @return 0 if it was able to start, any other value otherwise
       + */
       +int fiche_run(Fiche_Settings settings);
       +
       +
       +/**
       + * @brief array of symbols used in slug generation
       + * @remarks defined in fiche.c
       + */
       +extern const char *Fiche_Symbols;
        
       -void bind_to_port(int listen_socket, struct sockaddr_in serveraddr);
       -#if (HAVE_INET6)
       -void bind_to_port6(int listen_socket, struct sockaddr_in6 serveraddr6);
       -#endif
       -void error(char *buffer);
       -void perform_connection(int listen_socket);
       -void generate_url(char *buffer, char *slug, size_t slug_length, struct client_data data);
       -void save_to_file(char *buffer, char *slug, struct client_data data);
       -void display_info(struct client_data data, char *slug, char *message);
       -void startup_message();
       -void set_basedir();
       -void set_domain_name();
       -void load_list(char *file_path, int type);
       -void parse_parameters(int argc, char **argv);
       -void save_log(char *slug, char *hostaddrp, char *h_name);
       -void set_uid_gid();
       -
       -char *check_banlist(char *ip_address);
       -char *check_whitelist(char *ip_address);
       -char *get_date();
       -
       -struct sockaddr_in set_address(struct sockaddr_in serveraddr);
       -#if (HAVE_INET6)
       -struct sockaddr_in6 set_address6(struct sockaddr_in6 serveraddr6);
       -#endif
       -struct client_data get_client_address(struct sockaddr_in client_address);
       -#if (HAVE_INET6)
       -struct client_data get_client_address6(struct sockaddr_in6 client_address6);
       -#endif
        
        #endif
 (DIR) diff --git a/main.c b/main.c
       @@ -0,0 +1,133 @@
       +/*
       +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]
       +
       +Use netcat to push text - example:
       +$ cat fiche.c | nc localhost 9999
       +
       +-------------------------------------------------------------------------------
       +*/
       +
       +#include "fiche.h"
       +
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <unistd.h>
       +#include <getopt.h>
       +
       +
       +int main(int argc, char **argv) {
       +
       +    // Fiche settings instance
       +    Fiche_Settings fs;
       +
       +    // Initialize settings instance to default values
       +    fiche_init(&fs);
       +
       +    // Note: fiche_run is responsible for checking if these values
       +    // were set correctly
       +
       +    // Note: according to getopt documentation, we don't need to
       +    // copy strings, so we decided to go with pointer approach for these
       +
       +    // Parse input arguments
       +    int c;
       +    while ((c = getopt(argc, argv, "D6eSp:b:s:d:o:l:B:u:w:")) != -1) {
       +        switch (c) {
       +
       +            // domain
       +            case 'd':
       +            {
       +                fs.domain = optarg;
       +            }
       +            break;
       +
       +            // port
       +            case 'p':
       +            {
       +                fs.port = atoi(optarg);
       +            }
       +            break;
       +
       +            // slug size
       +            case 's':
       +            {
       +                fs.slug_len = atoi(optarg);
       +            }
       +            break;
       +
       +            // output directory path
       +            case 'o':
       +            {
       +                fs.output_dir_path = optarg;
       +            }
       +            break;
       +
       +            // buffer size
       +            case 'B':
       +            {
       +                fs.buffer_len = atoi(optarg);
       +            }
       +            break;
       +
       +            // user name
       +            case 'u':
       +            {
       +                fs.user_name = optarg;
       +            }
       +            break;
       +
       +            // log file path
       +            case 'l':
       +            {
       +                fs.log_file_path = optarg;
       +            }
       +            break;
       +
       +            // banlist file path
       +            case 'b':
       +            {
       +                fs.banlist_path = optarg;
       +            }
       +            break;
       +
       +            // whitelist file path
       +            case 'w':
       +            {
       +                fs.whitelist_path = optarg;
       +            }
       +            break;
       +
       +            // Display help in case of any unsupported argument
       +            default:
       +            {
       +                printf("usage: fiche [-dpsoBulbw].\n");
       +                printf("             [-d domain] [-p port] [-s slug size]\n");
       +                printf("             [-o output directory] [-B buffer size] [-u user name]\n");
       +                printf("             [-l log file] [-b banlist] [-w whitelist]\n");
       +                return 0;
       +            }
       +            break;
       +        }
       +    }
       +
       +
       +    fiche_run(fs);
       +
       +
       +    return 0;
       +}
       +
       +