From 9cca2408c3e4e4b3edea97aa67bb53d8764491bb Mon Sep 17 00:00:00 2001 From: Solene Rapenne Date: Mon, 4 Jul 2022 21:00:31 +0200 Subject: [PATCH] Separate vger functions into a new file This will ease the creation of unit tests and code reusability --- Makefile | 2 +- main.c | 318 +------------------------------------------------------ vger.c | 297 +++++++++++++++++++++++++++++++++++++++++++++++++++ vger.h | 27 +++++ 4 files changed, 326 insertions(+), 318 deletions(-) create mode 100644 vger.c create mode 100644 vger.h diff --git a/Makefile b/Makefile index 8013f37..96079ab 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ clean: -name "*.core" \) \ -delete -vger: main.o mimes.o utils.o opts.h +vger: vger.o main.o mimes.o utils.o opts.h ${CC} ${CFLAGS} -o $@ main.o mimes.o utils.o install: vger diff --git a/main.c b/main.c index ce9c7da..5860649 100644 --- a/main.c +++ b/main.c @@ -1,320 +1,4 @@ -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mimes.h" -#include "opts.h" -#include "utils.h" - -/* length of "gemini://" */ -#define GEMINI_PART 9 - -/* - * number of bytes to read with fgets() : 2014 + 1. - * fgets() reads at most size-1 (1024 here). - * See https://gemini.circumlunar.space/docs/specification.html. - */ -#define GEMINI_REQUEST_MAX 1025 - -void autoindex(const char *); -void cgi (const char *cgicmd); -void display_file(const char *); -void drop_privileges(const char *, const char *); -void echdir (const char *); -void status (const int, const char *); -void status_redirect(const int, const char *); -void status_error(const int, const char *); -void strip_trailing_slash(char *path); -int uridecode (char *); - - -void -echdir(const char *path) -{ - if (chdir(path) == -1) { - switch (errno) { - case ENOTDIR: /* FALLTHROUGH */ - case ENOENT: - status_error(51, "file not found"); - break; - case EACCES: - status_error(50, "Forbidden path"); - break; - default: - status_error(50, "Internal server error"); - break; - } - errlog("failed to chdir(%s)", path); - } -} - -int -uridecode(char *uri) -{ - int n = 0; - char c = '\0'; - long l = 0; - char *pos = NULL; - - if ((pos = strchr(uri, '%')) == NULL) - return n; - - while ((pos = strchr(pos, '%')) != NULL) { - if (strlen(pos) < 3) - return n; - - char hex[3] = {'\0'}; - for (size_t i = 0; i < 2; i++) - hex[i] = tolower(pos[i + 1]); - - errno = 0; - l = strtol(hex, 0, 16); - if (errno == ERANGE && (l == LONG_MAX || l == LONG_MIN)) - continue; /* conversion failed */ - - c = (char)l; - pos[0] = c; - /* rewind of two char to remove %hex */ - memmove(pos + 1, pos + 3, strlen(pos + 3) + 1); /* +1 for \0 */ - n++; - pos++; /* avoid infinite loop */ - } - return n; -} - -void -drop_privileges(const char *user, const char *path) -{ - struct passwd *pw; - - /* - * 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) - errlog("chroot requires program to be run as root"); - - /* search user uid from name */ - if ((pw = getpwnam(user)) == NULL) - errlog("the user %s can't be found on the system", user); - - /* chroot worked? */ - if (chroot(path) != 0) - errlog("the chroot_dir %s can't be used for chroot", path); - - chrooted = 1; - echdir("/"); - /* drop privileges */ - if (setgroups(1, &pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) { - errlog("dropping privileges to user %s (uid=%i) failed", - user, pw->pw_uid); - } - } -#ifdef __OpenBSD__ - /* - * prevent access to files other than the one in path - */ - if (chrooted) - eunveil("/", "r"); - else - eunveil(path, "r"); - - /* permission to execute what's inside cgidir */ - if (strlen(cgidir) > 0) - eunveil(cgidir, "rx"); - - eunveil(NULL, NULL); /* no more call to unveil() */ - - /* promise permissions */ - if (strlen(cgidir) > 0) - epledge("stdio rpath exec", NULL); - else - epledge("stdio rpath", NULL); -#endif - if (!chrooted) - echdir(path); /* move to the gemini data directory */ -} - -void -status(const int code, const char *file_mime) -{ - if (strcmp(file_mime, "text/gemini") == 0) - printf("%i %s; %s\r\n", code, file_mime, lang); - else - printf("%i %s\r\n", code, file_mime); -} - -void -status_redirect(const int code, const char *url) -{ - printf("%i %s\r\n", - code, url); -} - -void -status_error(const int code, const char *reason) -{ - printf("%i %s\r\n", - code, reason); -} - -void -display_file(const char *fname) -{ - FILE *fd = NULL; - struct stat sb = {0}; - ssize_t nread = 0; - const char *file_mime; - char *buffer[BUFSIZ]; - char target[FILENAME_MAX] = {'\0'}; - char tmp[PATH_MAX] = {'\0'}; /* used to build - * temporary path */ - - /* - * special case : fname empty. The user requested just the directory - * name - */ - if (strlen(fname) == 0) { - if (stat("index.gmi", &sb) == 0) { - /* there is index.gmi in the current directory */ - display_file("index.gmi"); - return; - } else if (doautoidx) { - /* no index.gmi, so display autoindex if enabled */ - autoindex("."); - return; - } else { - goto err; - } - } - /* this is to check if path exists and obtain metadata later */ - if (stat(fname, &sb) == -1) { - /* - * check if fname is a symbolic link if so, redirect using - * its target - */ - if (lstat(fname, &sb) != -1 && S_ISLNK(sb.st_mode) == 1) - goto redirect; - else - goto err; - } - /* check if directory */ - if (S_ISDIR(sb.st_mode) != 0) { - /* no ending "/", redirect to "fname/" */ - estrlcpy(tmp, fname, sizeof(tmp)); - estrlcat(tmp, "/", sizeof(tmp)); - status_redirect(31, tmp); - return; - } - /* open the file requested */ - if ((fd = fopen(fname, "r")) == NULL) - goto err; - - file_mime = get_file_mime(fname, default_mime); - - status(20, file_mime); - - /* read the file byte after byte in buffer and write it to stdout */ - while ((nread = fread(buffer, 1, sizeof(buffer), fd)) != 0) - fwrite(buffer, 1, nread, stdout); - goto closefd; /* close file descriptor */ - syslog(LOG_DAEMON, "path served %s", fname); - - return; - -err: - /* return an error code and no content */ - status_error(51, "file not found"); - syslog(LOG_DAEMON, "path invalid %s", fname); - goto closefd; - -redirect: - /* read symbolic link target to redirect */ - if (readlink(fname, target, FILENAME_MAX) == -1) - goto err; - - status_redirect(30, target); - syslog(LOG_DAEMON, "redirection from %s to %s", fname, target); - -closefd: - if (S_ISREG(sb.st_mode) != 0) - fclose(fd); -} - -void -autoindex(const char *path) -{ - /* display liks to files in path + a link to parent (..) */ - - int n = 0; - struct dirent **namelist; /* this must be freed at last */ - - syslog(LOG_DAEMON, "autoindex: %s", path); - - /* use alphasort to always have the same order on every system */ - if ((n = scandir(path, &namelist, NULL, alphasort)) < 0) { - status_error(50, "Internal server error"); - errlog("Can't scan %s", path); - } else { - status(20, "text/gemini"); - printf("=> .. ../\n"); /* display link to parent */ - for (int j = 0; j < n; j++) { - /* skip self and parent */ - if ((strcmp(namelist[j]->d_name, ".") == 0) || - (strcmp(namelist[j]->d_name, "..") == 0)) { - continue; - } - /* add "/" at the end of a directory path */ - if (namelist[j]->d_type == DT_DIR) - printf("=> ./%s/ %s/\n", namelist[j]->d_name, namelist[j]->d_name); - else - printf("=> ./%s %s\n", namelist[j]->d_name, namelist[j]->d_name); - free(namelist[j]); - } - free(namelist); - } -} - -void -cgi(const char *cgicmd) -{ - /* run cgicmd replacing current process */ - execl(cgicmd, cgicmd, NULL); - /* if execl is ok, this will never be reached */ - status(42, "Couldn't execute CGI script"); - errlog("error when trying to execl %s", cgicmd); - exit(1); -} - -void -strip_trailing_slash(char *path) -{ - size_t end = strlen(path); - if (end == 0) - return; - end--; - while (path[end] == '/') - path[end--] = '\0'; -} +#include "vger.c" int main(int argc, char **argv) diff --git a/vger.c b/vger.c new file mode 100644 index 0000000..6292838 --- /dev/null +++ b/vger.c @@ -0,0 +1,297 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mimes.h" +#include "opts.h" +#include "utils.h" +#include "vger.h" + + +void +echdir(const char *path) +{ + if (chdir(path) == -1) { + switch (errno) { + case ENOTDIR: /* FALLTHROUGH */ + case ENOENT: + status_error(51, "file not found"); + break; + case EACCES: + status_error(50, "Forbidden path"); + break; + default: + status_error(50, "Internal server error"); + break; + } + errlog("failed to chdir(%s)", path); + } +} + +int +uridecode(char *uri) +{ + int n = 0; + char c = '\0'; + long l = 0; + char *pos = NULL; + + if ((pos = strchr(uri, '%')) == NULL) + return n; + + while ((pos = strchr(pos, '%')) != NULL) { + if (strlen(pos) < 3) + return n; + + char hex[3] = {'\0'}; + for (size_t i = 0; i < 2; i++) + hex[i] = tolower(pos[i + 1]); + + errno = 0; + l = strtol(hex, 0, 16); + if (errno == ERANGE && (l == LONG_MAX || l == LONG_MIN)) + continue; /* conversion failed */ + + c = (char)l; + pos[0] = c; + /* rewind of two char to remove %hex */ + memmove(pos + 1, pos + 3, strlen(pos + 3) + 1); /* +1 for \0 */ + n++; + pos++; /* avoid infinite loop */ + } + return n; +} + +void +drop_privileges(const char *user, const char *path) +{ + struct passwd *pw; + + /* + * 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) + errlog("chroot requires program to be run as root"); + + /* search user uid from name */ + if ((pw = getpwnam(user)) == NULL) + errlog("the user %s can't be found on the system", user); + + /* chroot worked? */ + if (chroot(path) != 0) + errlog("the chroot_dir %s can't be used for chroot", path); + + chrooted = 1; + echdir("/"); + /* drop privileges */ + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) { + errlog("dropping privileges to user %s (uid=%i) failed", + user, pw->pw_uid); + } + } +#ifdef __OpenBSD__ + /* + * prevent access to files other than the one in path + */ + if (chrooted) + eunveil("/", "r"); + else + eunveil(path, "r"); + + /* permission to execute what's inside cgidir */ + if (strlen(cgidir) > 0) + eunveil(cgidir, "rx"); + + eunveil(NULL, NULL); /* no more call to unveil() */ + + /* promise permissions */ + if (strlen(cgidir) > 0) + epledge("stdio rpath exec", NULL); + else + epledge("stdio rpath", NULL); +#endif + if (!chrooted) + echdir(path); /* move to the gemini data directory */ +} + +void +status(const int code, const char *file_mime) +{ + if (strcmp(file_mime, "text/gemini") == 0) + printf("%i %s; %s\r\n", code, file_mime, lang); + else + printf("%i %s\r\n", code, file_mime); +} + +void +status_redirect(const int code, const char *url) +{ + printf("%i %s\r\n", + code, url); +} + +void +status_error(const int code, const char *reason) +{ + printf("%i %s\r\n", + code, reason); +} + +void +display_file(const char *fname) +{ + FILE *fd = NULL; + struct stat sb = {0}; + ssize_t nread = 0; + const char *file_mime; + char *buffer[BUFSIZ]; + char target[FILENAME_MAX] = {'\0'}; + char tmp[PATH_MAX] = {'\0'}; /* used to build + * temporary path */ + + /* + * special case : fname empty. The user requested just the directory + * name + */ + if (strlen(fname) == 0) { + if (stat("index.gmi", &sb) == 0) { + /* there is index.gmi in the current directory */ + display_file("index.gmi"); + return; + } else if (doautoidx) { + /* no index.gmi, so display autoindex if enabled */ + autoindex("."); + return; + } else { + goto err; + } + } + /* this is to check if path exists and obtain metadata later */ + if (stat(fname, &sb) == -1) { + /* + * check if fname is a symbolic link if so, redirect using + * its target + */ + if (lstat(fname, &sb) != -1 && S_ISLNK(sb.st_mode) == 1) + goto redirect; + else + goto err; + } + /* check if directory */ + if (S_ISDIR(sb.st_mode) != 0) { + /* no ending "/", redirect to "fname/" */ + estrlcpy(tmp, fname, sizeof(tmp)); + estrlcat(tmp, "/", sizeof(tmp)); + status_redirect(31, tmp); + return; + } + /* open the file requested */ + if ((fd = fopen(fname, "r")) == NULL) + goto err; + + file_mime = get_file_mime(fname, default_mime); + + status(20, file_mime); + + /* read the file byte after byte in buffer and write it to stdout */ + while ((nread = fread(buffer, 1, sizeof(buffer), fd)) != 0) + fwrite(buffer, 1, nread, stdout); + goto closefd; /* close file descriptor */ + syslog(LOG_DAEMON, "path served %s", fname); + + return; + +err: + /* return an error code and no content */ + status_error(51, "file not found"); + syslog(LOG_DAEMON, "path invalid %s", fname); + goto closefd; + +redirect: + /* read symbolic link target to redirect */ + if (readlink(fname, target, FILENAME_MAX) == -1) + goto err; + + status_redirect(30, target); + syslog(LOG_DAEMON, "redirection from %s to %s", fname, target); + +closefd: + if (S_ISREG(sb.st_mode) != 0) + fclose(fd); +} + +void +autoindex(const char *path) +{ + /* display liks to files in path + a link to parent (..) */ + + int n = 0; + struct dirent **namelist; /* this must be freed at last */ + + syslog(LOG_DAEMON, "autoindex: %s", path); + + /* use alphasort to always have the same order on every system */ + if ((n = scandir(path, &namelist, NULL, alphasort)) < 0) { + status_error(50, "Internal server error"); + errlog("Can't scan %s", path); + } else { + status(20, "text/gemini"); + printf("=> .. ../\n"); /* display link to parent */ + for (int j = 0; j < n; j++) { + /* skip self and parent */ + if ((strcmp(namelist[j]->d_name, ".") == 0) || + (strcmp(namelist[j]->d_name, "..") == 0)) { + continue; + } + /* add "/" at the end of a directory path */ + if (namelist[j]->d_type == DT_DIR) + printf("=> ./%s/ %s/\n", namelist[j]->d_name, namelist[j]->d_name); + else + printf("=> ./%s %s\n", namelist[j]->d_name, namelist[j]->d_name); + free(namelist[j]); + } + free(namelist); + } +} + +void +cgi(const char *cgicmd) +{ + /* run cgicmd replacing current process */ + execl(cgicmd, cgicmd, NULL); + /* if execl is ok, this will never be reached */ + status(42, "Couldn't execute CGI script"); + errlog("error when trying to execl %s", cgicmd); + exit(1); +} + +void +strip_trailing_slash(char *path) +{ + size_t end = strlen(path); + if (end == 0) + return; + end--; + while (path[end] == '/') + path[end--] = '\0'; +} diff --git a/vger.h b/vger.h new file mode 100644 index 0000000..a3d0ba2 --- /dev/null +++ b/vger.h @@ -0,0 +1,27 @@ +#ifndef vger_h_INCLUDED +#define vger_h_INCLUDED + +/* length of "gemini://" */ +#define GEMINI_PART 9 + +/* + * number of bytes to read with fgets() : 2014 + 1. + * fgets() reads at most size-1 (1024 here). + * See https://gemini.circumlunar.space/docs/specification.html. + */ +#define GEMINI_REQUEST_MAX 1025 + +void autoindex(const char *); +void cgi (const char *cgicmd); +void display_file(const char *); +void drop_privileges(const char *, const char *); +void echdir (const char *); +void status (const int, const char *); +void status_redirect(const int, const char *); +void status_error(const int, const char *); +void strip_trailing_slash(char *path); +int uridecode (char *); + + +#endif // vger_h_INCLUDED +