/* * Send a text file to the client */ void send_text_file(state *st) { FILE *fp; char in[BUFSIZE]; char out[BUFSIZE]; int line; if (st->debug) syslog(LOG_INFO, "outputting text file \"%s\"", st->req_realpath); if ((fp = fopen(st->req_realpath , "r")) == NULL) return; /* Loop through the file line by line */ line = 0; while (fgets(in, sizeof(in), fp)) { /* Covert to output charset & print */ if (st->opt_iconv) sstrniconv(st->out_charset, out, in); else sstrlcpy(out, in); chomp(out); #ifdef ENABLE_STRICT_RFC1436 if (strcmp(out, ".") == MATCH) printf(".." CRLF); else #endif printf("%s" CRLF, out); line++; } #ifdef ENABLE_STRICT_RFC1436 printf("." CRLF); #endif fclose(fp); }
/* * Add one suffix->filetype mapping to the filetypes array */ void add_ftype_mapping(state *st, char *suffix) { char *type; int i; /* Let's not do anything stupid */ if (!*suffix) return; if (!(type = strchr(suffix, '='))) return; /* Extract type from the suffix=X string */ *type++ = '\0'; if (!*type) return; /* Loop through the filetype array */ for (i = 0; i < st->filetype_count; i++) { /* Old entry found? */ if (strcasecmp(st->filetype[i].suffix, suffix) == MATCH) { st->filetype[i].type = *type; return; } } /* No old entry found - add new entry */ if (i < MAX_FILETYPES) { sstrlcpy(st->filetype[i].suffix, suffix); st->filetype[i].type = *type; st->filetype_count++; } }
/* * Add one selector rewrite mapping to the array */ void add_rewrite_mapping(state *st, char *match) { char *replace; /* Check input and split it into match & replace */ if (!*match) return; if (!(replace = strchr(match, '='))) return; *replace++ = '\0'; if (!*replace) return; /* Insert match/replace values into the array */ if (st->rewrite_count < MAX_REWRITE) { sstrlcpy(st->rewrite[st->rewrite_count].match, match); sstrlcpy(st->rewrite[st->rewrite_count].replace, replace); st->rewrite_count++; } }
/* * Print gopher menu line */ void info(state *st, char *str, char type) { char buf[BUFSIZE]; char selector[16]; /* Convert string to output charset */ if (st->opt_iconv) sstrniconv(st->out_charset, buf, str); else sstrlcpy(buf, str); /* Handle gopher title resources */ strclear(selector); if (type == TYPE_TITLE) { sstrlcpy(selector, "TITLE"); type = TYPE_INFO; } /* Output info line */ strcut(buf, st->out_width); printf("%c%s\t%s\t%s" CRLF, type, buf, selector, DUMMY_HOST); }
void get_shm_session(state *st, shm_state *shm) { int i; /* Get session id */ if ((i = get_shm_session_id(st, shm)) == ERROR) return; /* Get session data */ if (st->opt_vhost) { sstrlcpy(st->server_host, shm->session[i].server_host); st->server_port = shm->session[i].server_port; } }
/* * Print hURL redirect page */ void url_redirect(state *st) { char dest[BUFSIZE]; char *c; /* Basic security checking */ sstrlcpy(dest, st->req_selector + 4); if (sstrncmp(dest, "http://") != MATCH && sstrncmp(dest, "ftp://") != MATCH && sstrncmp(dest, "mailto:") != MATCH) die(st, ERR_ACCESS, "Refusing to HTTP redirect unsafe protocols"); if ((c = strchr(dest, '"'))) *c = '\0'; if ((c = strchr(dest, '?'))) *c = '\0'; /* Log the redirect */ if (st->opt_syslog) { syslog(LOG_INFO, "request for \"gopher://%s:%i/h%s\" from %s", st->server_host, st->server_port, st->req_selector, st->req_remote_addr); } log_combined(st, HTTP_OK); /* Output HTML */ printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n" "<HTML>\n<HEAD>\n" " <META HTTP-EQUIV=\"Refresh\" content=\"1;URL=%1$s\">\n" " <META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html;charset=iso-8859-1\">\n" " <TITLE>URL Redirect page</TITLE>\n" "</HEAD>\n<BODY>\n" "<STRONG>Redirecting to <A HREF=\"%1$s\">%1$s</A></STRONG>\n" "<PRE>\n", dest); footer(st); printf("</PRE>\n</BODY>\n</HTML>\n"); }
/* * Scan, stat and sort a directory folders first (scandir replacement) */ int sortdir(char *path, sdirent *list, int max) { DIR *dp; struct dirent *d; struct stat s; char buf[BUFSIZE]; int i; /* Try to open the dir */ if ((dp = opendir(path)) == NULL) return 0; i = 0; /* Loop through the directory & stat() everything */ while (max--) { if ((d = readdir(dp)) == NULL) break; snprintf(buf, sizeof(buf), "%s/%s", path, d->d_name); if (stat(buf, &s) == ERROR) continue; if (strlen(d->d_name) > sizeof(list[i].name)) continue; sstrlcpy(list[i].name, d->d_name); list[i].mode = s.st_mode; list[i].uid = s.st_uid; list[i].gid = s.st_gid; list[i].size = s.st_size; list[i].mtime = s.st_mtime; i++; } closedir(dp); /* Sort the entries */ if (i > 1) qsort(list, i, sizeof(sdirent), foldersort); /* Return number of entries found */ return i; }
/* * Parse command-line arguments */ void parse_args(state *st, int argc, char *argv[]) { FILE *fp; static const char readme[] = README; static const char license[] = LICENSE; struct stat file; char buf[BUFSIZE]; int opt; /* Parse args */ while ((opt = getopt(argc, argv, "h:p:r:t:g:a:c:u:m:l:w:o:s:i:k:f:e:R:D:L:A:P:n:db?-")) != ERROR) { switch(opt) { case 'h': sstrlcpy(st->server_host, optarg); break; case 'p': st->server_port = atoi(optarg); break; case 'r': sstrlcpy(st->server_root, optarg); break; case 't': st->default_filetype = *optarg; break; case 'g': sstrlcpy(st->map_file, optarg); break; case 'a': sstrlcpy(st->map_file, optarg); break; case 'c': sstrlcpy(st->cgi_file, optarg); break; case 'u': sstrlcpy(st->user_dir, optarg); break; case 'm': /* obsolete, replaced by -l */ case 'l': sstrlcpy(st->log_file, optarg); break; case 'w': st->out_width = atoi(optarg); break; case 'o': if (sstrncasecmp(optarg, "UTF-8") == MATCH) st->out_charset = UTF_8; if (sstrncasecmp(optarg, "ISO-8859-1") == MATCH) st->out_charset = ISO_8859_1; break; case 's': st->session_timeout = atoi(optarg); break; case 'i': st->session_max_kbytes = abs(atoi(optarg)); break; case 'k': st->session_max_hits = abs(atoi(optarg)); break; case 'f': sstrlcpy(st->filter_dir, optarg); break; case 'e': add_ftype_mapping(st, optarg); break; case 'R': add_rewrite_mapping(st, optarg); break; case 'D': sstrlcpy(st->server_description, optarg); break; case 'L': sstrlcpy(st->server_location, optarg); break; case 'A': sstrlcpy(st->server_admin, optarg); break; case 'n': if (*optarg == 'v') { st->opt_vhost = FALSE; break; } if (*optarg == 'l') { st->opt_parent = FALSE; break; } if (*optarg == 'h') { st->opt_header = FALSE; break; } if (*optarg == 'f') { st->opt_footer = FALSE; break; } if (*optarg == 'd') { st->opt_date = FALSE; break; } if (*optarg == 'c') { st->opt_magic = FALSE; break; } if (*optarg == 'o') { st->opt_iconv = FALSE; break; } if (*optarg == 'q') { st->opt_query = FALSE; break; } if (*optarg == 's') { st->opt_syslog = FALSE; break; } if (*optarg == 'a') { st->opt_caps = FALSE; break; } if (*optarg == 'm') { st->opt_shm = FALSE; break; } if (*optarg == 'r') { st->opt_root = FALSE; break; } break; case 'd': st->debug = TRUE; break; case 'b': puts(license); exit(EXIT_SUCCESS); default : puts(readme); exit(EXIT_SUCCESS); } } /* Sanitize options */ if (st->out_width > MAX_WIDTH) st->out_width = MAX_WIDTH; if (st->out_width < MIN_WIDTH) st->out_width = MIN_WIDTH; if (st->out_width < MIN_WIDTH + DATE_WIDTH) st->opt_date = FALSE; if (!st->opt_syslog) st->debug = FALSE; /* Primary vhost directory must exist or we disable vhosting */ if (st->opt_vhost) { snprintf(buf, sizeof(buf), "%s/%s", st->server_root, st->server_host); if (stat(buf, &file) == ERROR) st->opt_vhost = FALSE; } /* If -D arg looks like a file load the file contents */ if (*st->server_description == '/') { if ((fp = fopen(st->server_description , "r"))) { fgets(st->server_description, sizeof(st->server_description), fp); chomp(st->server_description); fclose(fp); } else strclear(st->server_description); } /* If -L arg looks like a file load the file contents */ if (*st->server_location == '/') { if ((fp = fopen(st->server_location , "r"))) { fgets(st->server_location, sizeof(st->server_location), fp); chomp(st->server_location); fclose(fp); } else strclear(st->server_location); } }
/* * Get OS name, version & architecture we're running on */ void platform(state *st) { #ifdef HAVE_UNAME #if defined(_AIX) || defined(__linux) || defined(__APPLE__) FILE *fp; #endif #if defined(__arm__) || defined(__mips__) || defined(__APPLE__) char buf[BUFSIZE]; #endif #ifdef __linux struct stat file; #endif struct utsname name; char sysname[64]; char release[64]; char machine[64]; char *c; /* Fetch system information */ uname(&name); strclear(sysname); strclear(release); strclear(machine); /* AIX-specific */ #ifdef _AIX /* Fix uname() results */ sstrlcpy(machine, "powerpc"); snprintf(release, sizeof(release), "%s.%s", name.version, name.release); /* Get CPU type */ if ((fp = popen("/usr/sbin/getsystype -i", "r"))) { fgets(machine, sizeof(machine), fp); pclose(fp); strreplace(machine, ' ', '_'); } /* Get hardware name using shell uname */ if (!*st->server_description && (fp = popen("/usr/bin/uname -M", "r"))) { fgets(st->server_description, sizeof(st->server_description), fp); pclose(fp); strreplace(st->server_description, ',', ' '); chomp(st->server_description); } #endif /* Mac OS X, just like Unix but totally different... */ #ifdef __APPLE__ /* Hardcode OS name */ sstrlcpy(sysname, "MacOSX"); /* Get OS X version */ if ((fp = popen("/usr/bin/sw_vers -productVersion", "r"))) { fgets(release, sizeof(release), fp); pclose(fp); } /* Get hardware name */ if (!*st->server_description && (fp = popen("/usr/sbin/sysctl -n hw.model", "r"))) { /* Read hardware name */ fgets(buf, sizeof(buf), fp); pclose(fp); /* Clones are gone now so we'll hardcode the manufacturer */ sstrlcpy(st->server_description, "Apple "); sstrlcat(st->server_description, buf); /* Remove hardware revision */ for (c = st->server_description; *c; c++) if (*c >= '0' && *c <= '9') { *c = '\0'; break; } } #endif /* Linux uname() just says Linux/2.6 - let's dig deeper... */ #ifdef __linux /* Most Linux ARM/MIPS boards have hardware name in /proc/cpuinfo */ #if defined(__arm__) || defined(__mips__) if (!*st->server_description && (fp = fopen("/proc/cpuinfo" , "r"))) { while (fgets(buf, sizeof(buf), fp)) { #ifdef __arm__ if ((c = strkey(buf, "Hardware"))) { #else if ((c = strkey(buf, "machine"))) { #endif sstrlcpy(st->server_description, c); chomp(st->server_description); break; } } fclose(fp); } #endif /* Identify RedHat */ if (!*sysname && (fp = fopen("/etc/redhat-release", "r"))) { fgets(sysname, sizeof(sysname), fp); fclose(fp); if ((c = strstr(sysname, "release "))) sstrlcpy(release, c + 8); if ((c = strchr(release, ' '))) *c = '\0'; if ((c = strchr(sysname, ' '))) *c = '\0'; if (strcmp(sysname, "Red") == MATCH) sstrlcpy(sysname, "RedHat"); } /* Identify Slackware */ if (!*sysname && (fp = fopen("/etc/slackware-version", "r"))) { fgets(sysname, sizeof(sysname), fp); fclose(fp); if ((c = strchr(sysname, ' '))) { sstrlcpy(release, c + 1); *c = '\0'; } } /* Uh-oh.... how about a standard Linux with lsb_release? */ if (stat("/usr/bin/lsb_release", &file) == OK && (file.st_mode & S_IXOTH)) { if (!*sysname && (fp = popen("/usr/bin/lsb_release -i -s", "r"))) { fgets(sysname, sizeof(sysname), fp); pclose(fp); } if (!*release && (fp = popen("/usr/bin/lsb_release -r -s", "r"))) { fgets(release, sizeof(release), fp); pclose(fp); } } /* OK, nothing worked - let's try /etc/issue for sysname */ if (!*sysname && (fp = fopen("/etc/issue", "r"))) { fgets(sysname, sizeof(sysname), fp); fclose(fp); if ((c = strchr(sysname, ' '))) *c = '\0'; if ((c = strchr(sysname, '\\'))) *c = '\0'; } /* Debian version should be in /etc/debian_version */ if (!*release && (fp = fopen("/etc/debian_version", "r"))) { fgets (release, sizeof(release), fp); fclose(fp); if ((c = strchr(release, '/'))) *c = '\0'; } #endif /* Haiku OS */ #ifdef __HAIKU__ /* Fix release name */ snprintf(release, sizeof(release), "R%s", name.release); #endif /* Fill in the blanks using uname() data */ if (!*sysname) sstrlcpy(sysname, name.sysname); if (!*release) sstrlcpy(release, name.release); if (!*machine) sstrlcpy(machine, name.machine); /* I always liked weird Perl-only functions */ chomp(sysname); chomp(release); chomp(machine); /* We're only interested in major.minor version */ if ((c = strchr(release, '.'))) if ((c = strchr(c + 1, '.'))) *c = '\0'; if ((c = strchr(release, '-'))) *c = '\0'; if ((c = strchr(release, '/'))) *c = '\0'; /* Create a nicely formatted platform string */ snprintf(st->server_platform, sizeof(st->server_platform), "%s/%s %s", sysname, #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) machine, release); #else release, machine); #endif /* Debug */ if (st->debug) { syslog(LOG_INFO, "generated platform string \"%s\"", st->server_platform); } #else /* Fallback reply */ sstrlcpy(st->server_platform, "Unknown computer-like system"); #endif } /* * Return current CPU load */ float loadavg(void) { FILE *fp; char buf[BUFSIZE]; /* Faster Linux version */ #ifdef __linux buf[0] = '\0'; if ((fp = fopen("/proc/loadavg" , "r")) == NULL) return 0; fgets(buf, sizeof(buf), fp); fclose(fp); return (float) atof(buf); /* Generic slow version - parse the output of uptime */ #else #ifdef HAVE_POPEN char *c; if ((fp = popen("/usr/bin/uptime", "r"))) { fgets(buf, sizeof(buf), fp); pclose(fp); if ((c = strstr(buf, "average: ")) || (c = strstr(buf, "averages: "))) return (float) atof(c + 10); } #endif /* Fallback reply */ return 0; #endif }
/* * Main */ int main(int argc, char *argv[]) { struct stat file; state st; char self[64]; char selector[BUFSIZE]; char buf[BUFSIZE]; char *dest; char *c; #ifdef HAVE_SHMEM struct shmid_ds shm_ds; shm_state *shm; int shmid; #endif /* Get the name of this binary */ if ((c = strrchr(argv[0], '/'))) sstrlcpy(self, c + 1); else sstrlcpy(self, argv[0]); /* Initialize state */ #ifdef HAVE_LOCALES setlocale(LC_TIME, DATE_LOCALE); #endif init_state(&st); srand(time(NULL) / (getpid() + getppid())); /* Handle command line arguments */ parse_args(&st, argc, argv); /* Open syslog() */ if (st.opt_syslog) openlog(self, LOG_PID, LOG_DAEMON); /* Make sure the computer is turned on */ #ifdef __HAIKU__ if (is_computer_on() != TRUE) die(&st, ERR_ACCESS, "Please turn on the computer first"); #endif /* Refuse to run as root */ #ifdef HAVE_PASSWD if (st.opt_root && getuid() == 0) die(&st, ERR_ACCESS, "Refusing to run as root"); #endif /* Try to get shared memory */ #ifdef HAVE_SHMEM if ((shmid = shmget(SHM_KEY, sizeof(shm_state), IPC_CREAT | SHM_MODE)) == ERROR) { /* Getting memory failed -> delete the old allocation */ shmctl(shmid, IPC_RMID, &shm_ds); shm = NULL; } else { /* Map shared memory */ if ((shm = (shm_state *) shmat(shmid, (void *) 0, 0)) == (void *) ERROR) shm = NULL; /* Initialize mapped shared memory */ if (shm && shm->start_time == 0) { shm->start_time = time(NULL); /* Keep server platform & description in shm */ platform(&st); sstrlcpy(shm->server_platform, st.server_platform); sstrlcpy(shm->server_description, st.server_description); } } /* For debugging shared memory issues */ if (!st.opt_shm) shm = NULL; /* Get server platform and description */ if (shm) { sstrlcpy(st.server_platform, shm->server_platform); if (!*st.server_description) sstrlcpy(st.server_description, shm->server_description); } else #endif platform(&st); /* Read selector */ if (fgets(selector, sizeof(selector) - 1, stdin) == NULL) selector[0] = '\0'; /* Remove trailing CRLF */ chomp(selector); if (st.debug) syslog(LOG_INFO, "client sent us \"%s\"", selector); /* Handle hURL: redirect page */ if (sstrncmp(selector, "URL:") == MATCH) { st.req_filetype = TYPE_HTML; sstrlcpy(st.req_selector, selector); url_redirect(&st); return OK; } /* Handle gopher+ root requests (UMN gopher client is seriously borken) */ if (sstrncmp(selector, "\t$") == MATCH) { printf("+-1" CRLF); printf("+INFO: 1Main menu\t\t%s\t%i" CRLF, st.server_host, st.server_port); printf("+VIEWS:" CRLF " application/gopher+-menu: <512b>" CRLF); printf("." CRLF); if (st.debug) syslog(LOG_INFO, "got a request for gopher+ root menu"); return OK; } /* Convert HTTP request to gopher (respond using headerless HTTP/0.9) */ if (sstrncmp(selector, "GET ") == MATCH || sstrncmp(selector, "POST ") == MATCH ) { if ((c = strchr(selector, ' '))) sstrlcpy(selector, c + 1); if ((c = strchr(selector, ' '))) *c = '\0'; st.req_protocol = PROTO_HTTP; if (st.debug) syslog(LOG_INFO, "got HTTP request for \"%s\"", selector); } /* Save default server_host & fetch session data (including new server_host) */ sstrlcpy(st.server_host_default, st.server_host); #ifdef HAVE_SHMEM if (shm) get_shm_session(&st, shm); #endif /* Loop through the selector, fix it & separate query_string */ dest = st.req_selector; if (selector[0] != '/') *dest++ = '/'; for (c = selector; *c;) { /* Skip duplicate slashes and /./ */ while (*c == '/' && *(c + 1) == '/') c++; if (*c == '/' && *(c + 1) == '.' && *(c + 2) == '/') c += 2; /* Start of a query string (either type 7 or HTTP-style)? */ if (*c == '\t' || (st.opt_query && *c == '?')) { sstrlcpy(st.req_query_string, c + 1); if ((c = strchr(st.req_query_string, '\t'))) *c = '\0'; break; } /* Start of virtual host hint? */ if (*c == ';') { if (st.opt_vhost) sstrlcpy(st.server_host, c + 1); /* Skip vhost on selector */ while (*c && *c != '\t') c++; continue; } /* Copy valid char */ *dest++ = *c++; } *dest = '\0'; /* Remove encodings from selector */ strndecode(st.req_selector, st.req_selector, sizeof(st.req_selector)); /* Deny requests for Slashdot and /../ hackers */ if (strstr(st.req_selector, "/.")) die(&st, ERR_ACCESS, "Refusing to serve out dotfiles"); /* Handle /server-status requests */ #ifdef HAVE_SHMEM if (sstrncmp(st.req_selector, SERVER_STATUS) == MATCH) { if (shm) server_status(&st, shm, shmid); return OK; } #endif /* Remove possible extra cruft from server_host */ if ((c = strchr(st.server_host, '\t'))) *c = '\0'; /* Guess request filetype so we can die() with style... */ st.req_filetype = gopher_filetype(&st, st.req_selector, FALSE); /* Convert seletor to path & stat() */ selector_to_path(&st); if (st.debug) syslog(LOG_INFO, "path to resource is \"%s\"", st.req_realpath); if (stat(st.req_realpath, &file) == ERROR) { /* Handle virtual /caps.txt requests */ if (st.opt_caps && sstrncmp(st.req_selector, CAPS_TXT) == MATCH) { #ifdef HAVE_SHMEM caps_txt(&st, shm); #else caps_txt(&st, NULL); #endif return OK; } /* Requested file not found - die() */ die(&st, ERR_NOTFOUND, NULL); } /* Fetch request filesize from stat() */ st.req_filesize = file.st_size; /* Everyone must have read access but no write access */ if ((file.st_mode & S_IROTH) == 0) die(&st, ERR_ACCESS, "File or directory not world-readable"); if ((file.st_mode & S_IWOTH) != 0) die(&st, ERR_ACCESS, "File or directory world-writeable"); /* If stat said it was a dir then it's a menu */ if ((file.st_mode & S_IFMT) == S_IFDIR) st.req_filetype = TYPE_MENU; /* Not a dir - let's guess the filetype again... */ else if ((file.st_mode & S_IFMT) == S_IFREG) st.req_filetype = gopher_filetype(&st, st.req_realpath, st.opt_magic); /* Menu selectors must end with a slash */ if (st.req_filetype == TYPE_MENU && strlast(st.req_selector) != '/') sstrlcat(st.req_selector, "/"); /* Change directory to wherever the resource was */ sstrlcpy(buf, st.req_realpath); if ((file.st_mode & S_IFMT) != S_IFDIR) c = dirname(buf); else c = buf; if (chdir(c) == ERROR) die(&st, ERR_ACCESS, NULL); /* Keep count of hits and data transfer */ #ifdef HAVE_SHMEM if (shm) { shm->hits++; shm->kbytes += st.req_filesize / 1024; /* Update user session */ update_shm_session(&st, shm); } #endif /* Log the request */ if (st.opt_syslog) { syslog(LOG_INFO, "request for \"gopher://%s:%i/%c%s\" from %s", st.server_host, st.server_port, st.req_filetype, st.req_selector, st.req_remote_addr); } /* Check file type & act accordingly */ switch (file.st_mode & S_IFMT) { case S_IFDIR: log_combined(&st, HTTP_OK); gopher_menu(&st); break; case S_IFREG: log_combined(&st, HTTP_OK); gopher_file(&st); break; default: die(&st, ERR_ACCESS, "Refusing to serve out special files"); } /* Clean exit */ return OK; }
/* * Initialize state struct to default/empty values */ void init_state(state *st) { static const char *filetypes[] = { FILETYPES }; char buf[BUFSIZE]; char *c; int i; /* Request */ strclear(st->req_selector); strclear(st->req_realpath); strclear(st->req_query_string); strclear(st->req_referrer); sstrlcpy(st->req_local_addr, get_local_address()); sstrlcpy(st->req_remote_addr, get_peer_address()); /* strclear(st->req_remote_host); */ st->req_filetype = DEFAULT_TYPE; st->req_protocol = PROTO_GOPHER; st->req_filesize = 0; /* Output */ st->out_width = DEFAULT_WIDTH; st->out_charset = DEFAULT_CHARSET; /* Settings */ sstrlcpy(st->server_root, DEFAULT_ROOT); sstrlcpy(st->server_host_default, DEFAULT_HOST); if ((c = getenv("HOSTNAME"))) sstrlcpy(st->server_host, c); else if ((gethostname(buf, sizeof(buf))) != ERROR) sstrlcpy(st->server_host, buf); st->server_port = DEFAULT_PORT; st->default_filetype = DEFAULT_TYPE; sstrlcpy(st->map_file, DEFAULT_MAP); sstrlcpy(st->tag_file, DEFAULT_TAG); sstrlcpy(st->cgi_file, DEFAULT_CGI); sstrlcpy(st->user_dir, DEFAULT_USERDIR); strclear(st->log_file); st->hidden_count = 0; st->filetype_count = 0; strclear(st->filter_dir); st->rewrite_count = 0; strclear(st->server_description); strclear(st->server_location); strclear(st->server_platform); strclear(st->server_admin); /* Session */ st->session_timeout = DEFAULT_SESSION_TIMEOUT; st->session_max_kbytes = DEFAULT_SESSION_MAX_KBYTES; st->session_max_hits = DEFAULT_SESSION_MAX_HITS; /* Feature options */ st->opt_vhost = TRUE; st->opt_parent = TRUE; st->opt_header = TRUE; st->opt_footer = TRUE; st->opt_date = TRUE; st->opt_syslog = TRUE; st->opt_magic = TRUE; st->opt_iconv = TRUE; st->opt_query = TRUE; st->opt_caps = TRUE; st->opt_shm = TRUE; st->opt_root = TRUE; st->debug = FALSE; /* Load default suffix -> filetype mappings */ for (i = 0; filetypes[i]; i += 2) { if (st->filetype_count < MAX_FILETYPES) { sstrlcpy(st->filetype[st->filetype_count].suffix, filetypes[i]); st->filetype[st->filetype_count].type = *filetypes[i + 1]; st->filetype_count++; } } }
/* * Convert gopher selector to an absolute path */ void selector_to_path(state *st) { DIR *dp; struct dirent *dir; struct stat file; #ifdef HAVE_PASSWD struct passwd *pwd; char *path = EMPTY; char *c; #endif char buf[BUFSIZE]; int i; /* Handle selector rewriting */ for (i = 0; i < st->rewrite_count; i++) { /* Match found? */ if (strstr(st->req_selector, st->rewrite[i].match) == st->req_selector) { /* Replace match with a new string */ snprintf(buf, sizeof(buf), "%s%s", st->rewrite[i].replace, st->req_selector + strlen(st->rewrite[i].match)); if (st->debug) { syslog(LOG_INFO, "rewriting selector \"%s\" -> \"%s\"", st->req_selector, buf); } sstrlcpy(st->req_selector, buf); } } #ifdef HAVE_PASSWD /* Virtual userdir (~user -> /home/user/public_gopher)? */ if (*(st->user_dir) && sstrncmp(st->req_selector, "/~") == MATCH) { /* Parse userdir login name & path */; sstrlcpy(buf, st->req_selector + 2); if ((c = strchr(buf, '/'))) { *c = '\0'; path = c + 1; } /* Check user validity */ if ((pwd = getpwnam(buf)) == NULL) die(st, ERR_NOTFOUND, "User not found"); if (pwd->pw_uid < PASSWD_MIN_UID) die(st, ERR_NOTFOUND, "User found but UID too low"); /* Generate absolute path to users own gopher root */ snprintf(st->req_realpath, sizeof(st->req_realpath), "%s/%s/%s", pwd->pw_dir, st->user_dir, path); /* Check ~public_gopher access rights */ if (stat(st->req_realpath, &file) == ERROR) die(st, ERR_NOTFOUND, NULL); if ((file.st_mode & S_IROTH) == 0) die(st, ERR_ACCESS, "~/public_gopher not world-readable"); if (file.st_uid != pwd->pw_uid) die(st, ERR_ACCESS, "~/ and ~/public_gopher owned by different users"); /* Userdirs always come from the default vhost */ if (st->opt_vhost) sstrlcpy(st->server_host, st->server_host_default); return; } #endif /* Virtual hosting */ if (st->opt_vhost) { /* Try looking for the selector from the current vhost */ snprintf(st->req_realpath, sizeof(st->req_realpath), "%s/%s%s", st->server_root, st->server_host, st->req_selector); if (stat(st->req_realpath, &file) == OK) return; /* Loop through all vhosts looking for the selector */ if ((dp = opendir(st->server_root)) == NULL) die(st, ERR_NOTFOUND, NULL); while ((dir = readdir(dp))) { /* Skip .hidden dirs and . & .. */ if (dir->d_name[0] == '.') continue; /* Special case - skip lost+found (don't ask) */ if (sstrncmp(dir->d_name, "lost+found") == MATCH) continue; /* Generate path to the found vhost */ snprintf(st->req_realpath, sizeof(st->req_realpath), "%s/%s%s", st->server_root, dir->d_name, st->req_selector); /* Did we find the selector under this vhost? */ if (stat(st->req_realpath, &file) == OK) { /* Virtual host found - update state & return */ sstrlcpy(st->server_host, dir->d_name); return; } } closedir(dp); } /* Handle normal selectors */ snprintf(st->req_realpath, sizeof(st->req_realpath), "%s%s", st->server_root, st->req_selector); }
void update_shm_session(state *st, shm_state *shm) { time_t now; char buf[BUFSIZE]; int delay; int i; /* Get current time */ now = time(NULL); /* No existing session found? */ if ((i = get_shm_session_id(st, shm)) == ERROR) { /* Look for an empty/expired session slot */ for (i = 0; i < SHM_SESSIONS; i++) { if ((now - shm->session[i].req_atime) > st->session_timeout) { /* Found slot -> initialize it */ sstrlcpy(shm->session[i].req_remote_addr, st->req_remote_addr); shm->session[i].hits = 0; shm->session[i].kbytes = 0; shm->session[i].session_id = rand(); break; } } } /* No available session slot found? */ if (i == SHM_SESSIONS) return; /* Get referrer from old session data */ if (*shm->session[i].server_host) { snprintf(buf, sizeof(buf), "gopher://%s:%i/%c%s", shm->session[i].server_host, shm->session[i].server_port, shm->session[i].req_filetype, shm->session[i].req_selector); sstrlcpy(st->req_referrer, buf); } /* Get public session id */ st->session_id = shm->session[i].session_id; /* Update session data */ sstrlcpy(shm->session[i].server_host, st->server_host); shm->session[i].server_port = st->server_port; sstrlcpy(shm->session[i].req_selector, st->req_selector); shm->session[i].req_filetype = st->req_filetype; shm->session[i].req_atime = now; shm->session[i].hits++; shm->session[i].kbytes += st->req_filesize / 1024; /* Transfer limits exceeded? */ if ((st->session_max_kbytes && shm->session[i].kbytes > st->session_max_kbytes) || (st->session_max_hits && shm->session[i].hits > st->session_max_hits)) { /* Calculate throttle delay */ delay = max(shm->session[i].kbytes / st->session_max_kbytes, shm->session[i].hits / st->session_max_hits); /* Throttle user */ syslog(LOG_INFO, "throttling user from %s for %i seconds", st->req_remote_addr, delay); sleep(delay); } }
/* * Handle gopher menus */ void gopher_menu(state *st) { FILE *fp; sdirent dir[MAX_SDIRENT]; struct tm *ltime; struct stat file; char buf[BUFSIZE]; char pathname[BUFSIZE]; char displayname[BUFSIZE]; char encodedname[BUFSIZE]; char timestr[20]; char sizestr[20]; char *parent; char *c; char type; int width; int num; int i; int n; /* Check for a gophermap */ snprintf(pathname, sizeof(pathname), "%s/%s", st->req_realpath, st->map_file); if (stat(pathname, &file) == OK && (file.st_mode & S_IFMT) == S_IFREG) { /* Parse gophermap */ if (gophermap(st, pathname, 0) == QUIT) { footer(st); return; } } else { /* Check for a gophertag */ snprintf(pathname, sizeof(pathname), "%s/%s", st->req_realpath, st->tag_file); if (stat(pathname, &file) == OK && (file.st_mode & S_IFMT) == S_IFREG) { /* Read & output gophertag */ if ((fp = fopen(pathname , "r"))) { fgets(buf, sizeof(buf), fp); chomp(buf); info(st, buf, TYPE_TITLE); info(st, EMPTY, TYPE_INFO); fclose(fp); } } /* No gophermap or tag found - print default header */ else if (st->opt_header) { /* Use the selector as menu title */ sstrlcpy(displayname, st->req_selector); /* Shorten too long titles */ while (strlen(displayname) > (st->out_width - sizeof(HEADER_FORMAT))) { if ((c = strchr(displayname, '/')) == NULL) break; if (!*++c) break; sstrlcpy(displayname, c); } /* Output menu title */ snprintf(buf, sizeof(buf), HEADER_FORMAT, displayname); info(st, buf, TYPE_TITLE); info(st, EMPTY, TYPE_INFO); } } /* Scan the directory */ num = sortdir(st->req_realpath, dir, MAX_SDIRENT); if (num < 0) die(st, ERR_NOTFOUND, "WTF?"); /* Create link to parent directory */ if (st->opt_parent) { sstrlcpy(buf, st->req_selector); parent = dirname(buf); /* Root has no parent */ if (strcmp(st->req_selector, ROOT) != MATCH) { /* Prevent double-slash */ if (strcmp(parent, ROOT) == MATCH) parent++; /* Print link */ printf("1%-*s\t%s/\t%s\t%i" CRLF, st->opt_date ? (st->out_width - 1) : (int) strlen(PARENT), PARENT, parent, st->server_host, st->server_port); } } /* Width of filenames for fancy listing */ width = st->out_width - DATE_WIDTH - 15; /* Loop through the directory entries */ for (i = 0; i < num; i++) { /* Get full path+name */ snprintf(pathname, sizeof(pathname), "%s/%s", st->req_realpath, dir[i].name); /* Skip dotfiles and non world-readables */ if (dir[i].name[0] == '.') continue; if ((dir[i].mode & S_IROTH) == 0) continue; /* Skip gophermaps and tags (but not dirs) */ if ((dir[i].mode & S_IFMT) != S_IFDIR) { if (strcmp(dir[i].name, st->map_file) == MATCH) continue; if (strcmp(dir[i].name, st->tag_file) == MATCH) continue; } /* Skip files marked for hiding */ for (n = 0; n < st->hidden_count; n++) if (strcmp(dir[i].name, st->hidden[n]) == MATCH) break; if (n < st->hidden_count) continue; /* Cruel hack... */ /* Generate display name with correct output charset */ if (st->opt_iconv) sstrniconv(st->out_charset, displayname, dir[i].name); else sstrlcpy(displayname, dir[i].name); /* #OCT-encode filename */ strnencode(encodedname, dir[i].name, sizeof(encodedname)); /* Handle inline .gophermap */ if (strstr(displayname, st->map_file) > displayname) { gophermap(st, pathname, 0); continue; } /* Handle directories */ if ((dir[i].mode & S_IFMT) == S_IFDIR) { /* Check for a gophertag */ snprintf(buf, sizeof(buf), "%s/%s", pathname, st->tag_file); if (stat(buf, &file) == OK && (file.st_mode & S_IFMT) == S_IFREG) { /* Use the gophertag as displayname */ if ((fp = fopen(buf , "r"))) { fgets(buf, sizeof(buf), fp); chomp(buf); fclose(fp); /* Skip empty gophertags */ if (*buf) { /* Convert to output charset */ if (st->opt_iconv) sstrniconv(st->out_charset, displayname, buf); else sstrlcpy(displayname, buf); } } } /* Dir listing with dates */ if (st->opt_date) { ltime = localtime(&dir[i].mtime); strftime(timestr, sizeof(timestr), DATE_FORMAT, ltime); /* Hack to get around UTF-8 byte != char */ n = width - strcut(displayname, width); strrepeat(buf, ' ', n); printf("1%s%s %s - \t%s%s/\t%s\t%i" CRLF, displayname, buf, timestr, st->req_selector, encodedname, st->server_host, st->server_port); } /* Regular dir listing */ else { strcut(displayname, st->out_width); printf("1%s\t%s%s/\t%s\t%i" CRLF, displayname, st->req_selector, encodedname, st->server_host, st->server_port); } continue; } /* Skip special files (sockets, fifos etc) */ if ((dir[i].mode & S_IFMT) != S_IFREG) continue; /* Get file type */ type = gopher_filetype(st, pathname, st->opt_magic); /* File listing with dates & sizes */ if (st->opt_date) { ltime = localtime(&dir[i].mtime); strftime(timestr, sizeof(timestr), DATE_FORMAT, ltime); strfsize(sizestr, dir[i].size, sizeof(sizestr)); /* Hack to get around UTF-8 byte != char */ n = width - strcut(displayname, width); strrepeat(buf, ' ', n); printf("%c%s%s %s %s\t%s%s\t%s\t%i" CRLF, type, displayname, buf, timestr, sizestr, st->req_selector, encodedname, st->server_host, st->server_port); } /* Regular file listing */ else { strcut(displayname, st->out_width); printf("%c%s\t%s%s\t%s\t%i" CRLF, type, displayname, st->req_selector, encodedname, st->server_host, st->server_port); } } /* Print footer */ footer(st); }
/* * Handle gophermaps */ int gophermap(state *st, char *mapfile, int depth) { FILE *fp; struct stat file; char line[BUFSIZE]; #ifdef HAVE_POPEN char command[BUFSIZE]; #endif char *selector; char *name; char *host; char *c; char type; int port; int exe; /* Prevent include loops */ if (depth > 4) return OK; /* Try to figure out whether the map is executable */ if (stat(mapfile, &file) == OK) { if ((file.st_mode & S_IXOTH)) { #ifdef HAVE_POPEN /* Quote the command in case path has spaces */ snprintf(command, sizeof(command), "'%s'", mapfile); #endif exe = TRUE; } else exe = FALSE; } /* This must be a shell include */ else { #ifdef HAVE_POPEN /* Let's assume the shell command runs as is without quoting */ sstrlcpy(command, mapfile); #endif exe = TRUE; } /* Debug output */ if (st->debug) { if (exe) syslog(LOG_INFO, "parsing executable gophermap \"%s\"", mapfile); else syslog(LOG_INFO, "parsing static gophermap \"%s\"", mapfile); } /* Try to execute or open the mapfile */ #ifdef HAVE_POPEN if (exe) { setenv_cgi(st, mapfile); if ((fp = popen(command, "r")) == NULL) return OK; } else #endif if ((fp = fopen(mapfile, "r")) == NULL) return OK; /* Read lines one by one */ while (fgets(line, sizeof(line) - 1, fp)) { /* Parse type & name */ chomp(line); type = line[0]; name = line + 1; /* Ignore #comments */ if (type == '#') continue; /* Stop handling gophermap? */ if (type == '*') return OK; if (type == '.') return QUIT; /* Print a list of users with public_gopher */ if (type == '~') { #ifdef HAVE_PASSWD userlist(st); #endif continue; } /* Print a list of available virtual hosts */ if (type == '%') { if (st->opt_vhost) vhostlist(st); continue; } /* Hide files in menus */ if (type == '-') { if (st->hidden_count < MAX_HIDDEN) sstrlcpy(st->hidden[st->hidden_count++], name); continue; } /* Override filetype mappings */ if (type == ':') { add_ftype_mapping(st, name); continue; } /* Include gophermap or shell exec */ if (type == '=') { gophermap(st, name, depth + 1); continue; } /* Title resource */ if (type == TYPE_TITLE) { info(st, name, TYPE_TITLE); continue; } /* Print out non-resources as info text */ if (!strchr(line, '\t')) { info(st, line, TYPE_INFO); continue; } /* Parse selector */ selector = EMPTY; if ((c = strchr(name, '\t'))) { *c = '\0'; selector = c + 1; } if (!*selector) selector = name; /* Parse host */ host = st->server_host; if ((c = strchr(selector, '\t'))) { *c = '\0'; host = c + 1; } /* Parse port */ port = st->server_port; if ((c = strchr(host, '\t'))) { *c = '\0'; port = atoi(c + 1); } /* Handle remote, absolute and hURL gopher resources */ if (sstrncmp(selector, "URL:") == MATCH || selector[0] == '/' || host != st->server_host) { printf("%c%s\t%s\t%s\t%i" CRLF, type, name, selector, host, port); } /* Handle relative resources */ else { printf("%c%s\t%s%s\t%s\t%i" CRLF, type, name, st->req_selector, selector, host, port); /* Automatically hide manually defined selectors */ #ifdef ENABLE_AUTOHIDING if (st->hidden_count < MAX_HIDDEN) sstrlcpy(st->hidden[st->hidden_count++], selector); #endif } } /* Clean up & return */ #ifdef HAVE_POPEN if (exe) pclose(fp); else #endif fclose(fp); return QUIT; }