From c2d813c0f10dd244f124c603b156029646d4912c Mon Sep 17 00:00:00 2001 From: Solene Rapenne Date: Tue, 1 Dec 2020 23:39:05 +0100 Subject: [PATCH] Init vger --- Makefile | 10 ++ main.c | 201 +++++++++++++++++++++++++++++++++++++ tests/test.sh | 29 ++++++ tests/var/gemini/index.gmi | 7 ++ tests/var/gemini/main.gmi | 1 + 5 files changed, 248 insertions(+) create mode 100644 Makefile create mode 100644 main.c create mode 100644 tests/test.sh create mode 100644 tests/var/gemini/index.gmi create mode 100644 tests/var/gemini/main.gmi diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..631637a --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +all: vger + +clean: + rm vger + +vger: main.c + ${CC} -o vger main.c + +test: vger + cd tests && sh test.sh diff --git a/main.c b/main.c new file mode 100644 index 0000000..91c2afc --- /dev/null +++ b/main.c @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2020 Solène Rapenne + * + * MIT LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define BUFF_LEN_1 1000 +#define BUFF_LEN_2 1025 +#define BUFF_LEN_3 1024 +#define GEMINI_PART 9 +#define DEFAULT_CHROOT "/var/gemini/" + +void ok_status(void); +void err_status(void); +void display_file(char *); + +int main(int, char **); + +void +ok_status(void) +{ + printf("20 text/gemini; lang=en\r\n"); +} + +void +err_status(void) +{ + printf("40 text/gemini; lang=en\r\n"); +} + +void +display_file(char *path) +{ + size_t buflen = BUFF_LEN_1; + char *buffer[BUFF_LEN_1]; + ssize_t nread; + struct stat sb; + + // this is to check if path is a directory + stat(path, &sb); + + FILE *fd = fopen(path, "r"); + + if (fd != NULL && S_ISDIR(sb.st_mode) != 1) { + + // check if directory + ok_status(); + + /* read the file and write it to stdout */ + while ((nread = fread(buffer, sizeof(char), buflen, fd)) != 0) + fwrite(buffer, sizeof(char), nread, stdout); + fclose(fd); + } else { + err_status(); + /* + * fprintf(stderr, "can't open %s %ld: %s\n", path,strlen(path), + * strerror(errno)); + */ + } + +} + +int +main(int argc, char **argv) +{ + char buffer[BUFF_LEN_2]; + char request[BUFF_LEN_2]; + char hostname[BUFF_LEN_2]; + char file[BUFF_LEN_2]; + char path[BUFF_LEN_2] = ""; + int option; + char *pos; + + while((option = getopt(argc, argv, ":d:")) != -1) + { + switch(option) + { + case 'd': + strlcpy(path, optarg, sizeof(path)); + break; + } + } + if(strlen(path) == 0) + strlcpy(path, DEFAULT_CHROOT, sizeof(DEFAULT_CHROOT)); + + if (unveil(path, "r") == -1) + err(1, "unveil"); + + if (pledge("stdio rpath", NULL) == -1) + err(1, "pledge"); + + /* + * read 1024 chars from stdin + * to get the request + */ + fgets(request, BUFF_LEN_3, stdin); + + /* remove \r\n at the end of string */ + pos = strchr(request, '\r'); + if(pos != NULL) + strlcpy(pos, "\0", 1); + + /* + * check if the beginning of the request starts with + * gemini:// + */ + int start_with_gemini = strncmp(request, "gemini://", 9); + + /* the request must start with gemini:// */ + if(start_with_gemini != 0) { + /* error code url malformed */ + printf("request «%s» doesn't match gemini:// at index %i", + request, start_with_gemini); + exit(1); + } + + /* remove the gemini:// part */ + strlcpy(buffer, request + GEMINI_PART, sizeof(buffer) - GEMINI_PART); + strlcpy(request, buffer, sizeof(request)); + + /* + * look for the first / after the hostname + * in order to split hostname and uri + */ + pos = strchr(request, '/'); + + if (pos != NULL) { + /* if there is a / found */ + int position = -1; + for (int i = 0; i < sizeof(request); i++) { + if (*pos == request[i]) { + position = i; + break; + } + } + + /* separate hostname and uri */ + if (position != -1) { + strlcpy(hostname, request, position); + strlcpy(file, request + position + 1, sizeof(request)); + + /* use a default file if no file are requested + * this can happen in two cases + * gemini://hostname/ + * gemini://hostname/directory/ + */ + if(strlen(file) == 0) + strlcpy(file, "/index.gmi", 11); + if(file[strlen(file)-1] == '/') + strlcat(file, "index.gmi", sizeof(file)); + + } else { + puts("error undefined"); + exit(2); + } + } else { + /* + * there are no slash / in the request + * -2 to remove \r\n + */ + strlcpy(hostname, request, sizeof(hostname)); + strlcpy(file, "/index.gmi", 11); + } + + /* add the base dir to the file requested */ + strlcat(path, file, sizeof(path)); + + /* open file and send it to stdout */ + display_file(path); + + return(0); +} diff --git a/tests/test.sh b/tests/test.sh new file mode 100644 index 0000000..695a7dc --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +set -x + +# serving a file +OUT=$(printf "gemini://host.name/main.gmi\r\n" | ../vger -d var/gemini/ | tee /dev/stderr | md5) +if ! [ $OUT = "d11e0c0ff074f5627f2d2af72fd07104" ] ; then echo "error" ; exit 1 ; fi + +# default index.gmi file +OUT=$(printf "gemini://host.name\r\n" | ../vger -d var/gemini/ | tee /dev/stderr | md5) +if ! [ $OUT = "3edd48286850d386592403956aec770f" ] ; then echo "error" ; exit 1 ; fi + +# default index.gmi file when using a trailing slash +OUT=$(printf "gemini://host.name/\r\n" | ../vger -d var/gemini/ | tee /dev/stderr | md5) +if ! [ $OUT = "3edd48286850d386592403956aec770f" ] ; then echo "error" ; exit 1 ; fi + +# file from /var/gemini/index.md +OUT=$(printf "gemini://host.name/index.md\r\n" | ../vger | tee /dev/stderr | md5) +if ! [ $OUT = "bdbb22f0d1f4dd9e31bfc91686e7441d" ] ; then echo "error" ; exit 1 ; fi + +#### no -d parameter from here + +# file from /var/gemini/blog/ +OUT=$(printf "gemini://host.name/blog/\r\n" | ../vger | tee /dev/stderr | md5) +if ! [ $OUT = "83bd01c9af0e44d5439b9ac95dc28132" ] ; then echo "error" ; exit 1 ; fi + +# file from /var/gemini/blog +OUT=$(printf "gemini://host.name/blog\r\n" | ../vger | tee /dev/stderr | md5) +if ! [ $OUT = "f78c481e1614f1713e077b89aba5ab94" ] ; then echo "error" ; exit 1 ; fi diff --git a/tests/var/gemini/index.gmi b/tests/var/gemini/index.gmi new file mode 100644 index 0000000..a6bb302 --- /dev/null +++ b/tests/var/gemini/index.gmi @@ -0,0 +1,7 @@ +this is + +the INDEX + +On multiples lines + +with accents @^~#@¹#^¹~#@^¹\~{@^¹~@^{ diff --git a/tests/var/gemini/main.gmi b/tests/var/gemini/main.gmi new file mode 100644 index 0000000..ce01362 --- /dev/null +++ b/tests/var/gemini/main.gmi @@ -0,0 +1 @@ +hello