This will ease the creation of unit tests and code reusabilitymaster
parent
8bd1144178
commit
9cca2408c3
4 changed files with 326 additions and 318 deletions
@ -0,0 +1,297 @@ |
||||
#include <sys/types.h> |
||||
#include <sys/stat.h> |
||||
#include <sys/wait.h> |
||||
|
||||
#include <ctype.h> |
||||
#include <dirent.h> |
||||
#include <err.h> |
||||
#include <errno.h> |
||||
#include <fcntl.h> |
||||
#include <limits.h> |
||||
#include <pwd.h> |
||||
#include <stdarg.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <syslog.h> |
||||
#include <unistd.h> |
||||
|
||||
#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'; |
||||
} |
@ -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
|
||||
|
Loading…
Reference in new issue