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(<ime);
+ localtime_r(<ime, &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;
+}
+
+