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