Introduction
Introduction Statistics Contact Development Disclaimer Help
Entirely rewritten fiche - fiche - A pastebin adjusted for gopher use
git clone git://vernunftzentrum.de/fiche.git
Log
Files
Refs
LICENSE
---
commit 8ff08a04093e51dfdbde0b6a4f8f3e58dcbe43be
parent c054f8dc20c193c6354fe7e636255758b06fc6f7
Author: solusipse <[email protected]>
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(-)
---
diff --git a/.gitignore b/.gitignore
@@ -1,2 +1,8 @@
# ignore binaries
/fiche
+
+# ignore default outpit dir
+code/
+
+# ignore log files
+*.log
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-al…
prefix=/usr/local
-all: fiche
+all:
+ ${CC} main.c fiche.c $(CFLAGS) -o fiche
install: fiche
install -m 0755 fiche $(prefix)/bin
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 …
+/**
+ * @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_…
- 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_addres…
- 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_…
+ 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_…
- }
-
- struct timeval timeout;
- timeout.tv_sec = 5;
- timeout.tv_usec = 0;
-
- if (setsockopt (connection_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeo…
- error("while setting setsockopt timeout");
- if (setsockopt (connection_socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeo…
- 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, sizeo…
- 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(cli…
- 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_n…
}
- else
- data.ip_address = hostaddrp;
- return data;
+ if (setuid(uid) != 0) {
+ print_error("Couldn't switch to requested user: %s!", settings->user_n…
+ }
+
+ 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,…
- else
- snprintf(contents, sizeof contents, "%s -- %s -- %s (%s)\n", "rej"…
+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)) !=…
+ 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)) != …
+ 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)) != …
+ 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…
- 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(serve…
- 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_…
-{
- 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…
+ // 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_IX…
+ // 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…
- snprintf(directory, strlen(BASEDIR) + strlen(slug) + 11 * sizeof(char) + 1…
+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", "abcdefghijklmnopqrstu…
- 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_si…
- printf(" [-o output directory] [-B buffer_…
- printf(" [-l log file] [-b banlist] [-w wh…
- 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;
}
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_…
-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
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] [-…
+ printf(" [-l log file] [-b banlist] [-w whitelist]…
+ return 0;
+ }
+ break;
+ }
+ }
+
+
+ fiche_run(fs);
+
+
+ return 0;
+}
+
+
You are viewing proxied material from vernunftzentrum.de. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.