-c is now relative to chroot and can be used with multiple vhosts
This commit is contained in:
parent
de579c346a
commit
c09063b10e
47
main.c
47
main.c
@ -8,7 +8,10 @@ main(int argc, char **argv)
|
||||
char hostname[GEMINI_REQUEST_MAX] = {'\0'};
|
||||
char path[GEMINI_REQUEST_MAX] = {'\0'};
|
||||
char query[GEMINI_REQUEST_MAX] = {'\0'};
|
||||
char cgi_dir[PATH_MAX] = {'\0'};
|
||||
char rel_cgi_dir[PATH_MAX] = {'\0'};
|
||||
char chroot_dir[PATH_MAX] = DEFAULT_CHROOT;
|
||||
char tmp[PATH_MAX] = {'\0'};
|
||||
int option = 0;
|
||||
int virtualhost = 0;
|
||||
|
||||
@ -34,7 +37,11 @@ main(int argc, char **argv)
|
||||
esnprintf(user, sizeof(user), "%s", optarg);
|
||||
break;
|
||||
case 'c':
|
||||
esnprintf(cgi_dir, sizeof(cgi_dir), "%s", optarg);
|
||||
esnprintf(rel_cgi_dir, sizeof(rel_cgi_dir), "%s", optarg);
|
||||
/* remove leading / */
|
||||
while (*rel_cgi_dir == '/')
|
||||
memmove(rel_cgi_dir, rel_cgi_dir+1,
|
||||
strlen(rel_cgi_dir)); // strlen +1-1
|
||||
break;
|
||||
case 'v':
|
||||
virtualhost = 1;
|
||||
@ -45,29 +52,43 @@ main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* do chroot if an user is supplied
|
||||
*/
|
||||
drop_privileges(user, chroot_dir, cgi_dir);
|
||||
|
||||
read_request(request);
|
||||
split_request(request, hostname, path, query);
|
||||
set_path(path, sizeof(path), virtualhost, hostname);
|
||||
|
||||
/* percent decode */
|
||||
/* do chroot if an user is supplied */
|
||||
if (*user)
|
||||
drop_privileges(user);
|
||||
|
||||
/* set actual chroot_dir */
|
||||
if (virtualhost) {
|
||||
esnprintf(tmp, sizeof(tmp), "%s/%s", chroot_dir, hostname);
|
||||
esnprintf(chroot_dir, sizeof(chroot_dir), "%s", tmp);
|
||||
}
|
||||
|
||||
/* cgi_dir is in chroot_dir */
|
||||
if (*rel_cgi_dir)
|
||||
esnprintf(cgi_dir, sizeof(cgi_dir),
|
||||
"%s/%s", chroot_dir, rel_cgi_dir);
|
||||
|
||||
set_rootdir(chroot_dir, cgi_dir, user);
|
||||
|
||||
if (strlen(path) == 0) { /* this is root dir */
|
||||
esnprintf(path, sizeof(path), "./");
|
||||
} else {
|
||||
uridecode(path);
|
||||
remove_double_dot(path);
|
||||
}
|
||||
|
||||
uridecode(query);
|
||||
uridecode(path);
|
||||
|
||||
remove_double_dot(path);
|
||||
|
||||
/* is it cgi ? */
|
||||
if (*cgi_dir)
|
||||
if (do_cgi(chroot_dir, cgi_dir, path, hostname, query) == 0)
|
||||
if (do_cgi(rel_cgi_dir, path, hostname, query) == 0)
|
||||
stop(EXIT_SUCCESS, NULL);
|
||||
/* *** from here, cgi didn't run *** */
|
||||
|
||||
/* check if path available */
|
||||
check_path(path, sizeof(path), virtualhost, strlen(hostname));
|
||||
check_path(path, sizeof(path));
|
||||
|
||||
/* regular file to stdout */
|
||||
display_file(path);
|
||||
|
2
opts.h
2
opts.h
@ -14,5 +14,3 @@
|
||||
static char default_mime[64] = DEFAULT_MIME;
|
||||
static char lang[16] = DEFAULT_LANG;
|
||||
static unsigned int doautoidx = DEFAULT_AUTOIDX;
|
||||
static char cgi_dir[PATH_MAX] = {'\0'};
|
||||
static int chrooted = 0;
|
||||
|
@ -80,23 +80,23 @@ OUT=$(printf "gemini://host.name/autoidx/\r\n" | ../vger -d var/gemini/ -i | tee
|
||||
if ! [ $OUT = "765bbbe2add810be8eb191bbde59e258" ] ; then echo "error" ; exit 1 ; fi
|
||||
|
||||
# cgi simple script
|
||||
OUT=$(printf "gemini://host.name/cgi-bin/test.cgi\r\n" | ../vger -d var/gemini/ -c var/gemini/cgi-bin | tee /dev/stderr | MD5)
|
||||
OUT=$(printf "gemini://host.name/cgi-bin/test.cgi\r\n" | ../vger -d var/gemini/ -c /cgi-bin | tee /dev/stderr | MD5)
|
||||
if ! [ $OUT = "666e48200f90018b5e96c2cf974882dc" ] ; then echo "error" ; exit 1 ; fi
|
||||
|
||||
# cgi with use of variables
|
||||
OUT=$(printf "gemini://host.name/cgi-bin/who.cgi?user=jean-mi\r\n" | ../vger -d var/gemini/ -c var/gemini/cgi-bin | tee /dev/stderr | MD5)
|
||||
OUT=$(printf "gemini://host.name/cgi-bin/who.cgi?user=jean-mi\r\n" | ../vger -d var/gemini/ -c /cgi-bin | tee /dev/stderr | MD5)
|
||||
if ! [ $OUT = "fa065a67d1f7c973501d4a9e3ca2ea57" ] ; then echo "error" ; exit 1 ; fi
|
||||
|
||||
# cgi with error
|
||||
OUT=$(printf "gemini://host.name/cgi-bin/nope\r\n" | ../vger -d var/gemini/ -c var/gemini/cgi-bin | tee /dev/stderr | MD5)
|
||||
OUT=$(printf "gemini://host.name/cgi-bin/nope\r\n" | ../vger -d var/gemini/ -c /cgi-bin | tee /dev/stderr | MD5)
|
||||
if ! [ $OUT = "31b98e160402a073298c12f763d5db64" ] ; then echo "error" ; exit 1 ; fi
|
||||
|
||||
# cgi with PATH_INFO
|
||||
OUT=$(printf "gemini://host.name/cgi-bin/test.cgi/path/info\r\n" | ../vger -d var/gemini -c var/gemini/cgi-bin | tee /dev/stderr | MD5)
|
||||
OUT=$(printf "gemini://host.name/cgi-bin/test.cgi/path/info\r\n" | ../vger -d var/gemini -c /cgi-bin | tee /dev/stderr | MD5)
|
||||
if ! [ $OUT = "ec64da76dc578ffb479fbfb23e3a7a5b" ] ; then echo "error" ; exit 1 ; fi
|
||||
|
||||
# virtualhost + cgi
|
||||
OUT=$(printf "gemini://perso.pw/cgi-bin/test.cgi\r\n" | ../vger -v -d var/gemini/ -c var/gemini/perso.pw/cgi-bin | tee /dev/stderr | MD5)
|
||||
OUT=$(printf "gemini://perso.pw/cgi-bin/test.cgi\r\n" | ../vger -v -d var/gemini/ -c /cgi-bin | tee /dev/stderr | MD5)
|
||||
if ! [ $OUT = "666e48200f90018b5e96c2cf974882dc" ] ; then echo "error" ; exit 1 ; fi
|
||||
|
||||
# percent-decoding
|
||||
|
5
vger.8
5
vger.8
@ -45,11 +45,10 @@ will read the file /var/gemini/hostname.example/file.gmi
|
||||
Enable CGI support.
|
||||
.Ar cgi_path
|
||||
files will be executed as a cgi script instead of returning their content.
|
||||
.Ar cgi_path must not end with '/'.
|
||||
If using virtualhost, you must insert the virtualhost directory in the cgi path.
|
||||
.Ar cgi_path should be relative to chroot so cgi can be called for different virtualhosts.
|
||||
As example, for a request gemini://hostname.example/cgi-bin/hello.cgi, one must set:
|
||||
.Bd -literal -offset indent
|
||||
vger -c /var/gemini/hostname.example/cgi-bin/hello.cgi
|
||||
vger -c cgi-bin
|
||||
.Ed
|
||||
.Pp
|
||||
In this case,
|
||||
|
120
vger.c
120
vger.c
@ -99,7 +99,7 @@ uridecode(char *uri)
|
||||
}
|
||||
|
||||
void
|
||||
drop_privileges(const char *user, const char *chroot_dir, const char *cgi_dir)
|
||||
drop_privileges(const char *user)
|
||||
{
|
||||
struct passwd *pw;
|
||||
|
||||
@ -107,22 +107,38 @@ drop_privileges(const char *user, const char *chroot_dir, const char *cgi_dir)
|
||||
* use chroot() if an user is specified requires root user to be
|
||||
* running the program to run chroot() and then drop privileges
|
||||
*/
|
||||
|
||||
/* is root? */
|
||||
if (getuid() != 0) {
|
||||
status(41, "privileges issue, see logs");
|
||||
stop(EXIT_FAILURE, "%s",
|
||||
"chroot requires program to be run as root");
|
||||
}
|
||||
|
||||
/* search user uid from name */
|
||||
if ((pw = getpwnam(user)) == NULL) {
|
||||
status(41, "privileges issue, see logs");
|
||||
stop(EXIT_FAILURE,
|
||||
"the user %s can't be found on the system", user);
|
||||
}
|
||||
|
||||
/* 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)) {
|
||||
status(41, "privileges issue, see logs");
|
||||
stop(EXIT_FAILURE,
|
||||
"dropping privileges to user %s (uid=%i) failed", \
|
||||
user, pw->pw_uid);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
set_rootdir(const char *chroot_dir, const char *cgi_dir, const char *user)
|
||||
{
|
||||
char capsule_dir[PATH_MAX] = {'\0'};
|
||||
|
||||
if (*user) {
|
||||
|
||||
/* is root? */
|
||||
if (getuid() != 0) {
|
||||
status(41, "privileges issue, see logs");
|
||||
stop(EXIT_FAILURE, "%s",
|
||||
"chroot requires program to be run as root");
|
||||
}
|
||||
|
||||
/* search user uid from name */
|
||||
if ((pw = getpwnam(user)) == NULL) {
|
||||
status(41, "privileges issue, see logs");
|
||||
stop(EXIT_FAILURE,
|
||||
"the user %s can't be found on the system", user);
|
||||
}
|
||||
|
||||
/* chroot worked? */
|
||||
if (chroot(chroot_dir) != 0) {
|
||||
status(41, "privileges issue, see logs");
|
||||
@ -130,26 +146,18 @@ drop_privileges(const char *user, const char *chroot_dir, const char *cgi_dir)
|
||||
"the chroot_dir %s can't be used for chroot", chroot_dir);
|
||||
}
|
||||
|
||||
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)) {
|
||||
status(41, "privileges issue, see logs");
|
||||
stop(EXIT_FAILURE,
|
||||
"dropping privileges to user %s (uid=%i) failed", \
|
||||
user, pw->pw_uid);
|
||||
}
|
||||
/* now chroot_dir is / */
|
||||
esnprintf(capsule_dir, sizeof(capsule_dir), "%s", "/");
|
||||
|
||||
} else {
|
||||
esnprintf(capsule_dir, sizeof(capsule_dir), "%s", chroot_dir);
|
||||
}
|
||||
|
||||
#ifdef __OpenBSD__
|
||||
/*
|
||||
* prevent access to files other than the one in chroot_dir
|
||||
*/
|
||||
if (chrooted)
|
||||
eunveil("/", "r");
|
||||
else
|
||||
eunveil(chroot_dir, "r");
|
||||
eunveil(capsule_dir, "r");
|
||||
|
||||
/* permission to execute what's inside cgi_dir */
|
||||
if (*cgi_dir)
|
||||
@ -163,8 +171,7 @@ drop_privileges(const char *user, const char *chroot_dir, const char *cgi_dir)
|
||||
else
|
||||
epledge("stdio rpath", NULL);
|
||||
#endif
|
||||
if (!chrooted)
|
||||
echdir(chroot_dir); /* move to the gemini data directory */
|
||||
echdir(capsule_dir); /* move to the gemini data directory */
|
||||
}
|
||||
|
||||
ssize_t
|
||||
@ -204,7 +211,7 @@ display_file(const char *path)
|
||||
}
|
||||
|
||||
int
|
||||
do_cgi(const char *chroot_dir, const char *cgi_dir, const char *path, const char *hostname, const char *query)
|
||||
do_cgi(const char *rel_cgi_dir, const char *path, const char *hostname, const char *query)
|
||||
{
|
||||
|
||||
/* WARNING : this function is fragile since it
|
||||
@ -213,21 +220,11 @@ do_cgi(const char *chroot_dir, const char *cgi_dir, const char *path, const char
|
||||
* if two path refer to the same inode
|
||||
*/
|
||||
|
||||
char cgirp[PATH_MAX] = {'\0'}; /* cgi dir path in chroot */
|
||||
char cgifp[PATH_MAX] = {'\0'}; /* cgi file to execute */
|
||||
char *path_info = NULL;
|
||||
|
||||
/* check if path starts with cgi_dir
|
||||
* compare beginning of path with cgi_dir
|
||||
* cgi_dir + strlen(chrootdir) (skip chrootdir)
|
||||
*/
|
||||
|
||||
esnprintf(cgirp, sizeof(cgirp), "%s", cgi_dir + strlen(chroot_dir));
|
||||
/* ensure there is no leading / if user didn't end chrootdir with */
|
||||
while (*cgirp == '/')
|
||||
memmove(cgirp, cgirp+1, strlen(cgirp+1)+1);
|
||||
|
||||
if (strncmp(cgirp, path, strlen(cgirp)) != 0)
|
||||
/* check if path starts with rel_cgi_dir */
|
||||
if (strncmp(rel_cgi_dir, path, strlen(rel_cgi_dir)) != 0)
|
||||
return 1; /* not in cgi_dir, go to display_file */
|
||||
|
||||
/* set env variables for CGI
|
||||
@ -248,11 +245,11 @@ do_cgi(const char *chroot_dir, const char *cgi_dir, const char *path, const char
|
||||
*/
|
||||
|
||||
/* find next item after cgi_dir in path:
|
||||
* path + strlen(cgirp) + 1 (skip '/')
|
||||
* path + strlen(rel_cgi_dir) + 1 (skip '/')
|
||||
*/
|
||||
|
||||
/* cgi file to execute */
|
||||
esnprintf(cgifp, sizeof(cgifp), "%s", path + strlen(cgirp) + 1);
|
||||
esnprintf(cgifp, sizeof(cgifp), "%s", path + strlen(rel_cgi_dir) + 1);
|
||||
if (!(*cgifp)) /* problem with cgi file, abort */
|
||||
return 1;
|
||||
|
||||
@ -267,7 +264,7 @@ do_cgi(const char *chroot_dir, const char *cgi_dir, const char *path, const char
|
||||
esetenv("SCRIPT_NAME", cgifp, 1);
|
||||
esetenv("SERVER_NAME", hostname, 1);
|
||||
|
||||
echdir(cgirp);
|
||||
echdir(rel_cgi_dir);
|
||||
|
||||
cgi(cgifp);
|
||||
return 0;
|
||||
@ -394,25 +391,8 @@ remove_double_dot(char *request)
|
||||
memmove(request, pos + 3, strlen(pos) + 1 - 3); /* "/.." = 3 */
|
||||
}
|
||||
|
||||
char *
|
||||
set_path(char *path, size_t pathsiz, int virtualhost, const char *hostname)
|
||||
{
|
||||
/* path is in a subdir named hostname */
|
||||
if (virtualhost) {
|
||||
char tmp[GEMINI_REQUEST_MAX] = {'\0'};
|
||||
esnprintf(tmp, sizeof(tmp), "%s/%s", hostname, path);
|
||||
esnprintf(path, pathsiz, "%s", tmp);
|
||||
}
|
||||
|
||||
if (strlen(path) == 0) /* this is root dir */
|
||||
esnprintf(path, pathsiz, "./");
|
||||
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
void
|
||||
check_path(char *path, size_t pathsiz, int virtualhost, size_t hstnm_o)
|
||||
check_path(char *path, size_t pathsiz)
|
||||
{
|
||||
struct stat sb = {0};
|
||||
char tmp[PATH_MAX] = {'\0'};
|
||||
@ -431,11 +411,7 @@ check_path(char *path, size_t pathsiz, int virtualhost, size_t hstnm_o)
|
||||
if (S_ISDIR(sb.st_mode)) {
|
||||
/* check if dir path end with "/" */
|
||||
if (path[strlen(path) - 1] != '/') {
|
||||
/* redirect to the dir with appropriate ending '/' */
|
||||
if (virtualhost) /* skip hostname */
|
||||
esnprintf(tmp, sizeof(tmp), "/%s/", path+hstnm_o+1);
|
||||
else
|
||||
esnprintf(tmp, sizeof(tmp), "/%s/", path);
|
||||
esnprintf(tmp, sizeof(tmp), "/%s/", path);
|
||||
status(31, "%s", tmp);
|
||||
stop(EXIT_SUCCESS, NULL);
|
||||
}
|
||||
|
8
vger.h
8
vger.h
@ -42,12 +42,12 @@ static char _request[GEMINI_REQUEST_MAX] = {'\0'};
|
||||
ssize_t autoindex(const char *);
|
||||
void cgi(const char *);
|
||||
char * read_request(char *);
|
||||
void check_path(char *, size_t, int, size_t);
|
||||
void check_path(char *, size_t);
|
||||
ssize_t display_file(const char *);
|
||||
int do_cgi(const char *, const char *, const char *, const char *, const char *);
|
||||
void drop_privileges(const char *, const char *, const char *);
|
||||
int do_cgi(const char *, const char *, const char *, const char *);
|
||||
void drop_privileges(const char *);
|
||||
void set_rootdir(const char *, const char *, const char *);
|
||||
void remove_double_dot(char *);
|
||||
char * set_path(char *, size_t, int, const char *);
|
||||
void split_request(const char *, char *, char *, char *);
|
||||
void status(const int, const char *, ...);
|
||||
void stop(const int, const char *, ...);
|
||||
|
Loading…
Reference in New Issue
Block a user