/*
net.m Networking utility functions for cheezmud.
Copyright (C) 1995 David Flater.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* Networking utility functions. Some code fragments trace their origins
* through several generations back to LPMud by Lars Pensj|.
*
* This is an expurgated version of the code I used for Unetd, with the high
* level functions redone to support the mud. The low level functions have
* been working for a very long time and I have no intention of modifying them
* for reasons of aesthetics or efficiency.
*/
#include "net.h"
#include "cheezmud.h"
struct conlist *
findcon ();
void
flush_write ();
void
cat_text_file ();
void
lost_player ();
int insocket=-1, nfds=-1;
fd_set fdset, wrset, tfdset, twrset;
static struct conlist *cons = NULL;
long messbufsize;
char *messbuf;
void *
safmalloc (size_t bytes)
{
void *a;
assert (a = (void *) malloc (bytes));
return a;
}
void
append_to_queue (char **buff, long *length, char *newdata, long newlength, long *memlength)
{
if (*buff == NULL)
{
assert (!(*length));
*buff = (char *) safmalloc (newlength);
*length = newlength;
memcpy (*buff, newdata, newlength);
}
else
{
*length += newlength;
if (*memlength < *length)
{
/* Doing lots of reallocs seems to be a bad thing. This should reduce
* the number of reallocs.
*/
*memlength = *length << 1;
assert ((*buff = (char *) realloc (*buff, *memlength)));
}
memcpy (*buff + *length - newlength, newdata, newlength);
}
}
/* Remove data from the front of a queue */
void
shorten_queue (char **buff, long *length, long remove_amount, long *memlength)
{
assert (remove_amount <= *length);
*length -= remove_amount;
if (*length)
memcpy (*buff, (*buff)+remove_amount, *length);
else
{
free (*buff);
*buff = NULL;
*memlength = 0;
}
}
/* Get rid of trailing CR's and LF's */
char *
noeol (char *s)
{
char *a;
a = &(s[strlen(s)]);
while (a > s)
if (*(a-1) == '\n' || *(a-1) == '\r')
*(--a) = '\0';
else
break;
return s;
}
char *
get_password (char *player)
{
FILE *fp;
static char passwd[80];
char buf[80], buf2[80];
assert (fp = fopen ("etc/passwd", "rt"));
while (fgets (buf, 80, fp))
{
assert (sscanf (buf, "%s", buf2) == 1);
if (!strcmp (buf2, player))
{
fclose (fp);
strcpy (passwd, buf+strlen(buf2)+1);
return (noeol (passwd));
}
}
fclose (fp);
return NULL;
}
/************* Message Handlers ************/
void
bitbucket ();
void
newhandler ();
void
authhandler ();
void
playerhandler ();
/* The /dev/null of message handlers. Connections being deleted use this. */
void
bitbucket (char *buf, struct conlist *t)
{
;
}
void
newhandler (char *buf, struct conlist *t)
{
if (buf[0] == '\0')
{
text_sock_write (t, "login: ");
return;
}
t->name = strdup (buf);
t->type = auth;
t->handler = authhandler;
text_sock_write (t, "password: ");
}
void
authhandler (char *buf, struct conlist *t)
{
struct conlist *u;
char *p;
char temp[160];
if (!(p = get_password (t->name)))
{
text_sock_write (t, "Wrong.\n");
delmark (t);
sprintf (temp, "Bad login from %s: %s/%s", t->hname, t->name, buf);
cheezlog (temp);
return;
}
if (strcmp (p, buf))
{
text_sock_write (t, "Wrong.\n");
delmark (t);
sprintf (temp, "Bad login from %s: %s/%s", t->hname, t->name, buf);
cheezlog (temp);
return;
}
u = findcon (t->name);
if (u)
{
text_sock_write (t, "Usurping existing character.\n");
text_sock_write (u, "Your character has been usurped.\n");
t->mudplayer = u->mudplayer;
u->mudplayer = NULL;
delmark (u);
[t->mudplayer setty: t];
}
else
{
if (strcmp (t->name, mudadmin)) {
t->mudplayer = [Player new];
t->maxidle = 3600;
}
else {
t->mudplayer = [Mudadmin new];
t->maxidle = 0;
}
[[[t->mudplayer setmudname: t->name] setty: t] load];
}
t->type = player;
t->handler = playerhandler;
t->notify = lost_player;
text_sock_write (t, "> ");
sprintf (temp, "%s logged in from %s", t->name, t->hname);
cheezlog (temp);
}
void
playerhandler (char *buf, struct conlist *t)
{
if (strlen(buf) > 80)
buf[80] = '\0';
if (buf[0] != '\0') {
if (!([t->mudplayer ohce: buf]))
text_sock_write (t, "Can't do that.\n");
}
text_sock_write (t, "> ");
}
/************* Loss Notification Handlers ************/
void
no_notify (struct conlist *t)
{
;
}
void
lost_player (struct conlist *t)
{
if (t->mudplayer)
[t->mudplayer logout];
}
/***************/
/* Mark a connection for deletion. It's added to wrset to insure that the
* deletion will occur in a timely fashion.
*/
void
delmark (struct conlist *t)
{
t->type = delete;
t->handler = bitbucket;
FD_CLR (t->con, &fdset);
FD_SET (t->con, &wrset);
}
/* Initialize the conlist structure for new connections and add to conlist.
* Fields which are very connection-dependent are not set here.
*/
void
init_conlist (struct conlist *temp)
{
temp->next = cons;
cons = temp;
temp->type = new;
temp->handler = newhandler;
temp->inbuffer = temp->yettosend = NULL;
temp->inbuflen = temp->inmemsize = temp->sendlen = temp->sendmemsize = 0;
temp->notify = no_notify;
temp->lastrecv = time(NULL);
temp->maxidle = 60;
temp->name = NULL;
temp->mudplayer = NULL;
}
/* Set options for most sockets we will use.
* -- Non-blocking I/O
* -- Keep connections alive
*/
void
sockopt (int socket)
{
/* struct protoent *tcp; */
int tmp = 1;
/* Set non-blocking I/O. */
#if 0
assert (ioctl(socket, FIONBIO, &tmp) != -1);
#endif
#ifdef __hpux
assert (fcntl (socket, F_SETFL, O_NONBLOCK) != -1);
#else
assert (fcntl (socket, F_SETFL, O_NDELAY) != -1);
#endif
/* Socket-level option to keep connections "warm" */
tmp = 1;
if (setsockopt (socket, SOL_SOCKET, SO_KEEPALIVE, (char *) &tmp,
sizeof (tmp)))
{
perror ("setsockopt");
assert (0);
}
}
void
powerup ()
{
struct sockaddr_in sin;
int tmp;
sig_setup ();
messbufsize = 1000;
messbuf = safmalloc (messbufsize);
FD_ZERO (&fdset);
FD_ZERO (&wrset);
memset ((char *) &sin, '\0', sizeof sin);
sin.sin_family = (short)AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
assert ((insocket = socket (AF_INET, SOCK_STREAM, 0)) != -1);
tmp = 1;
if (setsockopt (insocket, SOL_SOCKET, SO_REUSEADDR,
(char *) &tmp, sizeof (tmp)))
{
perror ("setsockopt");
assert (0);
}
sin.sin_port = htons (inport_number);
assert (bind (insocket, (struct sockaddr *)(&sin), sizeof sin) != -1);
sockopt (insocket);
assert (listen (insocket, 5) != -1);
FD_SET (insocket, &fdset);
nfds = max (insocket, nfds);
}
int
get_message (char **messbuf, long *size, struct conlist *t)
{
long l, m;
char *buff;
buff = *messbuf;
if (t->maxidle)
if (time(NULL) - t->lastrecv > t->maxidle) {
text_sock_write (t, "You snooze, you lose.\n");
return -1;
}
/* Try to read new data. */
unbreak_pipe ();
l = read (t->con, buff, (*size)-1);
if (broke_pipe ())
{
return -1;
}
if (l <= -1)
{
if (errno != EWOULDBLOCK && errno != EAGAIN)
{
fflush (stdout);
perror ("read");
fflush (stderr);
return -1;
}
l = 0;
}
else if (!l)
{
return -1;
}
else
t->lastrecv = time(NULL);
buff[l] = '\0';
/* Append new packet to buffered data */
if (l)
append_to_queue (&(t->inbuffer), &(t->inbuflen), buff, l, &(t->inmemsize));
/* Try to return messages */
if ((m = (int) memchr (t->inbuffer, '\n', t->inbuflen)))
{
m = m - (int)(t->inbuffer) + 1;
if (m >= *size)
{
*size = m+1;
assert (*messbuf = (char *) realloc (*messbuf, *size));
}
buff = *messbuf;
memcpy (buff, t->inbuffer, m);
buff[m] = '\0';
noeol (buff);
}
/* Remove received data from the input buffer */
if (m)
{
shorten_queue (&(t->inbuffer), &(t->inbuflen), m, &(t->inmemsize));
return 1;
}
/* Sorry, no donuts today. */
return 0;
}
/* Write raw data (no translations) onto a socket, or queue it up at least. */
void
bin_sock_write (struct conlist *t, char *data, long len)
{
append_to_queue (&(t->yettosend), &(t->sendlen), data, len,
&(t->sendmemsize));
flush_write (t);
}
/* Translate EOLs */
void
text_sock_write (struct conlist *t, char *buff)
{
char *buff2;
long length, from, to;
length = strlen (buff);
buff2 = (char *) safmalloc (length*2+1);
for (from=0,to=0; buff[from] != '\0';)
{
if (buff[from] == '\n')
{
/* Avoid duplication */
if (to == 0)
buff2[to++] = '\r';
else if (buff2[to-1] != '\r')
buff2[to++] = '\r';
}
buff2[to++] = buff[from++];
}
buff2[to] = '\0'; /* Just in case */
bin_sock_write (t, buff2, to);
free (buff2);
return;
}
/* Check for new connections and accept them. */
int openfiles=0;
void
accept_new_cons ()
{
int fd;
struct sockaddr addr;
int length = sizeof addr;
if ((fd = accept (insocket, &addr, &length)) == -1)
{
if (errno != EWOULDBLOCK && errno != EAGAIN)
{
if (errno == EMFILE || errno == ENFILE)
{
if (!openfiles)
{
openfiles = 1;
fflush (stdout);
perror ("accept");
fflush (stderr);
puts ("^^^^ This error will only be reported once to avoid filling the log file...");
}
}
else
{
fflush (stdout);
perror ("accept");
fflush (stderr);
}
}
}
else
{
struct hostent *hp;
struct conlist *temp;
struct sockaddr_in *u = (struct sockaddr_in *)(&addr);
unsigned long a = u->sin_addr.s_addr;
char temp2[160];
sockopt (fd);
temp = (struct conlist *) safmalloc (sizeof (struct conlist));
init_conlist (temp);
FD_SET (fd, &fdset);
nfds = max (fd, nfds);
temp->con = fd;
if (!(hp = gethostbyaddr ((const char *)(&a), sizeof a, AF_INET)))
strcpy (temp->hname, inet_ntoa (u->sin_addr));
else
strcpy (temp->hname, hp->h_name);
sprintf (temp2, "Connection from %s", temp->hname);
cheezlog (temp2);
/* Say Hi */
cat_text_file (temp, "etc/motd");
text_sock_write (temp, "login: ");
/* See if there are any more waiting */
accept_new_cons ();
}
}
/* Close and destroy a connection */
void
remove_con (struct conlist *delme)
{
struct conlist *p, *t;
if (cons->con == delme->con)
cons = delme->next;
else
{
p = cons;
t = p->next;
while (t)
{
if (t->con == delme->con)
{
p->next = t->next;
break;
}
p = t;
t = t->next;
}
assert (t);
}
FD_CLR (delme->con, &fdset);
FD_CLR (delme->con, &wrset);
close (delme->con);
if (delme->inbuffer)
free (delme->inbuffer);
if (delme->yettosend)
free (delme->yettosend);
if (delme->name)
free (delme->name);
/* Mudplayer is freed by main.m */
/* if (delme->mudplayer) */
/* [delme->mudplayer free]; */
free (delme);
}
void
cat_text_file (struct conlist *t, char *fname)
{
FILE *fp;
if ((fp = fopen (fname, "r")))
{
char buf[1000];
while (fgets (buf, 1000, fp))
text_sock_write (t, buf);
fclose (fp);
}
}
/* Actually send queued data (at last) */
void
flush_write (struct conlist *t)
{
long a;
/* Refuse to write to a socket which is marked for deletion. */
if (t->type == delete)
{
/* Dump data */
shorten_queue (&(t->yettosend), &(t->sendlen), t->sendlen, &(t->sendmemsize));
return;
}
unbreak_pipe ();
a = write (t->con, t->yettosend, (unsigned) (t->sendlen));
if (broke_pipe ())
{
delmark (t);
return;
}
if (a < 0)
{
if (errno != EWOULDBLOCK && errno != EAGAIN)
{
fflush (stdout);
perror ("write");
fflush (stderr);
delmark (t);
return;
}
return;
}
if (a)
shorten_queue (&(t->yettosend), &(t->sendlen), a, &(t->sendmemsize));
/* Remove from wrset those connections which have been flushed.
*/
if (t->type != delete)
if ((!(t->sendlen)) && FD_ISSET (t->con, &wrset))
FD_CLR (t->con, &wrset);
/* Add to wrset those connections which have outstanding junk. */
if ((t->sendlen) && (!(FD_ISSET (t->con, &wrset))))
FD_SET (t->con, &wrset);
}
/* Find a player's connection */
struct conlist *
findcon (char *dude)
{
struct conlist *a = cons;
while (a)
{
if (strcmp (a->name, dude) == 0 && a->type == player)
break;
a = a->next;
}
return a;
}
void
service_cons ()
{
int busy;
struct conlist *t;
/* This program performs internal buffering to break packets into separate
* commands and to coalesce them into large data objects. It is necessary to
* loop here until all outstanding data have been read since data in our
* internal buffers will not trigger the select.
*/
do {
/* This is about as far down as I feel comfortable putting the heartbeat. */
/* This way, everybody gets at least one round-robin shot at typing */
/* something between heartbeats. */
do_heartbeats ();
busy = 0;
accept_new_cons ();
t = cons;
while (t)
{
if (t->type == delete)
{
struct conlist *u = t->next;
(*(t->notify)) (t);
remove_con (t);
t = u;
}
else
{
if (t->sendlen)
flush_write (t);
/* flush_write can delmark t */
if (t->type != delete)
{
/* Try to read */
switch (get_message (&messbuf, &messbufsize, t)) {
case -1:
/* Connection has gone amok -- close it */
delmark (t);
break;
case 0:
/* No traffic */
break;
case 1:
/* Got a message */
busy = 1;
(*(t->handler)) (messbuf, t);
}
}
t = t->next;
}
}
} while (busy);
}
void
wait_for_activity ()
{
int ts;
struct timeval tv;
tfdset = fdset; twrset = wrset;
/* We want to try to keep up the heartbeats */
/* NOTE: There is a good reason that the minimum for tv is 0/1, not 0/0. */
/* _Bad_ things happen when it's zero! */
tv.tv_sec = 0;
tv.tv_usec = min (1, (heartbeat_microseconds - timesincelastheartbeat ()));
ts = select (nfds+1, &tfdset, &twrset, &tfdset, &tv);
if (ts < 0)
{
fflush (stdout);
perror ("select");
fflush (stderr);
/* DON'T PANIC! Any old signal will cause this. */
}
}
void
show_users (struct conlist *tty)
{
char temp[80];
int l;
struct conlist *t = cons;
text_sock_write (tty, "Cheezmud User Level Minutes Idle\n");
text_sock_write (tty, "====================================\n");
while (t) {
switch (t->type) {
case player:
l = [(t->mudplayer) level];
if (l == -1)
sprintf (temp, " Admin %.2f\n",
(float)(time(NULL) - t->lastrecv) / 60.0);
else
sprintf (temp, " %5d %.2f\n",
l, (float)(time(NULL) - t->lastrecv) / 60.0);
strncpy (temp, t->name, min (strlen (t->name), 13));
text_sock_write (tty, temp);
break;
case new:
case auth:
sprintf (temp, "<login> %.2f\n",
(float)(time(NULL) - t->lastrecv) / 60.0);
text_sock_write (tty, temp);
break;
case delete:
;
}
t = t->next;
}
}
void
de_notify (struct conlist *t)
{
t->notify = no_notify;
}