aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-07-17 20:20:21 -0700
committerFuwn <[email protected]>2024-07-17 20:20:21 -0700
commite5e083b260e523fef972f8697cb114e0da1f87b7 (patch)
treee300c56b8cc2bfe01532faa8d031b15e40639a90
parentd7a5a2724e0745776d9037410376bf767b611b4e (diff)
downloadgigi-e5e083b260e523fef972f8697cb114e0da1f87b7.tar.xz
gigi-e5e083b260e523fef972f8697cb114e0da1f87b7.zip
feat: rewrite in go
-rw-r--r--.gitignore1
-rw-r--r--Dockerfile31
-rw-r--r--README.md19
-rw-r--r--Tupfile15
-rw-r--r--build.ninja14
-rw-r--r--gigi.c209
-rw-r--r--gigi.go120
7 files changed, 139 insertions, 270 deletions
diff --git a/.gitignore b/.gitignore
index 2062f6a..50e9ba7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@
# Development Artifacts
build
compile_commands.json
+gigi
# Ninja
.ninja_*
diff --git a/Dockerfile b/Dockerfile
index 60dde91..8ca5b8e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -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"]
diff --git a/README.md b/README.md
index fa2bfd9..39047a8 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/gigi.c b/gigi.c
deleted file mode 100644
index 5100706..0000000
--- a/gigi.c
+++ /dev/null
@@ -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
diff --git a/gigi.go b/gigi.go
new file mode 100644
index 0000000..e127b7d
--- /dev/null
+++ b/gigi.go
@@ -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
+}