You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
226 lines
6.0 KiB
226 lines
6.0 KiB
#include "vger.c" |
|
|
|
int |
|
main(int argc, char **argv) |
|
{ |
|
char request [GEMINI_REQUEST_MAX] = {'\0'}; |
|
char user [_SC_LOGIN_NAME_MAX] = ""; |
|
char hostname [GEMINI_REQUEST_MAX] = {'\0'}; |
|
char query [PATH_MAX] = {'\0'}; |
|
char chroot_dir[PATH_MAX] = DEFAULT_CHROOT; |
|
char file [FILENAME_MAX] = DEFAULT_INDEX; |
|
char dir [PATH_MAX] = {'\0'}; |
|
char *pos = NULL; |
|
int option = 0; |
|
int virtualhost = 0; |
|
int docgi = 0; |
|
|
|
/* |
|
* request : contain the whole request from client : gemini://...\r\n |
|
* user : username, used in drop_privileges() |
|
* hostname : extracted from hostname. used with virtualhosts and cgi SERVER_NAME |
|
* query : file requested in cgi : gemini://...?query |
|
* file : file basename to display. Emtpy is a directory has been requested |
|
* dir : directory requested. vger will chdir() in to find file |
|
* pos : used to parse request and split into interesting parts |
|
*/ |
|
|
|
while ((option = getopt(argc, argv, ":d:l:m:u:c:vi")) != -1) { |
|
switch (option) { |
|
case 'd': |
|
estrlcpy(chroot_dir, optarg, sizeof(chroot_dir)); |
|
break; |
|
case 'l': |
|
estrlcpy(lang, "lang=", sizeof(lang)); |
|
estrlcat(lang, optarg, sizeof(lang)); |
|
break; |
|
case 'm': |
|
estrlcpy(default_mime, optarg, sizeof(default_mime)); |
|
break; |
|
case 'u': |
|
estrlcpy(user, optarg, sizeof(user)); |
|
break; |
|
case 'c': |
|
estrlcpy(cgidir, optarg, sizeof(cgidir)); |
|
docgi = 1; |
|
break; |
|
case 'v': |
|
virtualhost = 1; |
|
break; |
|
case 'i': |
|
doautoidx = 1; |
|
break; |
|
} |
|
} |
|
|
|
/* |
|
* do chroot if an user is supplied |
|
*/ |
|
drop_privileges(user, chroot_dir); |
|
|
|
/* |
|
* read 1024 chars from stdin |
|
* to get the request |
|
* (actually 1024 + \0) |
|
*/ |
|
if (fgets(request, GEMINI_REQUEST_MAX, stdin) == NULL) { |
|
/* EOF reached before reading anything */ |
|
if (feof(stdin)) { |
|
status(59, "request is too short and probably empty"); |
|
errlog("request is too short and probably empty"); |
|
|
|
/* error before reading anything */ |
|
} else if (ferror(stdin)) { |
|
status(59, "Error while reading request"); |
|
errlog("Error while reading request: %s", request); |
|
} |
|
} |
|
/* check if string ends with '\n', or to long */ |
|
if (request[strnlen(request, GEMINI_REQUEST_MAX) - 1] != '\n') { |
|
status(59, "request is too long (1024 max)"); |
|
errlog("request is too long (1024 max): %s", request); |
|
} |
|
/* remove \r\n at the end of string */ |
|
pos = strchr(request, '\r'); |
|
if (pos != NULL) |
|
*pos = '\0'; |
|
|
|
/* |
|
* check if the beginning of the request starts with |
|
* gemini:// |
|
*/ |
|
if (strncmp(request, "gemini://", GEMINI_PART) != 0) { |
|
/* error code url malformed */ |
|
errlog("request «%s» doesn't match gemini://", |
|
request); |
|
} |
|
syslog(LOG_DAEMON, "request %s", request); |
|
|
|
/* remove the gemini:// part */ |
|
memmove(request, request + GEMINI_PART, strlen(request) + 1 - GEMINI_PART); |
|
|
|
/* remove all "/.." for safety reasons */ |
|
while ((pos = strstr(request, "/..")) != NULL) |
|
memmove(request, pos + 3, strlen(pos) + 1 - 3); /* "/.." = 3 */ |
|
|
|
/* look for hostname in request : first thing before first / if any */ |
|
pos = strchr(request, '/'); |
|
if (pos != NULL) { |
|
/* copy what's after hostname in dir */ |
|
estrlcpy(dir, pos, strlen(pos) + 1); |
|
/* just keep hostname in request : stop the string with \0 */ |
|
pos[0] = '\0'; |
|
} |
|
/* check if client added :port at end of hostname and remove it */ |
|
pos = strchr(request, ':'); |
|
if (pos != NULL) { |
|
/* end string at : */ |
|
pos[0] = '\0'; |
|
} |
|
/* copy hostname from request */ |
|
estrlcpy(hostname, request, sizeof(hostname)); |
|
|
|
/* remove leading '/' in dir */ |
|
while (dir[0] == '/') |
|
memmove(dir, dir + 1, strlen(dir + 1) + 1); |
|
|
|
if (virtualhost) { |
|
/* add hostname at the beginning of the dir path */ |
|
char tmp [PATH_MAX] = {'\0'}; |
|
estrlcpy(tmp, hostname, sizeof(tmp)); |
|
estrlcat(tmp, "/", sizeof(tmp)); |
|
estrlcat(tmp, dir, sizeof(tmp)); |
|
estrlcpy(dir, tmp, sizeof(dir)); |
|
} |
|
|
|
/* remove a query string before percent decoding */ |
|
/* look for "?" if any to set query for cgi, remove it */ |
|
pos = strchr(dir, '?'); |
|
if (pos != NULL) { |
|
estrlcpy(query, pos + 1, sizeof(query)); |
|
uridecode(query); |
|
pos[0] = '\0'; |
|
} |
|
|
|
/* percent decode */ |
|
uridecode(dir); |
|
|
|
/* |
|
* split dir and filename. file is last part after last '/'. if none |
|
* found, then requested file is actually a directory |
|
*/ |
|
if (strlen(dir) > 0) { |
|
/* |
|
* if in cgidir, only the first file after cgidir/FILE is to be executed |
|
* the rest is PATH_INFO |
|
*/ |
|
/* find the string of cgidir without chroot_dir prefix */ |
|
char tmp [PATH_MAX] = {'\0'}; |
|
char *cgipp; |
|
strip_trailing_slash(chroot_dir); |
|
strip_trailing_slash(cgidir); |
|
estrlcpy(tmp, cgidir + strlen(chroot_dir), sizeof(tmp)); |
|
cgipp = tmp; |
|
if (tmp[0] == '/') |
|
cgipp++; |
|
|
|
if (docgi && strncmp(dir, cgipp, strlen(cgipp)) == 0) { |
|
pos = strchr(dir+strlen(cgipp), '/'); |
|
} else { |
|
pos = strrchr(dir, '/'); |
|
} |
|
if (pos != NULL) { |
|
estrlcpy(file, pos + 1, sizeof(file)); /* +1 : no leading '/' */ |
|
pos[0] = '\0'; |
|
|
|
/* change directory to requested directory */ |
|
if (strlen(dir) > 0) |
|
echdir(dir); |
|
} else { |
|
estrlcpy(file, dir, sizeof(file)); |
|
} |
|
} |
|
if (docgi) { |
|
/* check if directory is cgidir */ |
|
char cgifp [PATH_MAX] = {'\0'}; |
|
estrlcpy(cgifp, chroot_dir, sizeof(cgifp)); |
|
if (cgifp[strlen(cgifp) - 1] != '/') |
|
estrlcat(cgifp, "/", sizeof(cgifp)); |
|
|
|
estrlcat(cgifp, dir, sizeof(cgifp)); |
|
|
|
/* not cgipath, display file content */ |
|
if (strcmp(cgifp, cgidir) != 0) |
|
goto file_to_stdout; |
|
|
|
/* set env variables for CGI */ |
|
/* |
|
* see |
|
* https://lists.orbitalfox.eu/archives/gemini/2020/000315.htm |
|
* l |
|
*/ |
|
esetenv("GATEWAY_INTERFACE", "CGI/1.1", 1); |
|
esetenv("SERVER_PROTOCOL", "GEMINI", 1); |
|
esetenv("SERVER_SOFTWARE", "vger/1", 1); |
|
|
|
if (*query) |
|
esetenv("QUERY_STRING", query, 1); |
|
|
|
/* if we've got here and file contains a '/', it should be interpreted as PATH_INFO */ |
|
pos = strchr(file, '/'); |
|
if (pos != NULL) { |
|
setenv("PATH_INFO", pos, 1); |
|
pos[0] = '\0'; /* keep only script name */ |
|
} |
|
esetenv("SCRIPT_NAME", file, 1); |
|
esetenv("SERVER_NAME", hostname, 1); |
|
|
|
cgi(file); |
|
return 0; |
|
} |
|
file_to_stdout: |
|
/* regular file to stdout */ |
|
display_file(file); |
|
|
|
return (0); |
|
}
|
|
|