diff options
| author | Fuwn <[email protected]> | 2024-07-17 20:20:21 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2024-07-17 20:20:21 -0700 |
| commit | e5e083b260e523fef972f8697cb114e0da1f87b7 (patch) | |
| tree | e300c56b8cc2bfe01532faa8d031b15e40639a90 | |
| parent | d7a5a2724e0745776d9037410376bf767b611b4e (diff) | |
| download | gigi-e5e083b260e523fef972f8697cb114e0da1f87b7.tar.xz gigi-e5e083b260e523fef972f8697cb114e0da1f87b7.zip | |
feat: rewrite in go
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Dockerfile | 31 | ||||
| -rw-r--r-- | README.md | 19 | ||||
| -rw-r--r-- | Tupfile | 15 | ||||
| -rw-r--r-- | build.ninja | 14 | ||||
| -rw-r--r-- | gigi.c | 209 | ||||
| -rw-r--r-- | gigi.go | 120 |
7 files changed, 139 insertions, 270 deletions
@@ -13,6 +13,7 @@ # Development Artifacts build compile_commands.json +gigi # Ninja .ninja_* @@ -1,36 +1,15 @@ -FROM alpine:latest as environment - -RUN apk update \ - && apk upgrade \ - && apk add --no-cache libstdc++ - -FROM environment as build_environment - -RUN apk add --no-cache \ - clang \ - ninja \ - alpine-sdk \ - linux-headers - -FROM build_environment as builder +FROM golang:alpine as builder WORKDIR /gigi -COPY ./gigi.c ./gigi.c -COPY ./build.ninja ./build.ninja +COPY gigi.go . -RUN sed -i 's/#include <bits\/types\/FILE.h>//g' gigi.c +RUN go build -ldflags "-s -w" -o gigi gigi.go -RUN ninja - -RUN strip /gigi/build/gigi - -FROM environment +FROM alpine WORKDIR /gigi -COPY --from=builder /gigi/build/gigi ./ - -EXPOSE 79 +COPY --from=builder /gigi/gigi . ENTRYPOINT ["/gigi/gigi"] @@ -1,6 +1,6 @@ # 👧️ Gigi -> A [Finger](https://www.rfc-editor.org/info/rfc742) protocol server for risk takers +> An honest [Finger](https://www.rfc-editor.org/info/rfc742) protocol server Gigi is a Finger protocol server with few features. @@ -60,16 +60,18 @@ the profile files. The named volume is persistent, and can be found at Docker also significantly reduces the risk of running Gigi, as it is sandboxed from the host system. In static mode, there is little to no risk, but in dynamic -mode, there is a significant risk for arbitrary code execution. +mode, there is a small risk for arbitrary code execution depending on your +`.gigi/do` file. ### Configuration Gigi is configured through the `./.gigi` directory. -Dynamic response mode is disabled by default in [`gigi.c`](./gigi.c) -because it is very unsafe. If you wish to live on the edge, uncomment the -`GIGI_DYNAMIC` macro. Dropping Gigi into a container is significantly safer -than running it on a host machine, so consider that as an option, too. +Dynamic response mode is disabled by default as dynamic code execution can be a +big security risk. If you wish to live on the edge, pass the `GIGI_DYNAMIC` +environment variable with a value greater than `1` to Gigi. Dropping Gigi into a +container is significantly safer than running it on a host machine, so consider +that as an option, too. Dynamic mode runs any and all executables located at the path `./.gigi/do`, and passes any arguments from the Finger request to the executable. @@ -83,6 +85,11 @@ To emulate dynamic mode, minus the support for arguments, you can setup a service of some kind to periodically update the contents of one of the static files. +You can additionally modify the `GIGI_PORT` environment variable to change the +port Gigi listens on. The default port is 79. If you're running Gigi in a +Docker container, you can ignore this variable and map any ports using Docker +directly. + ## Licence This project is licensed with the [GNU General Public License v3.0](./LICENSE). diff --git a/Tupfile b/Tupfile deleted file mode 100644 index 7812aaa..0000000 --- a/Tupfile +++ /dev/null @@ -1,15 +0,0 @@ -# Output Directory -BUILD_DIRECTORY = build - -# Compiler Configuration -CC = clang -CC_FLAGS = -std=c89 -Weverything -MMD -Wno-unsafe-buffer-usage - -# Clang-tidy Configuration -CLANG_TIDY_CHECKS = '-*,bugprone-*,clang-analyzer-*,concurrency-*,cppcoreguildelines-*,llvm-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-bugprone-reserved-identifier,-misc-include-cleaner' -CLANG_TIDY_FLAGS = -checks=$(CLANG_TIDY_CHECKS) -warnings-as-errors=* -quiet - -: foreach ./*.c |> clang-format -i %f |> -: foreach ./*.c |> clang-tidy $(CLANG_TIDY_FLAGS) %f -- $(CC_FLAGS) |> -: foreach ./*.c |> ^j^ $(CC) $(CC_FLAGS) -MF $(BUILD_DIRECTORY)/%B.d -c %f -o %o |> $(BUILD_DIRECTORY)/%B.o | $(BUILD_DIRECTORY)/%B.d -: $(BUILD_DIRECTORY)/*.o |> $(CC) %f -o %o |> $(BUILD_DIRECTORY)/gigi diff --git a/build.ninja b/build.ninja deleted file mode 100644 index a5447e6..0000000 --- a/build.ninja +++ /dev/null @@ -1,14 +0,0 @@ -outdir = build -cc = clang -name = gigi - -rule compile - command = $cc -std=c89 -c $in -o $out - -rule link - command = $cc $in -o $out - -build $outdir/$name.o: compile ./$name.c -build $outdir/$name: link $outdir/$name.o - -default $outdir/$name @@ -1,209 +0,0 @@ -#define _GNU_SOURCE -/* #define GIGI_DYNAMIC */ - -#include <asm-generic/socket.h> -#include <bits/types/FILE.h> -#include <netinet/in.h> -#include <stddef.h> -#include <stdio.h> -#include <string.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -enum { - MAXIMUM_COMMAND_LENGTH = 256, - MAXIMUM_DATA_SIZE = 512, - FINGER_PORT = 79, - MAXIMUM_CONNECTIONS = 5 -}; - -#ifdef GIGI_DYNAMIC -/* This is a proof of concept. Please do not actually use `gigi_do`, as it is - * clearly susceptible to command injection. */ -void gigi_do(int connection_file_descriptor, const char *arguments); -#else -void gigi_show(int connection_file_descriptor, const char *arguments); -#endif - -int main(void) { - int server_socket_file_descriptor; - int client_socket_file_descriptor; - struct sockaddr_in server_address; - struct sockaddr_in client_address; - char client_message_buffer[MAXIMUM_COMMAND_LENGTH]; - socklen_t client_address_length; - int reuse_address_option = 1; - ssize_t client_socket_bytes_read; - - server_socket_file_descriptor = socket(AF_INET, SOCK_STREAM, 0); - - if (server_socket_file_descriptor < 0) { - perror("error: could not create server socket\n"); - - return 1; - } - - memset(&server_address, 0, sizeof(server_address)); - - server_address.sin_family = AF_INET; - server_address.sin_addr.s_addr = htonl(INADDR_ANY); - server_address.sin_port = htons(FINGER_PORT); - - if (setsockopt(server_socket_file_descriptor, SOL_SOCKET, SO_REUSEADDR, - &reuse_address_option, sizeof(reuse_address_option)) < 0) { - perror("error: could not set server socket option\n"); - - return 1; - } - - if (bind(server_socket_file_descriptor, (struct sockaddr *)&server_address, - sizeof(server_address)) < 0) { - perror("error: could not bind server socket\n"); - - return 1; - } - - if (listen(server_socket_file_descriptor, MAXIMUM_CONNECTIONS) < 0) { - perror("error: could not listen on server socket\n"); - - return 1; - } - - client_address_length = sizeof(client_address); - - for (;;) { - client_socket_file_descriptor = - accept(server_socket_file_descriptor, - (struct sockaddr *)&client_address, &client_address_length); - - if (client_socket_file_descriptor < 0) { - perror("error: could not accept on client socket\n"); - - return 1; - } - - memset(client_message_buffer, 0, sizeof(client_message_buffer)); - - client_socket_bytes_read = - read(client_socket_file_descriptor, client_message_buffer, - sizeof(client_message_buffer) - 1); - - if (client_socket_bytes_read < 0) { - perror("error: could not read from client socket\n"); - - return 1; - } - - client_message_buffer[client_socket_bytes_read] = '\0'; - -#ifdef GIGI_DYNAMIC - gigi_do(client_socket_file_descriptor, client_message_buffer); -#else - gigi_show(client_socket_file_descriptor, client_message_buffer); -#endif - - if (close(client_socket_file_descriptor) < 0) { - perror("error: could not close client socket\n"); - - return 1; - } - } - - /* if (close(server_socket_file_descriptor) < 0) { - perror("error: could not close server socket\n"); - - return 1; - } - - return 0; */ -} - -#ifdef GIGI_DYNAMIC -void gigi_do(int connection_file_descriptor, const char *arguments) { - FILE *gigi_do_file; - char gigi_do_command[MAXIMUM_COMMAND_LENGTH]; - char gigi_do_message[MAXIMUM_DATA_SIZE]; - - snprintf(gigi_do_command, MAXIMUM_COMMAND_LENGTH, "./.gigi/do %s", arguments); - - gigi_do_file = (FILE *)popen(gigi_do_command, "r"); - - if (!gigi_do_file) { - perror("error: could not open gigi do file pipe\n"); - return; - } - - while (fgets(gigi_do_message, MAXIMUM_DATA_SIZE, gigi_do_file) != NULL) { - if (write(connection_file_descriptor, gigi_do_message, - strlen(gigi_do_message)) < 0) { - perror("error: could not write to client socket\n"); - } - } - - if (pclose(gigi_do_file) != 0) { - perror("error: could not close gigi do file pipe\n"); - } -} -#endif - -#ifndef GIGI_DYNAMIC -void gigi_show(int connection_file_descriptor, const char *arguments) { - FILE *gigi_show_file; - char gigi_show_message[MAXIMUM_DATA_SIZE]; - - gigi_show_file = fopen("./.gigi/show", "r"); - - if (arguments[0] != '\0') { - char gigi_show_command[MAXIMUM_COMMAND_LENGTH]; - char gigi_show_arguments[MAXIMUM_COMMAND_LENGTH]; - size_t gigi_show_arguments_index; - - snprintf(gigi_show_arguments, MAXIMUM_COMMAND_LENGTH, "%s", arguments); - - for (gigi_show_arguments_index = 0; - gigi_show_arguments_index < strlen(gigi_show_arguments); - ++gigi_show_arguments_index) { - if (gigi_show_arguments[gigi_show_arguments_index] == '\n' || - gigi_show_arguments[gigi_show_arguments_index] == '\r') { - gigi_show_arguments[gigi_show_arguments_index] = '\0'; - } - } - - if (gigi_show_arguments[0] == '\0') { - snprintf(gigi_show_arguments, MAXIMUM_COMMAND_LENGTH, "default"); - } - - snprintf(gigi_show_command, MAXIMUM_COMMAND_LENGTH, "./.gigi/%s", - gigi_show_arguments); - - gigi_show_file = fopen(gigi_show_command, "r"); - - if (!gigi_show_file) { - fprintf(stderr, "error: could not open gigi show file: %s\n", - gigi_show_command); - - gigi_show_file = fopen("./.gigi/default", "r"); - } - - if (!gigi_show_file) { - perror("error: could not open default gigi show file: default\n"); - - return; - } - - printf("ok: %s\n", gigi_show_command); - } - - while (fgets(gigi_show_message, MAXIMUM_DATA_SIZE, gigi_show_file) != NULL) { - if (write(connection_file_descriptor, gigi_show_message, - strlen(gigi_show_message)) < 0) { - perror("error: could not write to client socket\n"); - } - } - - if (fclose(gigi_show_file) != 0) { - perror("error: could not close gigi show file\n"); - } -} -#endif @@ -0,0 +1,120 @@ +package main + +import ( + "fmt" + "log" + "net" + "os" + "os/exec" + "strconv" + "strings" +) + +const ( + maximumCommandLength = 256 + modeStatic = 0 + modeDynamic = 1 +) + +func main() { + fingerPortOption := os.Getenv("GIGI_PORT") + modeOption := os.Getenv("GIGI_DYNAMIC") + mode := modeStatic + + if fingerPortOption == "" { + fingerPortOption = "79" + } + + if modeOption != "" { + if mode, _ = strconv.Atoi(modeOption); mode > 0 { + mode = modeDynamic + } + } + + listener, listenerError := net.Listen("tcp", fmt.Sprintf(":%s", fingerPortOption)) + + if listenerError != nil { + log.Fatalf("error: %s\n", listenerError.Error()) + } + + for { + connection, connectionError := listener.Accept() + + if connectionError != nil { + log.Println("warn: listener could not accept connection") + } + + go handleConnection(connection, mode) + } +} + +func handleConnection(connection net.Conn, mode int) { + defer connection.Close() + + connectionReadBuffer := make([]byte, maximumCommandLength) + _, readError := connection.Read(connectionReadBuffer) + + if readError != nil { + log.Println("warn: could not read from connection") + + return + } + + bufferContent := strings.Replace( + strings.Replace( + strings.Replace(string(connectionReadBuffer), "\x00", "", -1), + "\n", "", -1), "\r", "", -1) + + if len(bufferContent) == 0 { + bufferContent = "default" + } + + var fileContent string + var fileReadError error + + switch mode { + case modeDynamic: + fileContent, fileReadError = runFile(bufferContent) + + default: + fileContent, fileReadError = readFile(bufferContent) + } + + if fileReadError != nil { + log.Printf("warn: could not read from file: %s", bufferContent) + + return + } + + connection.Write([]byte(fileContent)) + log.Printf("info: success: %s", bufferContent) +} + +func readFile(filename string) (string, error) { + fileContent, fileReadError := os.ReadFile(fmt.Sprintf("./.gigi/%s", filename)) + + if fileReadError != nil { + fileContent, fileReadError = os.ReadFile("./.gigi/default") + + if fileReadError != nil { + log.Printf("error: could not read from file: %s\n", filename) + + return "", fileReadError + } + } + + return string(fileContent), nil +} + +func runFile(arguments string) (string, error) { + command := exec.Command("./.gigi/do", arguments) + commandOutput, commandError := command.Output() + + if commandError != nil { + log.Printf("error: could not run command: %s\n", commandError) + + return "", commandError + } + + return string(commandOutput), nil +} |