diff --git a/README.md b/README.md index b96821c..7d6fc1a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # A simplistic and secure Gemini server -**Vger** is a gemini server supporting virtualhosts, default language -choice and MIME types detection. +**Vger** is a gemini server supporting chroot, virtualhosts, default +language choice and MIME types detection. **Vger** design is relying on inetd and a daemon to take care of TLS. The idea is to delegate TLS and network to daemons which @@ -43,9 +43,10 @@ without a `-d` parameter. **Vger** has a few parameters you can use in inetd configuration. -- `-d PATH`: use `PATH` to look for files. Default is `/var/gemini` +- `-d PATH`: use `PATH` as the data directory to serve files from. Default is `/var/gemini` - `-l LANG`: change the language in the status return code. Default is `en` - `-v`: enable virtualhost support, the hostname in the query will be considered as a directory name. +- `-u username`: enable chroot to the data directory and drop privileges to `username`. # How to configure Vger using relayd and inetd diff --git a/main.c b/main.c index 59aa2a1..45a241d 100644 --- a/main.c +++ b/main.c @@ -1,10 +1,11 @@ -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include #include "mimes.c" #define BUFF_LEN_1 1000 @@ -92,12 +93,14 @@ main(int argc, char **argv) char file [BUFF_LEN_2]; char path [BUFF_LEN_2] = DEFAULT_CHROOT; char lang [3] = DEFAULT_LANG; + char user [_SC_LOGIN_NAME_MAX]; + struct passwd *pw; int virtualhost = 0; int option; int start_with_gemini; char *pos; - while ((option = getopt(argc, argv, ":d:l:v")) != -1) { + while ((option = getopt(argc, argv, ":d:l:u:v")) != -1) { switch (option) { case 'd': strlcpy(path, optarg, sizeof(path)); @@ -108,10 +111,33 @@ main(int argc, char **argv) case 'l': strlcpy(lang, optarg, sizeof(lang)); break; + case 'u': + strlcpy(user, optarg, sizeof(user)); + break; } } + /* + * use chroot() if an user is specified requires root user to be + * running the program to run chroot() and then drop privileges + */ + if (strlen(user) > 0) { + /* is root? */ + if (getuid() != 0) + err(1, "chroot requires root user"); + /* search user uid from name */ + if ((pw = getpwnam(user)) == NULL) + err(1, "finding user"); + + /* chroot worked? */ + if (chroot(path) != 0) + err(1, "chroot"); + + /* drop privileges */ + if (setuid(pw->pw_uid) != 0) + err(1, "Can't drop privileges"); + } #ifdef __OpenBSD__ /* * prevent access to files other than the one in path diff --git a/tests/test.sh b/tests/test.sh index 1d07b15..94fa946 100644 --- a/tests/test.sh +++ b/tests/test.sh @@ -42,6 +42,13 @@ if ! [ $OUT = "0d36a423a4e8be813fda4022f08b3844" ] ; then echo "error" ; exit 1 OUT=$(printf "gemini://perso.pw\r\n" | ../vger -v -d var/gemini/ -l fr | tee /dev/stderr | $MD5) if ! [ $OUT = "7db981ce93fee268f29324912800f00d" ] ; then echo "error" ; exit 1 ; fi +type doas 2>/dev/null +if [ $? -eq 0 ]; then + # file from local directory chroot + OUT=$(printf "gemini://perso.pw\r\n" | doas ../vger -v -d var/gemini/ -u solene -l fr | tee /dev/stderr | $MD5) + if ! [ $OUT = "7db981ce93fee268f29324912800f00d" ] ; then echo "error" ; exit 1 ; fi +fi + #### no -d parameter from here if [ -d /var/gemini/ ] diff --git a/vger.8 b/vger.8 index 8512e41..7fe7752 100644 --- a/vger.8 +++ b/vger.8 @@ -9,6 +9,7 @@ .Op Fl l Ar lang .Op Fl v .Op Fl d Ar path +.Op Fl u Ar username .Sh DESCRIPTION .Nm is a secure gemini server that is meant to be run on @@ -40,6 +41,14 @@ On will use .Xr unveil 2 on this path in read-only to prevent file access outside this directory. +.It Op Fl u Ar username +Enable +.Xr chroot 2 +on the data directory and then drop privileges to +.Ar username . +This requires +.Nm +to be run as root user. .El .Sh DEPLOYMENT .Nm @@ -70,6 +79,7 @@ relay "gemini" { } .Ed .Sh SEE ALSO +.Xr chroot 2 , .Xr unveil 2 , .Xr relayd.conf 5 , .Xr inetd 8 ,