btmux-0.6-rc4/doc/
btmux-0.6-rc4/event/
btmux-0.6-rc4/game/
btmux-0.6-rc4/game/maps/
btmux-0.6-rc4/game/mechs/
btmux-0.6-rc4/game/text/help/
btmux-0.6-rc4/game/text/help/cat_faction/
btmux-0.6-rc4/game/text/help/cat_inform/
btmux-0.6-rc4/game/text/help/cat_misc/
btmux-0.6-rc4/game/text/help/cat_mux/
btmux-0.6-rc4/game/text/help/cat_mux/cat_commands/
btmux-0.6-rc4/game/text/help/cat_mux/cat_functions/
btmux-0.6-rc4/game/text/help/cat_templates/
btmux-0.6-rc4/game/text/wizhelp/
btmux-0.6-rc4/include/
btmux-0.6-rc4/misc/
btmux-0.6-rc4/python/
btmux-0.6-rc4/src/hcode/btech/
btmux-0.6-rc4/tree/
/*
 * bsd.c 
 */

#include "copyright.h"
#include "config.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>

#include "mudconf.h"
#include "db.h"
#include "file_c.h"
#include "externs.h"
#include "interface.h"
#include "flags.h"
#include "powers.h"
#include "alloc.h"
#include "command.h"
#include "attrs.h"
#include "rbtree.h"
#include <errno.h>
#include "logcache.h"

#include "debug.h"

#if SQL_SUPPORT
#include "sqlchild.h"
#endif

extern void dispatch(void);
extern char *silly_atr_get(dbref, int);

struct event listen_sock_ev;

#ifdef IPV6_SUPPORT
struct event listen6_sock_ev;
#endif

int mux_bound_socket = -1;
#ifdef IPV6_SUPPORT
int mux_bound_socket6 = -1;
#endif
int ndescriptors = 0;

DESC *descriptor_list = NULL;

void mux_release_socket();
void make_nonblocking(int s);

DESC *initializesock(int, struct sockaddr_storage *, int);
DESC *new_connection(int);
int process_input(DESC *);

void accept_new_connection(int, short, void *);

void bind_descriptor(DESC *d) {
    d->refcount++;
#if 0
    dprintk("%p bound, count %d", d, d->refcount);
#endif
}

void release_descriptor(DESC *d) {
    d->refcount--;
#if 0
    dprintk("%p released, count %d", d, d->refcount);
#endif
    if(d->refcount == 0) {
        dprintk("%p destructing", d);
        freeqs(d);

		/*
		 * Is this desc still in interactive mode? 
		 */
		if(d->program_data != NULL) {
			int num = 0;
            DESC *dtemp;
			DESC_ITER_PLAYER(d->player, dtemp) num++;

			if(num == 0) {
				for(int i = 0; i < MAX_GLOBAL_REGS; i++) {
					free_lbuf(d->program_data->wait_regs[i]);
				}
				free(d->program_data);
			}
		}

		free(d);
    }
}


void set_lastsite(DESC * d, char *lastsite)
{
	char buf[LBUF_SIZE];

	if(d->player) {
		if(!lastsite)
			lastsite = silly_atr_get(d->player, A_LASTSITE);
		strcpy(buf, lastsite);
		atr_add_raw(d->player, A_LASTSITE, buf);
	}
}

void shutdown_services()
{
	dnschild_destruct();
	flush_sockets();
#ifdef SQL_SUPPORT
	sqlchild_destruct();
#endif
#ifdef ARBITRARY_LOGFILES
	logcache_destruct();
#endif
	event_loopexit(NULL);
}

int bind_mux_socket(int port)
{
	int s, opt;
	struct sockaddr_in server;

	s = socket(AF_INET, SOCK_STREAM, 0);
	if(s < 0) {
		log_perror("NET", "FAIL", NULL, "creating master socket");
		exit(3);
	}
	opt = 1;
	if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &opt,
				  sizeof(opt)) < 0) {
		log_perror("NET", "FAIL", NULL, "setsockopt");
	}
	if(fcntl(s, F_SETFD, FD_CLOEXEC) < 0) {
		log_perror("LOGCACHE", "FAIL", NULL,
				   "fcntl(fd, F_SETFD, FD_CLOEXEC)");
		close(s);
		abort();
	}
	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))) {
		log_perror("NET", "FAIL", NULL, "bind");
		close(s);
		exit(4);
	}
	dprintk("connection socket raised and bound, %d", s);
	listen(s, 5);
	return s;
}

