#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'; }