/* xsocket.c */
#include "copyright.h"
/* Patched 5/3/90 by Scott C. Goehring (gt4@mentor.cc.purdue.edu) to */
/* display hostnames instead of octets in the log and to display the */
/* hostname to wizards on a WHO command. */
/* Repatched 5/7/90 by Russ Smith (russ@uokmax.ecn.uoknor.edu) to */
/* make the hostnames show only to ID #1 and to reverse the WHO list */
/* (that is, newest login last) as it should be. ;-) */
/* Patched again 5/10/90 by Scott Goehring to not reverse the WHO */
/* list (which is REALLY how it should be :-)), to use a tabular */
/* format for WHO, and to regularize Russ' GOD mode patches */
/* Patches cleaned up 5/20/90 by Jim Aspnes */
#include <stdio.h>
#include <sys/types.tcp.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/errno.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include "db.h"
#include "interface.h"
#include "config.h"
extern int errno;
extern int reserved;
int shutdown_flag = 0;
static const char *connect_fail = "Either that player does not exist, or has a different password.\n";
static const char *create_fail = "Either there is already a player with that name, or that name is illegal.\n";
static const char *flushed_message = "<Output Flushed>\n";
static const char *shutdown_message = "Going down - Bye\n";
struct text_block {
int nchars;
struct text_block *nxt;
char *start;
char *buf;
};
struct text_queue {
struct text_block *head;
struct text_block **tail;
};
struct descriptor_data {
int descriptor;
int connected;
char addr[51];
dbref player;
char *output_prefix;
char *output_suffix;
int output_size;
struct text_queue output;
struct text_queue input;
char *raw_input;
char *raw_input_at;
long connected_at;
long last_time;
int quota;
struct sockaddr_in address; /* added 3/6/90 SCG */
struct descriptor_data *next;
struct descriptor_data **prev;
} *descriptor_list = 0;
static int sock;
static int ndescriptors = 0;
char ccom[1024];
dbref cplr;
void process_commands();
void shovechars();
void shutdownsock();
struct descriptor_data *initializesock();
void make_nonblocking();
void freeqs();
void welcome_user();
void check_connect();
void close_sockets();
const char *addrout();
void dump_users();
void set_signals();
struct descriptor_data *new_connection();
void parse_connect();
void set_userstring();
int do_command();
char *strsave();
int make_socket();
int queue_string();
int queue_write();
int process_output();
int process_input();
int bailout();
void announce_connect();
void announce_disconnect();
const char *time_format_1();
const char *time_format_2();
#define MALLOC(result, type, number) do { \
if (!((result) = (type *) malloc ((number) * sizeof (type)))) \
panic("Out of memory"); \
} while (0)
#define FREE(x) (free((void *) x))
#ifndef BOOLEXP_DEBUGGING
void main(argc, argv)
int argc;
char **argv;
{
/* save a file descriptor */
reserved = open("/dev/null", O_RDWR);
if (argc < 3) {
fprintf(stderr, "Usage: %s infile dumpfile [port]\n", *argv);
exit(1);
}
if (init_game(argv[1], argv[2]) < 0) {
fprintf(stderr, "Couldn't load %s!\n", argv[1]);
exit(2);
}
setpgrp();
set_signals();
/* go do it */
shovechars(argc >= 4 ? atoi(argv[3]) : TINYPORT);
close_sockets();
dump_database();
exit(0);
}
#endif /* BOOLEXP_DEBUGGING */
void set_signals()
{
int dump_status();
/* we don't care about SIGPIPE, we notice it in select() and write() */
signal(SIGPIPE, SIG_IGN);
/* standard termination signals */
signal(SIGINT, bailout);
signal(SIGTERM, bailout);
/* catch these because we might as well */
/*
* signal (SIGQUIT, bailout); signal (SIGILL, bailout); signal (SIGTRAP,
* bailout); signal (SIGIOT, bailout); signal (SIGEMT, bailout); signal
* (SIGFPE, bailout); signal (SIGBUS, bailout); signal (SIGSEGV, bailout);
* signal (SIGSYS, bailout); signal (SIGTERM, bailout); signal (SIGXCPU,
* bailout); signal (SIGXFSZ, bailout); signal (SIGVTALRM, bailout); signal
* (SIGUSR2, bailout);
*/
/* want a core dump for now!! */
/* status dumper (predates "WHO" command) */
signal(SIGUSR1, dump_status);
}
void raw_notify(player, msg)
dbref player;
const char *msg;
{
struct descriptor_data *d;
if (!(db[player].flags & PLAYER_CONNECT))
return;
for (d = descriptor_list; d; d = d->next) {
if (d->connected && d->player == player) {
queue_string(d, msg);
queue_write(d, "\n", 1);
}
}
}
struct timeval timeval_sub(now, then)
struct timeval now;
struct timeval then;
{
now.tv_sec -= then.tv_sec;
now.tv_usec -= then.tv_usec;
if (now.tv_usec < 0) {
now.tv_usec += 1000000;
now.tv_sec--;
}
return now;
}
int msec_diff(now, then)
struct timeval now;
struct timeval then;
{
return ((now.tv_sec - then.tv_sec) * 1000
+ (now.tv_usec - then.tv_usec) / 1000);
}
struct timeval msec_add(t, x)
struct timeval t;
int x;
{
t.tv_sec += x / 1000;
t.tv_usec += (x % 1000) * 1000;
if (t.tv_usec >= 1000000) {
t.tv_sec += t.tv_usec / 1000000;
t.tv_usec = t.tv_usec % 1000000;
}
return t;
}
struct timeval update_quotas(last, current)
struct timeval last;
struct timeval current;
{
int nslices;
struct descriptor_data *d;
nslices = msec_diff(current, last) / COMMAND_TIME_MSEC;
if (nslices > 0) {
for (d = descriptor_list; d; d = d->next) {
d->quota += COMMANDS_PER_TIME * nslices;
if (d->quota > COMMAND_BURST_SIZE)
d->quota = COMMAND_BURST_SIZE;
}
}
return msec_add(last, nslices * COMMAND_TIME_MSEC);
}
void shovechars(port)
int port;
{
fd_set input_set, output_set;
long now;
struct timeval last_slice, current_time;
struct timeval next_slice;
struct timeval timeout, slice_timeout;
int maxd, found;
struct descriptor_data *d, *dnext;
struct descriptor_data *newd;
int avail_descriptors;
sock = make_socket(port);
maxd = sock + 1;
gettimeofday(&last_slice, (struct timezone *) 0);
avail_descriptors = getdtablesize() - 4;
while (shutdown_flag == 0) {
gettimeofday(¤t_time, (struct timezone *) 0);
last_slice = update_quotas(last_slice, current_time);
process_commands();
if (shutdown_flag)
break;
/* test for events */
dispatch();
/* any queued robot commands waiting? */
timeout.tv_sec = test_top() ? 0 : 1000;
timeout.tv_usec = 0;
next_slice = msec_add(last_slice, COMMAND_TIME_MSEC);
slice_timeout = timeval_sub(next_slice, current_time);
FD_ZERO(&input_set);
FD_ZERO(&output_set);
if (ndescriptors < avail_descriptors)
FD_SET(sock, &input_set);
for (d = descriptor_list; d; d = d->next) {
if (d->input.head)
timeout = slice_timeout;
else
FD_SET(d->descriptor, &input_set);
if (d->output.head)
FD_SET(d->descriptor, &output_set);
}
if ((found = select(maxd, &input_set, &output_set,
(fd_set *) 0, &timeout)) < 0) {
if (errno != EINTR) {
perror("select");
return;
}
} else {
/* if !found then time for robot commands */
if (!found) {
do_top() && do_top() && do_top();
continue;
}
(void) time(&now);
if (FD_ISSET(sock, &input_set)) {
if (!(newd = new_connection(sock))) {
if (errno
&& errno != EINTR
&& errno != EMFILE
&& errno != ENFILE) {
perror("new_connection");
return;
}
} else {
if (newd->descriptor >= maxd)
maxd = newd->descriptor + 1;
}
}
for (d = descriptor_list; d; d = dnext) {
dnext = d->next;
if (FD_ISSET(d->descriptor, &input_set)) {
d->last_time = now;
if (!process_input(d)) {
shutdownsock(d);
continue;
}
}
if (FD_ISSET(d->descriptor, &output_set)) {
if (!process_output(d)) {
shutdownsock(d);
}
}
}
}
}
}
struct descriptor_data *new_connection(sock)
int sock;
{
int newsock;
struct sockaddr_in addr;
int addr_len;
addr_len = sizeof(addr);
newsock = accept(sock, (struct sockaddr *) & addr, &addr_len);
if (newsock < 0) {
return 0;
#ifdef LOCKOUT
} else if (forbidden_site(ntohl(addr.sin_addr.s_addr))) {
fprintf(stderr, "REFUSED CONNECTION from %s(%d) on descriptor %d\n",
addrout(addr.sin_addr.s_addr),
ntohs(addr.sin_port), newsock);
shutdown(newsock, 2);
close(newsock);
errno = 0;
return 0;
#endif /* LOCKOUT */
} else {
struct hostent *hent;
char buff[100];
long tt;
hent = gethostbyaddr(&(addr.sin_addr.s_addr),
sizeof(addr.sin_addr.s_addr), AF_INET);
if (hent)
strcpy(buff, hent->h_name);
else
strcpy(buff, inet_ntoa(addr.sin_addr.s_addr));
time(&tt);
fprintf(stderr, "USER CONNECT: des: %d host %s time: %s", newsock, buff, ctime(&tt));
return initializesock(newsock, &addr, buff);
}
}
const char *addrout(a)
long a;
{
/* extern char *inet_ntoa(); */
#ifdef HOST_NAME
struct hostent *he;
a = htonl(a);
he = gethostbyaddr(&a, sizeof(a), AF_INET);
if (he)
return he->h_name;
else
return inet_ntoa(a);
#else
/* return inet_ntoa(a); */ return ("OOPS");
#endif /* HOST_NAME */
}
void clearstrings(d)
struct descriptor_data *d;
{
if (d->output_prefix) {
FREE(d->output_prefix);
d->output_prefix = 0;
}
if (d->output_suffix) {
FREE(d->output_suffix);
d->output_suffix = 0;
}
}
void shutdownsock(d)
struct descriptor_data *d;
{
if (d->connected) {
fprintf(stderr, "DISCONNECT descriptor %d player %s(%d)\n",
d->descriptor, db[d->player].name, d->player);
announce_disconnect(d->player);
} else {
fprintf(stderr, "DISCONNECT descriptor %d never connected\n",
d->descriptor);
}
clearstrings(d);
shutdown(d->descriptor, 2);
close(d->descriptor);
freeqs(d);
*d->prev = d->next;
if (d->next)
d->next->prev = d->prev;
FREE(d);
ndescriptors--;
}
struct descriptor_data *initializesock(s, a, addr)
int s;
struct sockaddr_in *a;
char *addr;
{
struct descriptor_data *d;
/* fprintf(stderr,"3\n");fflush(stderr); */
ndescriptors++;
MALLOC(d, struct descriptor_data, 1);
d->descriptor = s;
d->connected = 0;
make_nonblocking(s);
d->output_prefix = 0;
d->output_suffix = 0;
d->output_size = 0;
d->output.head = 0;
d->output.tail = &d->output.head;
d->input.head = 0;
d->input.tail = &d->input.head;
d->raw_input = 0;
d->raw_input_at = 0;
d->quota = COMMAND_BURST_SIZE;
d->last_time = 0;
strncpy(d->addr, addr, 50);
d->address = *a; /* added 5/3/90 SCG */
if (descriptor_list)
descriptor_list->prev = &d->next;
d->next = descriptor_list;
d->prev = &descriptor_list;
descriptor_list = d;
/* fprintf(stderr,"4\n");fflush(stderr); */
welcome_user(d);
/* fprintf(stderr,"5\n");fflush(stderr); */
return d;
}
int make_socket(port)
int port;
{
int s;
struct sockaddr_in server;
int opt;
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
perror("creating stream socket");
exit(3);
}
opt = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
(char *) &opt, sizeof(opt)) < 0) {
perror("setsockopt");
exit(1);
}
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(port);
if (bind(s, (struct sockaddr *) & server, sizeof(server))) {
perror("binding stream socket");
close(s);
exit(4);
}
listen(s, 5);
return s;
}
struct text_block *make_text_block(s, n)
const char *s;
int n;
{
struct text_block *p;
MALLOC(p, struct text_block, 1);
MALLOC(p->buf, char, n);
bcopy(s, p->buf, n);
p->nchars = n;
p->start = p->buf;
p->nxt = 0;
return p;
}
void free_text_block(t)
struct text_block *t;
{
FREE(t->buf);
FREE((char *) t);
}
void add_to_queue(q, b, n)
struct text_queue *q;
const char *b;
int n;
{
struct text_block *p;
if (n == 0)
return;
p = make_text_block(b, n);
p->nxt = 0;
*q->tail = p;
q->tail = &p->nxt;
}
int flush_queue(q, n)
struct text_queue *q;
int n;
{
struct text_block *p;
int really_flushed = 0;
n += strlen(flushed_message);
while (n > 0 && (p = q->head)) {
n -= p->nchars;
really_flushed += p->nchars;
q->head = p->nxt;
free_text_block(p);
}
p = make_text_block(flushed_message, strlen(flushed_message));
p->nxt = q->head;
q->head = p;
if (!p->nxt)
q->tail = &p->nxt;
really_flushed -= p->nchars;
return really_flushed;
}
int queue_write(d, b, n)
struct descriptor_data *d;
const char *b;
int n;
{
int space;
space = MAX_OUTPUT - d->output_size - n;
if (space < 0)
d->output_size -= flush_queue(&d->output, -space);
add_to_queue(&d->output, b, n);
d->output_size += n;
return n;
}
int queue_string(d, s)
struct descriptor_data *d;
const char *s;
{
return queue_write(d, s, strlen(s));
}
int process_output(d)
struct descriptor_data *d;
{
struct text_block **qp, *cur;
int cnt;
for (qp = &d->output.head; cur = *qp;) {
cnt = write(d->descriptor, cur->start, cur->nchars);
if (cnt < 0) {
/* !!!!!! needs investigation !!!!! */
/* if (errno == EWOULDBLOCK) */
return 1;
return 0;
}
d->output_size -= cnt;
if (cnt == cur->nchars) {
if (!cur->nxt)
d->output.tail = qp;
*qp = cur->nxt;
free_text_block(cur);
continue; /* do not adv ptr */
}
cur->nchars -= cnt;
cur->start += cnt;
break;
}
return 1;
}
void make_nonblocking(s)
int s;
{
if (fcntl(s, F_SETFL, FNDELAY) == -1) {
perror("make_nonblocking: fcntl");
panic("FNDELAY fcntl failed");
}
}
void freeqs(d)
struct descriptor_data *d;
{
struct text_block *cur, *next;
cur = d->output.head;
while (cur) {
next = cur->nxt;
free_text_block(cur);
cur = next;
}
d->output.head = 0;
d->output.tail = &d->output.head;
cur = d->input.head;
while (cur) {
next = cur->nxt;
free_text_block(cur);
cur = next;
}
d->input.head = 0;
d->input.tail = &d->input.head;
if (d->raw_input)
FREE(d->raw_input);
d->raw_input = 0;
d->raw_input_at = 0;
}
void welcome_user(d)
struct descriptor_data *d;
{
queue_string(d, WELCOME_MESSAGE);
}
char *strsave(s)
const char *s;
{
char *p;
MALLOC(p, char, strlen(s) + 1);
if (p)
strcpy(p, s);
return p;
}
void save_command(d, command)
struct descriptor_data *d;
const char *command;
{
add_to_queue(&d->input, command, strlen(command) + 1);
}
int process_input(d)
struct descriptor_data *d;
{
char buf[1024];
int got;
char *p, *pend, *q, *qend;
got = read(d->descriptor, buf, sizeof buf);
if (got <= 0)
return 0;
if (!d->raw_input) {
MALLOC(d->raw_input, char, MAX_COMMAND_LEN);
d->raw_input_at = d->raw_input;
}
p = d->raw_input_at;
pend = d->raw_input + MAX_COMMAND_LEN - 1;
for (q = buf, qend = buf + got; q < qend; q++) {
if (*q == '\n') {
*p = '\0';
if (p > d->raw_input)
save_command(d, d->raw_input);
p = d->raw_input;
} else if (p < pend && isascii(*q) && isprint(*q)) {
*p++ = *q;
}
}
if (p > d->raw_input) {
d->raw_input_at = p;
} else {
FREE(d->raw_input);
d->raw_input = 0;
d->raw_input_at = 0;
}
return 1;
}
void set_userstring(userstring, command)
char **userstring;
const char *command;
{
if (*userstring) {
FREE(*userstring);
*userstring = 0;
}
while (*command && isascii(*command) && isspace(*command))
command++;
if (*command)
*userstring = strsave(command);
}
void process_commands()
{
int nprocessed;
struct descriptor_data *d, *dnext;
struct text_block *t;
do {
nprocessed = 0;
for (d = descriptor_list; d; d = dnext) {
dnext = d->next;
if (d->quota > 0 && (t = d->input.head)) {
d->quota--;
nprocessed++;
if (!do_command(d, t->start)) {
shutdownsock(d);
} else {
d->input.head = t->nxt;
if (!d->input.head)
d->input.tail = &d->input.head;
free_text_block(t);
}
}
}
} while (nprocessed > 0);
}
int do_command(d, command)
struct descriptor_data *d;
char *command;
{
depth = 0;
if (!strcmp(command, QUIT_COMMAND)) {
return 0;
} else if (!strcmp(command, WHO_COMMAND)) {
if (d->output_prefix) {
queue_string(d, d->output_prefix);
queue_write(d, "\n", 1);
}
dump_users(d);
if (d->output_suffix) {
queue_string(d, d->output_suffix);
queue_write(d, "\n", 1);
}
} else if (!strncmp(command, PREFIX_COMMAND, strlen(PREFIX_COMMAND))) {
set_userstring(&d->output_prefix, command + strlen(PREFIX_COMMAND));
} else if (!strncmp(command, SUFFIX_COMMAND, strlen(SUFFIX_COMMAND))) {
set_userstring(&d->output_suffix, command + strlen(SUFFIX_COMMAND));
} else {
if (d->connected) {
if (d->output_prefix) {
queue_string(d, d->output_prefix);
queue_write(d, "\n", 1);
}
cplr = d->player;
strcpy(ccom, command);
process_command(d->player, command, d->player);
if (d->output_suffix) {
queue_string(d, d->output_suffix);
queue_write(d, "\n", 1);
}
} else {
check_connect(d, command);
}
}
return 1;
}
void check_connect(d, msg)
struct descriptor_data *d;
const char *msg;
{
char command[MAX_COMMAND_LEN];
char user[MAX_COMMAND_LEN];
char password[MAX_COMMAND_LEN];
dbref player;
parse_connect(msg, command, user, password);
if (!strncmp(command, "co", 2)) {
player = connect_player(user, password);
if (player == NOTHING) {
queue_string(d, connect_fail);
fprintf(stderr, "FAILED CONNECT %s on descriptor %d\n",
user, d->descriptor);
} else {
fprintf(stderr, "CONNECTED %s(%d) on descriptor %d\n",
db[player].name, player, d->descriptor);
d->connected = 1;
d->connected_at = time(0);
d->player = player;
announce_connect(player);
do_look_around(player);
}
} else if (!strncmp(command, "cr", 2)) {
player = create_player(user, password);
if (player == NOTHING) {
queue_string(d, create_fail);
fprintf(stderr, "FAILED CREATE %s on descriptor %d\n",
user, d->descriptor);
} else {
fprintf(stderr, "CREATED %s(%d) on descriptor %d\n",
db[player].name, player, d->descriptor);
d->connected = 1;
d->connected_at = time(0);
d->player = player;
announce_connect(player);
do_look_around(player);
}
} else {
welcome_user(d);
}
}
void parse_connect(msg, command, user, pass)
const char *msg;
char *command;
char *user;
char *pass;
{
char *p;
while (*msg && isascii(*msg) && isspace(*msg))
msg++;
p = command;
while (*msg && isascii(*msg) && !isspace(*msg))
*p++ = *msg++;
*p = '\0';
while (*msg && isascii(*msg) && isspace(*msg))
msg++;
p = user;
while (*msg && isascii(*msg) && !isspace(*msg))
*p++ = *msg++;
*p = '\0';
while (*msg && isascii(*msg) && isspace(*msg))
msg++;
p = pass;
while (*msg && isascii(*msg) && !isspace(*msg))
*p++ = *msg++;
*p = '\0';
}
void close_sockets()
{
struct descriptor_data *d, *dnext;
for (d = descriptor_list; d; d = dnext) {
dnext = d->next;
write(d->descriptor, shutdown_message, strlen(shutdown_message));
if (shutdown(d->descriptor, 2) < 0)
perror("shutdown");
close(d->descriptor);
}
close(sock);
}
void emergency_shutdown()
{
close_sockets();
}
void boot_off(player)
dbref player;
{
struct descriptor_data *d;
for (d = descriptor_list; d; d = d->next) {
if (d->connected && d->player == player) {
shutdownsock(d);
return;
}
}
}
int bailout(sig, code, scp)
int sig;
int code;
struct sigcontext *scp;
{
char message[1024];
sprintf(message, "BAILOUT: caught signal %d code %d", sig, code);
panic(message);
_exit(7);
return 0;
}
int dump_status()
{
struct descriptor_data *d;
long now;
(void) time(&now);
fprintf(stderr, "STATUS REPORT:\n");
for (d = descriptor_list; d; d = d->next) {
if (d->connected) {
fprintf(stderr, "PLAYING descriptor %d player %s(%d)",
d->descriptor, db[d->player].name, d->player);
if (d->last_time)
fprintf(stderr, " idle %d seconds\n",
now - d->last_time);
else
fprintf(stderr, " never used\n");
} else {
fprintf(stderr, "CONNECTING descriptor %d", d->descriptor);
if (d->last_time)
fprintf(stderr, " idle %d seconds\n",
now - d->last_time);
else
fprintf(stderr, " never used\n");
}
}
return 0;
}
void dump_users(e)
struct descriptor_data *e;
{
struct descriptor_data *d;
long now;
char buf[1024];
time(&now);
queue_string(e, "Player Name On For Idle\n");
for (d = descriptor_list; d; d = d->next) {
if (d->connected) {
sprintf(buf, "%-16s %10s %4s",
db[d->player].name,
time_format_1(now - d->connected_at),
time_format_2(now - d->last_time));
if (e->connected && Wizard(e->player)) {
sprintf(buf + strlen(buf),
" [%s]", d->addr);
}
queue_string(e, buf);
queue_write(e, "\n", 1);
}
}
}
const char *time_format_1(dt)
long dt;
{
#ifdef kjkjkjk
register struct tm *delta;
static char buf[64];
if (dt < 0)
dt = 0;
delta = gmtime(&dt);
if (delta->tm_yday > 0) {
sprintf(buf, "%dd %02d:%02d",
delta->tm_yday, delta->tm_hour, delta->tm_min);
} else {
sprintf(buf, "%02d:%02d",
delta->tm_hour, delta->tm_min);
}
return buf;
#endif
return ("");
}
const char *time_format_2(dt)
long dt;
{
#ifdef klklk
register struct tm *delta;
static char buf[64];
if (dt < 0)
dt = 0;
delta = gmtime(&dt);
if (delta->tm_yday > 0) {
sprintf(buf, "%dd", delta->tm_yday);
} else if (delta->tm_hour > 0) {
sprintf(buf, "%dh", delta->tm_hour);
} else if (delta->tm_min > 0) {
sprintf(buf, "%dm", delta->tm_min);
} else {
sprintf(buf, "%ds", delta->tm_sec);
}
return buf;
#endif
return ("");
}
void announce_connect(player)
dbref player;
{
dbref loc;
char buf[BUFFER_LEN];
if ((loc = getloc(player)) == NOTHING)
return;
sprintf(buf, "%s has connected.", db[player].name);
notify_except(db[loc].contents, player, buf);
db[player].flags |= PLAYER_CONNECT;
}
void announce_disconnect(player)
dbref player;
{
dbref loc;
int num;
char buf[BUFFER_LEN];
struct descriptor_data *d;
if ((loc = getloc(player)) == NOTHING)
return;
sprintf(buf, "%s has disconnected.", db[player].name);
notify_except(db[loc].contents, player, buf);
for (num = 0, d = descriptor_list; d; d = d->next)
if (d->connected && (d->player == player))
num++;
if (num < 2)
db[player].flags &= ~PLAYER_CONNECT;
}