void mux_release_socket()
{
	dprintk("releasing mux main socket.");
	event_del(&listen_sock_ev);
	close(mux_bound_socket);
	mux_bound_socket = -1;
#ifdef IPV6_SUPPORT
	event_del(&listen6_sock_ev);
	close(mux_bound_socket6);
	mux_bound_socket6 = -1;
#endif
}

int eradicate_broken_fd(int fd)
{
	struct stat statbuf;
	DESC *d;

	DESC_ITER_ALL(d) {
		if((fd && d->descriptor == fd) ||
		   (!fd && fstat(d->descriptor, &statbuf) < 0)) {
			/* An invalid player connection... eject, eject, eject. */
			log_error(LOG_PROBLEMS, "ERR", "EBADF",
					  "Broken descriptor %d for player #%d", d->descriptor,
					  d->player);
			shutdownsock(d, R_SOCKDIED);
		}
	}
	if(mux_bound_socket != -1 && fstat(mux_bound_socket, &statbuf) < 0) {
		log_error(LOG_PROBLEMS, "ERR", "EBADF",
				  "Broken descriptor on our main port.");
		mux_bound_socket = -1;
		return -1;
	}
#ifdef IPV6_SUPPORT
	if(mux_bound_socket6 != -1 && fstat(mux_bound_socket6, &statbuf) < 0) {
		log_error(LOG_PROBLEMS, "ERR", "EBADF",
				  "Broken descriptor for our ipv6 port.");
		mux_bound_socket6 = -1;
		return -1;
	}
#endif
	return 0;
}

void accept_client_input(int fd, short event, void *arg)
{
	DESC *connection = (DESC *) arg;

	if(connection->flags & DS_AUTODARK) {
		connection->flags &= ~DS_AUTODARK;
		s_Flags(connection->player, Flags(connection->player) & ~DARK);
	}

	if(!process_input(connection)) {
		shutdownsock(connection, R_SOCKDIED);
	}
}

void bsd_write_callback(struct bufferevent *bufev, void *arg)
{
}

void bsd_read_callback(struct bufferevent *bufev, void *arg)
{
}

void bsd_error_callback(struct bufferevent *bufev, short whut, void *arg)
{
	dprintk("error %d", whut);
}

#ifndef HAVE_GETTIMEOFDAY
#define get_tod(x)	{ (x)->tv_sec = time(NULL); (x)->tv_usec = 0; }
#else
#define get_tod(x)	gettimeofday(x, (struct timezone *)0)
#endif

struct timeval queue_slice = { 0, 0 };
struct event queue_ev;
struct timeval last_slice, current_time;

void runqueues(int fd, short event, void *arg)
{
	pid_t pchild;
	int status = 0;
	event_add(&queue_ev, &queue_slice);
	get_tod(&current_time);
	last_slice = update_quotas(last_slice, current_time);
	pchild = waitpid(-1, &status, WNOHANG);
	if(pchild > 0) {
		dprintk("unexpected child %d exited with exit status %d.", pchild,
				WEXITSTATUS(status));
	}
	if(mudconf.queue_chunk)
		do_top(mudconf.queue_chunk);
}

void shovechars(int port)
{

	queue_slice.tv_sec = 0;
	queue_slice.tv_usec = mudconf.timeslice * 1000;

	mudstate.debug_cmd = (char *) "< shovechars >";

	dprintk("shovechars starting, sock is %d.", mux_bound_socket);
#ifdef IPV6_SUPPORT
	dprintk("shovechars starting, ipv6 sock is %d.", mux_bound_socket);
#endif

	if(mux_bound_socket < 0) {
		mux_bound_socket = bind_mux_socket(port);
	}
	event_set(&listen_sock_ev, mux_bound_socket, EV_READ | EV_PERSIST,
			  accept_new_connection, NULL);
	event_add(&listen_sock_ev, NULL);

#ifdef IPV6_SUPPORT
	if(mux_bound_socket6 < 0) {
		mux_bound_socket6 = bind_mux6_socket(port);
	}
	event_set(&listen6_sock_ev, mux_bound_socket6, EV_READ | EV_PERSIST,
			  accept_new6_connection, NULL);
	event_add(&listen6_sock_ev, NULL);
#endif

	evtimer_set(&queue_ev, runqueues, NULL);
	evtimer_add(&queue_ev, &queue_slice);

	get_tod(&last_slice);
	get_tod(&current_time);

	event_dispatch();
}

