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(<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_… | |
+ 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; | |
+} | |
+ | |
+ |