/*-------------------------------------------------------*/
/* util/bgopherd.c ( NTHU CS MapleBBS Ver 2.39 ) */
/*-------------------------------------------------------*/
/* target : simple gopher daemon for BBS */
/* create : 95/03/29 */
/* update : 96/10/07 */
/*-------------------------------------------------------*/
#include "bbs.h"
#include <sys/wait.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <syslog.h>
#define GOPHER_PORT 70
#define GOPHER_HOME (BBSHOME "/man")
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 1024
#endif
#define TCP_BUFSIZ 4096
#define TCP_QLEN 5
#define GOPHER_LOGFILE "/var/run/bgopherd.log"
#define GOPHER_PIDFILE "/var/run/bgopherd.pid"
static struct sockaddr_in xsin;
static int rfds; /* read file descriptor set */
dashf(fname)
char *fname;
{
struct stat st;
return (stat(fname, &st) == 0 && S_ISREG(st.st_mode));
}
/* ----------------------------------------------------- */
/* operation log and debug information */
/* ----------------------------------------------------- */
int flog; /* log file descriptor */
void
logit(key, msg)
char *key;
char *msg;
{
time_t now;
struct tm *p;
char buf[256];
time(&now);
p = localtime(&now);
sprintf(buf, "%02d/%02d/%02d %02d:%02d:%02d %-7s%s\n",
p->tm_year, p->tm_mon + 1, p->tm_mday,
p->tm_hour, p->tm_min, p->tm_sec, key, msg);
write(flog, buf, strlen(buf));
}
void
log_init()
{
flog = open(GOPHER_LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0644);
logit("START", "gopher daemon");
}
void
log_close()
{
close(flog);
}
#if 0
/* ----------------------------------------------------- */
/* 取得 remote user name 以判定身份 */
/* ----------------------------------------------------- */
/*
* rfc931() speaks a common subset of the RFC 931, AUTH, TAP, IDENT and RFC
* 1413 protocols. It queries an RFC 931 etc. compatible daemon on a remote
* host to look up the owner of a connection. The information should not be
* used for authentication purposes. This routine intercepts alarm signals.
*
* Diagnostics are reported through syslog(3).
*
* Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
*/
#include <setjmp.h>
#define STRN_CPY(d,s,l) { strncpy((d),(s),(l)); (d)[(l)-1] = 0; }
#define STRING_LENGTH 60
#define RFC931_TIMEOUT 10
#define RFC931_PORT 113 /* Semi-well-known port */
#define ANY_PORT 0 /* Any old port will do */
/* ------------------------- */
/* timeout - handle timeouts */
/* ------------------------- */
static jmp_buf timebuf;
static void
timeout(sig)
int sig;
{
longjmp(timebuf, sig);
}
void
getremotename(from, rhost, rname)
struct sockaddr_in *from;
char *rhost;
char *rname;
{
struct sockaddr_in our_sin;
struct sockaddr_in rmt_sin;
unsigned rmt_port, rmt_pt;
unsigned our_port, our_pt;
FILE *fp;
char buffer[512], user[80], *cp;
int s;
struct hostent *hp;
/* get remote host name */
hp = NULL;
if (setjmp(timebuf) == 0)
{
signal(SIGALRM, timeout);
alarm(3);
hp = gethostbyaddr((char *) &from->sin_addr, sizeof(struct in_addr),
from->sin_family);
alarm(0);
}
strcpy(rhost, hp ? hp->h_name : (char *) inet_ntoa(from->sin_addr));
/*
* Use one unbuffered stdio stream for writing to and for reading from the
* RFC931 etc. server. This is done because of a bug in the SunOS 4.1.x
* stdio library. The bug may live in other stdio implementations, too.
* When we use a single, buffered, bidirectional stdio stream ("r+" or "w+"
* mode) we read our own output. Such behaviour would make sense with
* resources that support random-access operations, but not with sockets.
*/
*rname = '\0';
s = sizeof our_sin;
if (getsockname(0, &our_sin, &s) < 0)
return;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
logit("ERROR", "socket in rfc931");
return;
}
if (!(fp = fdopen(s, "r+")))
{
close(s);
return;
}
/*
* Set up a timer so we won't get stuck while waiting for the server.
*/
if (setjmp(timebuf) == 0)
{
signal(SIGALRM, timeout);
alarm(RFC931_TIMEOUT);
/*
* Bind the local and remote ends of the query socket to the same IP
* addresses as the connection under investigation. We go through all
* this trouble because the local or remote system might have more than
* one network address. The RFC931 etc. client sends only port numbers;
* the server takes the IP addresses from the query socket.
*/
our_pt = ntohs(our_sin.sin_port);
our_sin.sin_port = htons(ANY_PORT);
rmt_sin = *from;
rmt_pt = ntohs(rmt_sin.sin_port);
rmt_sin.sin_port = htons(RFC931_PORT);
setbuf(fp, (char *) 0);
s = fileno(fp);
if (bind(s, (struct sockaddr *) & our_sin, sizeof(our_sin)) >= 0 &&
connect(s, (struct sockaddr *) & rmt_sin, sizeof(rmt_sin)) >= 0)
{
/*
* Send query to server. Neglect the risk that a 13-byte write would
* have to be fragmented by the local system and cause trouble with
* buggy System V stdio libraries.
*/
fprintf(fp, "%u,%u\r\n", rmt_pt, our_pt);
fflush(fp);
/*
* Read response from server. Use fgets()/sscanf() so we can work
* around System V stdio libraries that incorrectly assume EOF when a
* read from a socket returns less than requested.
*/
if (fgets(buffer, sizeof(buffer), fp) && !ferror(fp) && !feof(fp)
&& sscanf(buffer, "%u , %u : USERID :%*[^:]:%79s",
&rmt_port, &our_port, user) == 3
&& rmt_pt == rmt_port && our_pt == our_port)
{
/*
* Strip trailing carriage return. It is part of the protocol, not
* part of the data.
*/
if (cp = strchr(user, '\r'))
*cp = 0;
strcpy(rname, user);
}
}
alarm(0);
}
fclose(fp);
}
#endif
/* ----------------------------------------------------- */
/* buffered TCP I/O routines */
/* ----------------------------------------------------- */
int tcp_pos;
char tcp_pool[TCP_BUFSIZ + 512];
void
tcp_init()
{
tcp_pos = 0;
}
int
tcp_puts(sock, msg)
int sock;
char *msg;
{
int pos, len;
char *head, *tail;
len = strlen(msg);
pos = tcp_pos;
head = tcp_pool;
tail = head + pos;
memcpy(tail, msg, len);
memcpy(tail + len, "\r\n", 2);
pos += (len + 2);
while (pos >= TCP_BUFSIZ)
{
len = write(sock, head, TCP_BUFSIZ);
if (len <= 0)
return 0;
pos -= len;
if (pos == 0)
break;
memcpy(head, head + len, pos);
}
tcp_pos = pos;
return len;
}
int
tcp_flush(sock, msg)
int sock;
char *msg;
{
int pos, len;
if (msg != NULL)
{
len = tcp_puts(sock, msg);
if (len == 0)
return 0;
}
pos = tcp_pos;
if (pos == 0)
return 0;
msg = tcp_pool;
for (;;)
{
len = write(sock, msg, pos);
if (len <= 0)
return 0;
pos -= len;
if (pos == 0)
{
tcp_pos = 0;
return len;
}
msg += len;
}
}
/* ----------------------------------------------------- */
/* transform to real path & security check */
/* ----------------------------------------------------- */
int
real_path(path)
char *path;
{
int ch, level;
char *source, *target;
if (*path == '/')
return 0;
level = 1;
source = target = path;
for (;;)
{
ch = *source;
if (ch == '/')
{
int next;
next = source[1];
if (next == '/')
{
return 0; /* [//] */
}
else if (next == '.')
{
next = source[2];
if (next == '/')
return 0; /* [/./] */
if (next == '.' && source[3] == '/')
{
/* -------------------------- */
/* abc/xyz/../def ==> abc/def */
/* -------------------------- */
for (;;)
{
if (target <= path)
return 0;
target--;
if (*target == '/')
break;
}
source += 3;
continue;
}
}
level++;
}
*target = ch;
if (ch == 0)
return level;
target++;
source++;
}
}
/* ----------------------------------------------------- */
/* send file out in text-mail format */
/* ----------------------------------------------------- */
void
send_file(sock, fpath)
int sock;
char *fpath;
{
FILE *fp;
struct stat st;
if (!stat(fpath, &st) && S_ISREG(st.st_mode) && (fp = fopen(fpath, "r")))
{
int ch;
char *ip, *p, buf[512];
ip = buf;
while (fgets(ip, 511, fp))
{
for (p = ip; ch = *p; p++)
{
if (ch == '\n' || ch == '\r')
{
*p = '\0';
break;
}
}
if (*ip == '.' && ip[1] == '\0')
tcp_puts(sock, "..");
else
tcp_puts(sock, ip);
}
fclose(fp);
}
}
/* ----------------------------------------------------- */
/* transform .Names to gopher index format & send it out */
/* ----------------------------------------------------- */
a_timestamp(buf, time)
char *buf;
time_t *time;
{
struct tm *pt = localtime(time);
sprintf(buf, "%02d/%02d/%02d", pt->tm_mon + 1, pt->tm_mday, pt->tm_year);
}
void
send_index(int sock, char* fpath, char* plus)
{
int mode;
FILE *fp;
static char gtitle[80], gserver[80], gport[8];
char *ptr, buf[1024];
char outbuf[2048];
fileheader fhdr;
time_t dtime;
char date[20];
#ifdef DEBUG
logit("index", fpath);
#endif
sprintf(outbuf, "%s.DIR", fpath);
if (fp = fopen(outbuf, "r")) {
while (fread(&fhdr, sizeof(fhdr), 1, fp) == 1) {
strcpy(gtitle, fhdr.title);
gethostname(gserver, MAXHOSTNAMELEN);
strcpy(gport, "70");
sprintf(outbuf, "%s%s", fpath, fhdr.filename);
mode = dashf(outbuf) ? 0 : 1;
sprintf(outbuf, "%c/%s%s", mode + '0', fpath, fhdr.filename);
if (!real_path(outbuf))
continue;
ptr = buf;
strcpy(ptr, outbuf);
dtime = atoi(fhdr.filename + 2);
a_timestamp(date, &dtime);
sprintf(outbuf, "%s%c%-43.42s%-13s[%s]\t%s\t%s\t%s",
plus, '0' + mode, gtitle, fhdr.owner, date, ptr, gserver, gport);
tcp_puts(sock, outbuf);
}
fclose(fp);
}
}
/* ----------------------------------------------------- */
/* parse client's command and serve it */
/* ----------------------------------------------------- */
void
serve(sock)
int sock;
{
register char *ptr;
int n, ch;
char buf[1024];
ptr = buf;
n = read(sock, ptr, 1024);
if (n < 0)
return;
ptr[n] = '\0';
while (ch = *ptr)
{
if (ch == '\r' || ch == '\n')
{
*ptr = '\0';
n = ptr - buf;
break;
}
ptr++;
}
ptr = buf;
ch = *ptr;
if (ch == 0)
ch = '1';
logit("cmd", ptr);
tcp_init();
switch (ch)
{
case '0':
ptr += 2;
if (real_path(ptr))
send_file(sock, ptr);
break;
case '1':
if (n <= 2)
*ptr = '\0';
else
{
strcpy(ptr + n, "/");
ptr += 2;
}
send_index(sock, ptr, "");
break;
case '\t':
tcp_puts(sock, "+-1");
send_index(sock, "", "+INFO: ");
break;
default:
return;
}
tcp_flush(sock, ".");
}
/* ----------------------------------------------------- */
/* daemon : initialize and other stuff */
/* ----------------------------------------------------- */
start_daemon()
{
int n, on;
char buf[80];
struct sockaddr_in fsin;
/*
* More idiot speed-hacking --- the first time conversion makes the C
* library open the files containing the locale definition and time zone.
* If this hasn't happened in the parent process, it happens in the
* children, once per connection --- and it does add up.
*/
time_t dummy = time(NULL);
struct tm *dummy_time = localtime(&dummy);
struct tm *other_dummy_time = gmtime(&dummy);
strftime(buf, 80, "%d/%b/%Y:%H:%M:%S", dummy_time);
n = getdtablesize();
if (fork())
exit(0);
while (n)
(void) close(--n);
n = open(GOPHER_PIDFILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (n >= 0)
{
sprintf(buf, "%5d\n", getpid());
write(n, buf, 6);
close(n);
}
(void) open("/dev/null", O_RDONLY);
(void) dup2(0, 1);
(void) dup2(0, 2);
if ((n = open("/dev/tty", O_RDWR)) > 0)
{
ioctl(n, TIOCNOTTY, 0);
close(n);
}
openlog("bgopherd", LOG_PID, LOG_AUTH);
syslog(LOG_NOTICE, "start\n");
}
int
bind_port(port)
int port;
{
int sock, on;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
syslog(LOG_NOTICE, "socket\n");
exit(1);
}
on = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0)
syslog(LOG_ERR, "(SO_REUSEADDR): %m");
on = 0;
#if 0 /* 0825 */
on = 4096;
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *) &on, sizeof(on));
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *) &on, sizeof(on));
#endif
xsin.sin_port = htons(port);
if (bind(sock, (struct sockaddr *)&xsin, sizeof xsin) < 0) {
syslog(LOG_INFO, "bgopherd bind_port can't bind to %d",port);
exit(1);
}
if (listen(sock, TCP_QLEN) < 0) {
syslog(LOG_INFO, "bgopherd bind_port can't listen to %d",port);
exit(1);
}
FD_SET(sock, (fd_set *) & rfds);
return sock;
}
#if 0
void
reaper()
{
int state, pid;
while ((pid = waitpid(-1, &state, WNOHANG | WUNTRACED)) > 0);
}
#endif
void
abort_server()
{
log_close();
exit(1);
}
void
reapchild()
{
int state, pid;
/* signal(SIGCHLD, reapchild); */
while ((pid = waitpid(-1, &state, WNOHANG | WUNTRACED)) > 0);
}
main(int argc, char** argv)
{
int msock, csock, ofds, nfds, sr;
extern int errno;
fd_set rset;
struct timeval tv;
pid_t pid, slot;
start_daemon();
signal(SIGCHLD, reapchild);
memset(&xsin, 0, sizeof(xsin));
xsin.sin_family = AF_INET;
rfds = 0;
log_init();
(void) signal(SIGHUP, abort_server);
#if 0
(void) signal(SIGCHLD, reaper);
#endif
if (argc > 1)
{
msock = -1;
for (nfds = 1; nfds < argc; nfds++)
{
csock = atoi(argv[nfds]);
if (csock > 0)
msock = bind_port(csock);
else
break;
}
if (msock < 0) {
syslog(LOG_INFO, "bgopherd started with invalid arguments (no port)");
exit(1);
}
}
else {
static int ports[] = {70 /*, 4001, 4002, 4003, 4004, 4005 */ };
for (nfds = 0; nfds < sizeof(ports) / sizeof(int); nfds++)
{
msock = bind_port(ports[nfds]);
}
}
setgid(BBSGID);
setuid(BBSUID);
chdir(GOPHER_HOME);
/* main loop */
ofds = rfds;
nfds = msock + 1;
for (;;)
{
forever:
rfds = ofds;
tv.tv_sec = 60 * 30;
tv.tv_usec = 0;
msock = select(nfds, (fd_set *) & rfds, NULL, NULL, &tv);
if (msock <= 0)
continue;
msock = 0;
csock = 1;
for (;;)
{
if (csock & rfds)
break;
if (++msock >= nfds)
goto forever;
csock <<= 1;
}
slot = sizeof(xsin);
do
{
csock = accept(msock, (struct sockaddr *)&xsin, (int*)&slot);
} while (csock < 0 && errno == EINTR);
if (csock < 0)
continue;
pid = fork();
if (!pid)
{
while (--nfds >= 0)
close(nfds);
close(msock);
serve(csock);
/*
shutdown(csock, 2);
close(csock);
*/
exit(0);
}
else
close(csock);
}
}