void accept_new_connection(int sock, short event, void *arg)
{
	int newsock, addr_len, len;
	char *buff, *buff1, *cmdsave;
	DESC *d;
	struct sockaddr_storage addr;
	char addrname[1024];
	char addrport[32];

	cmdsave = mudstate.debug_cmd;
	mudstate.debug_cmd = (char *) "< new_connection >";

	addr_len = sizeof(struct sockaddr);

	newsock =
		accept(sock, (struct sockaddr *) &addr, (unsigned int *) &addr_len);
	if(newsock < 0)
		return;

	getnameinfo((struct sockaddr *) &addr, addr_len,
				addrname, 1024, addrport, 32,
				NI_NUMERICHOST | NI_NUMERICSERV);

	if(site_check(&addr, addr_len, mudstate.access_list) == H_FORBIDDEN) {
		log_error(LOG_NET | LOG_SECURITY, "NET", "SITE",
				  "Connection refused from %s %s.", addrname, addrport);

		fcache_rawdump(newsock, FC_CONN_SITE);
		shutdown(newsock, 2);
		close(newsock);
		errno = 0;
		d = NULL;
	} else {
		log_error(LOG_NET, "NET", "CONN", "Connection opened from %s %s.",
				  addrname, addrport);

		d = initializesock(newsock, &addr, addr_len);
	}
	mudstate.debug_cmd = cmdsave;
	return;
}

/*
 * Disconnect reasons that get written to the logfile 
 */

static const char *disc_reasons[] = {
	"Unspecified",
	"Quit",
	"Inactivity Timeout",
	"Booted",
	"Remote Close or Net Failure",
	"Game Shutdown",
	"Login Retry Limit",
	"Logins Disabled",
	"Logout (Connection Not Dropped)",
	"Too Many Connected Players"
};

/*
 * Disconnect reasons that get fed to A_ADISCONNECT via announce_disconnect 
 */

static const char *disc_messages[] = {
	"unknown",
	"quit",
	"timeout",
	"boot",
	"netdeath",
	"shutdown",
	"badlogin",
	"nologins",
	"logout"
};

void shutdownsock(DESC * d, int reason)
{
	char *buff, *buff2;
	time_t now;
	int i, num;
	DESC *dtemp;

	if((reason == R_LOGOUT) &&
	   (site_check(&d->saddr, d->saddr_len,
				   mudstate.access_list) == H_FORBIDDEN))
		reason = R_QUIT;

	if(d->flags & DS_CONNECTED) {
		if(d->outstanding_dnschild_query)
			dnschild_kill(d->outstanding_dnschild_query);

		/*
		 * Do the disconnect stuff if we aren't doing a LOGOUT * * *
		 * * * * (which keeps the connection open so the player can *
		 * * connect * * * * to a different character). 
		 */

		if(reason != R_LOGOUT) {
			fcache_dump(d, FC_QUIT);
		}
		log_error(LOG_NET | LOG_LOGIN, "NET", "DISC",
				  "[%d/%s] Logout by %s(#%d), <Reason: %s>",
				  d->descriptor, d->addr, Name(d->player), d->player,
				  disc_reasons[reason]);

		/*
		 * If requested, write an accounting record of the form: * *
		 * * * * * Plyr# Flags Cmds ConnTime Loc Money [Site]
		 * <DiscRsn>  * *  * Name 
		 */

		log_error(LOG_ACCOUNTING, "DIS", "ACCT",
				  "%d %s %d %d %d %d [%s] <%s> %s",
				  d->player, decode_flags(GOD, Flags(d->player),
										  Flags2(d->player),
										  Flags3(d->player)),
				  d->command_count, mudstate.now - d->connected_at,
				  Location(d->player), Pennies(d->player), d->addr,
				  disc_reasons[reason], Name(d->player));
		announce_disconnect(d->player, d, disc_messages[reason]);
	} else {
		if(reason == R_LOGOUT) {
			reason = R_QUIT;
			log_error(LOG_SECURITY | LOG_NET, "NET", "DISC",
					  "[%d/%s] Connection closed, never connected. <Reason: %s>",
					  d->descriptor, d->addr, disc_reasons[reason]);

		}
	}

	clearstrings(d);
	if(reason == R_LOGOUT) {
		d->flags &= ~DS_CONNECTED;
		d->connected_at = mudstate.now;
		d->retries_left = mudconf.retry_limit;
		d->command_count = 0;
		d->timeout = mudconf.idle_timeout;
		d->player = 0;
		d->doing[0] = '\0';
		d->hudkey[0] = '\0';
		d->quota = mudconf.cmd_quota_max;
		d->last_time = 0;
		d->host_info =
			site_check(&d->saddr, d->saddr_len, mudstate.access_list) |
			site_check(&d->saddr, d->saddr_len, mudstate.suspect_list);
		d->input_tot = d->input_size;
		d->output_tot = 0;
		welcome_user(d);
	} else {
		event_del(&d->sock_ev);
		shutdown(d->descriptor, 2);
		close(d->descriptor);
		bufferevent_free(d->sock_buff);

		*d->prev = d->next;
		if(d->next)
			d->next->prev = d->prev;
        d->flags |= DS_DEAD;
        release_descriptor(d);
		
		ndescriptors--;
	}
}

