Example #1
0
/*
 * 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);
}
Example #2
0
/*
 * 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++;
	}
}
Example #3
0
/*
 * 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++;
	}
}
Example #4
0
/*
 * 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);
}
Example #5
0
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;
	}
}
Example #6
0
/*
 * 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");
}
Example #7
0
/*
 * 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;
}
Example #8
0
/*
 * 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);
	}
}
Example #9
0
/*
 * 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
}
Example #10
0
/*
 * 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;
}
Example #11
0
/*
 * 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++;
		}
	}
}
Example #12
0
/*
 * 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);
}
Example #13
0
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);
	}
}
Example #14
0
/*
 * 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);
}
Example #15
0
/*
 * 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;
}