/* * 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; }