void make_nonblocking(int s)
{
	long flags = 0;

	if(fcntl(s, F_GETFL, &flags) < 0) {
		log_perror("NET", "FAIL", "make_nonblocking", "fcntl F_GETFL");
	}
	flags |= O_NONBLOCK;
	if(fcntl(s, F_SETFL, flags) < 0) {
		log_perror("NET", "FAIL", "make_nonblocking", "fcntl F_SETFL");
	}
	flags = 1;
	if(setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags)) < 0) {
		log_perror("NET", "FAIL", "make_nonblocking", "setsockopt NDELAY");
	}
}

void make_blocking(int s)
{
	long flags = 0;

	if(fcntl(s, F_GETFL, &flags) < 0) {
		log_perror("NET", "FAIL", "make_blocking", "fcntl F_GETFL");
	}
	flags &= ~O_NONBLOCK;
	if(fcntl(s, F_SETFL, flags) < 0) {
		log_perror("NET", "FAIL", "make_blocking", "fcntl F_SETFL");
	}
	flags = 0;
	if(setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags)) < 0) {
		log_perror("NET", "FAIL", "make_blocking", "setsockopt NDELAY");
	}
}
extern int fcache_conn_c;

DESC *initializesock(int s, struct sockaddr_storage *saddr, int saddr_len)
{
	DESC *d;

	ndescriptors++;
	d = malloc(sizeof(DESC));
	memset(d, 0, sizeof(DESC));

	d->descriptor = s;
	d->flags = 0;
	d->connected_at = mudstate.now;
	d->retries_left = mudconf.retry_limit;
	d->command_count = 0;
	d->timeout = mudconf.idle_timeout;
	d->host_info =
		site_check(saddr, saddr_len, mudstate.access_list) |
		site_check(saddr, saddr_len, mudstate.suspect_list);
	d->player = 0;				/*
								 * be sure #0 isn't wizard.  Shouldn't be. 
								 */
	d->chokes = 0;

	d->addr[0] = '\0';
	d->doing[0] = '\0';
	d->hudkey[0] = '\0';
	d->username[0] = '\0';
	make_nonblocking(s);
	d->output_prefix = NULL;
	d->output_suffix = NULL;
	d->output_size = 0;
	d->output_tot = 0;
	d->output_lost = 0;
	d->input_size = 0;
	d->input_tot = 0;
	d->input_lost = 0;
	d->raw_input_at = (char *) d->input;
	memset(d->input, 0, sizeof(d->input));
	d->quota = mudconf.cmd_quota_max;
	d->program_data = NULL;
	d->last_time = 0;
	memcpy(&d->saddr, saddr, saddr_len);
	d->saddr_len = saddr_len;
    d->refcount = 1;

	if(descriptor_list)
		descriptor_list->prev = &d->next;
	d->hashnext = NULL;
	d->next = descriptor_list;
	d->prev = &descriptor_list;
	getnameinfo((struct sockaddr *) saddr, saddr_len, d->addr,
				sizeof(d->addr), NULL, 0, NI_NUMERICHOST);
	descriptor_list = d;

	d->outstanding_dnschild_query = dnschild_request(d);

	d->sock_buff = bufferevent_new(d->descriptor, bsd_write_callback,
								   bsd_read_callback, bsd_error_callback,
								   NULL);
	bufferevent_disable(d->sock_buff, EV_READ);
	bufferevent_enable(d->sock_buff, EV_WRITE);
	event_set(&d->sock_ev, d->descriptor, EV_READ | EV_PERSIST,
			  accept_client_input, d);
	event_add(&d->sock_ev, NULL);

	welcome_user(d);
	return d;
}

int process_input(DESC * d)
{
	static char buf[LBUF_SIZE];
	int got, in, lost;
	char *p, *pend, *q, *qend;
	char *cmdsave;

	cmdsave = mudstate.debug_cmd;
	mudstate.debug_cmd = (char *) "< process_input >";


	got = in = read(d->descriptor, buf, (sizeof buf - 1));
	if(got <= 0) {
		if(errno == EINTR)
			return 1;
        else if(errno == EAGAIN)
            return 1;
		else
			return 0;
	}

    bind_descriptor(d);
    
	if(Wizard(d->player) && strncmp("@segfault", buf, 9) == 0) {
		queue_string(d, "@segfault failed. (check logfile for reason.)\n");
		*(char *) 0xDEADBEEF = '9';
	}

	buf[got] = 0;
	if(!d->raw_input_at)
		d->raw_input_at = (char *) d->input;
	p = d->raw_input_at;
	pend = (char *) d->input + sizeof(d->input) - 1;
	lost = 0;
	for(q = buf, qend = buf + got; q < qend; q++) {
		if(*q == '\n') {
			*p = '\0';
			if(p > (char *) d->input) {
                if(d->flags & DS_DEAD) {
                    dprintk("bailing '%s' on dead descriptor %p for user #%d", d->input, d, d->player);
                    break;
                }
				run_command(d, (char *) d->input);
				memset(d->input, 0, sizeof(d->input));
				p = d->raw_input_at = (char *) d->input;
				pend = (char *) d->input + sizeof(d->input) - 1;
			} else {
				in -= 1;		/*
								 * for newline 
								 */
			}
		} else if((*q == '\b') || (*q == 127)) {
			if(*q == 127)
				queue_string(d, "\b \b");
			else
				queue_string(d, " \b");
			in -= 2;
			if(p > (char *) d->input)
				p--;
			if(p < d->raw_input_at)
				(d->raw_input_at)--;
		} else if(p < pend && ((isascii(*q) && isprint(*q)) || *q < 0)) {
			*p++ = *q;
		} else {
			in--;
			if(p >= pend)
				lost++;
		}
	}
	if(p > (char *) d->input) {
		d->raw_input_at = p;
	}
	d->input_tot += got;
	d->input_size += in;
	d->input_lost += lost;
    release_descriptor(d);
	mudstate.debug_cmd = cmdsave;
	return 1;
}

void flush_sockets()
{
	int null = 0;
	DESC *d, *dnext;
	DESC_SAFEITER_ALL(d, dnext) {
		if(d->chokes) {
#if TCP_CORK
			setsockopt(d->descriptor, IPPROTO_TCP, TCP_CORK, &null,
					   sizeof(null));
#else
#ifdef TCP_NOPUSH
			setsockopt(d->descriptor, IPPROTO_TCP, TCP_NOPUSH, &null,
					   sizeof(null));
#endif
#endif
			d->chokes = 0;
		}
		if(d->sock_buff && EVBUFFER_LENGTH(d->sock_buff->output)) {
			dprintk("sock %d for user #%d output evbuffer misalign: %d, totallen: %d, off: %d, pending %d.",
				 (int) d->descriptor, 
                 (int) d->player,
                 (int) d->sock_buff->output->misalign,
				 (int) d->sock_buff->output->totallen,
				 (int) d->sock_buff->output->off, 
                 EVBUFFER_LENGTH(d->sock_buff->output));
			evbuffer_write(d->sock_buff->output, d->descriptor);
		}
		fsync(d->descriptor);
	}
}

void close_sockets(int emergency, char *message)
{
	DESC *d, *dnext;

	DESC_SAFEITER_ALL(d, dnext) {
		if(emergency) {
			WRITE(d->descriptor, message, strlen(message));
			if(shutdown(d->descriptor, 2) < 0)
				log_perror("NET", "FAIL", NULL, "shutdown");
			dprintk("shutting down fd %d", d->descriptor);
			dprintk("output evbuffer misalign: %d, totallen: %d, off: %d",
					d->sock_buff->output->misalign,
					d->sock_buff->output->totallen,
					d->sock_buff->output->off);
			fsync(d->descriptor);
			if(d->outstanding_dnschild_query)
				dnschild_kill(d->outstanding_dnschild_query);
			event_loop(EVLOOP_ONCE);
			event_del(&d->sock_ev);
			bufferevent_free(d->sock_buff);
			close(d->descriptor);
		} else {
			queue_string(d, message);
			queue_write(d, "\r\n", 2);
			shutdownsock(d, R_GOING_DOWN);
		}
	}
	close(mux_bound_socket);
	event_del(&listen_sock_ev);
}

void emergency_shutdown(void)
{
	close_sockets(1, (char *) "Going down - Bye.\n");
}