//*****************************************************************************
//
// socket.c
//
// This file contains the socket code, used for accepting new connections as
// well as reading and writing to sockets, and closing down unused sockets.
//
//*****************************************************************************
#include "wrapsock.h"
#include <netdb.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <zlib.h>
#include <pthread.h>
#include "mud.h"
#include "character.h"
#include "account.h"
#include "save.h"
#include "utils.h"
#include "socket.h"
#include "auxiliary.h"
#include "hooks.h"
#include "scripts/scripts.h"
#include "scripts/pyplugs.h"
//*****************************************************************************
// optional modules
//*****************************************************************************
#ifdef MODULE_ALIAS
#include "alias/alias.h"
#endif
// provides a unique identifier number to every socket that connects to the
// mud. Used mostly for referring to sockets in Python
#define START_SOCK_UID 1
int next_sock_uid = START_SOCK_UID;
//
// Here it is... the big ol' datastructure for sockets. Yum.
struct socket_data {
CHAR_DATA * player;
ACCOUNT_DATA * account;
char * hostname;
char inbuf[MAX_INPUT_LEN];
char next_command[MAX_BUFFER];
bool cmd_read;
bool bust_prompt;
bool closed;
int lookup_status;
int control;
int uid;
char * page_string; // the string that has been paged to us
int curr_page; // the current page we're on
int tot_pages; // the total number of pages the string has
BUFFER * text_editor; // where we do our actual work
BUFFER * outbuf; // our buffer of pending output
LIST * input_handlers;// a stack of our input handlers and prompts
LIST * input; // lines of input we have received
LIST * command_hist; // the commands we've executed in the past
unsigned char compressing; /* MCCP support */
z_stream * out_compress; /* MCCP support */
unsigned char * out_compress_buf; /* MCCP support */
HASHTABLE * auxiliary; // auxiliary data installed by other modules
};
//
// contains an input handler and the socket prompt in one structure, so they
// can be stored together in the socket_data. Allows for the option of Python
// or C input handlers and prompt pairs.
typedef struct input_handler_data {
void *handler; // (* handler)(SOCKET_DATA *, char *);
void *prompt; // (* prompt)(SOCKET_DATA *);
bool python;
} IH_PAIR;
//
// required for looking up a socket's IP in a new thread
typedef struct lookup_data {
SOCKET_DATA * dsock; // the socket we wish to do a hostlookup on
char * buf; // the buffer it should be stored in
} LOOKUP_DATA;
/* global variables */
fd_set fSet; /* the socket list for polling */
fd_set rFd;
/* mccp support */
const unsigned char compress_will [] = { IAC, WILL, TELOPT_COMPRESS, '\0' };
const unsigned char compress_will2 [] = { IAC, WILL, TELOPT_COMPRESS2, '\0' };
/*
* Init_socket()
*
* Used at bootup to get a free
* socket to run the server from.
*/
int init_socket()
{
struct sockaddr_in my_addr;
int sockfd, reuse = 1;
/* let's grab a socket */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
/* setting the correct values */
my_addr.sin_family = AF_INET;
my_addr.sin_addr.s_addr = INADDR_ANY;
my_addr.sin_port = htons(mudport);
/* this actually fixes any problems with threads */
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) == -1)
{
perror("Error in setsockopt()");
exit(1);
}
/* bind the port */
bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr));
/* start listening already :) */
listen(sockfd, 3);
/* return the socket */
return sockfd;
}
/*
* New_socket()
*
* Initializes a new socket, get's the hostname
* and puts it in the active socket_list.
*/
SOCKET_DATA *new_socket(int sock)
{
struct sockaddr_in sock_addr;
pthread_attr_t attr;
pthread_t thread_lookup;
LOOKUP_DATA * lData;
SOCKET_DATA * sock_new;
int argp = 1;
socklen_t size;
/* initialize threads */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
/* create and clear the socket */
sock_new = calloc(1, sizeof(SOCKET_DATA));
/* attach the new connection to the socket list */
FD_SET(sock, &fSet);
/* clear out the socket */
clear_socket(sock_new, sock);
sock_new->closed = FALSE;
/* set the socket as non-blocking */
ioctl(sock, FIONBIO, &argp);
/* update the socket list and table */
listPut(socket_list, sock_new);
propertyTablePut(sock_table, sock_new);
/* do a host lookup */
size = sizeof(sock_addr);
if (getpeername(sock, (struct sockaddr *) &sock_addr, &size) < 0)
{
perror("New_socket: getpeername");
sock_new->hostname = strdup("unknown");
}
else
{
/* set the IP number as the temporary hostname */
sock_new->hostname = strdup(inet_ntoa(sock_addr.sin_addr));
if (!compares(sock_new->hostname, "127.0.0.1"))
{
/* allocate some memory for the lookup data */
if ((lData = malloc(sizeof(*lData))) == NULL)
{
bug("New_socket: Cannot allocate memory for lookup data.");
abort();
}
/* Set the lookup_data for use in lookup_address() */
lData->buf = strdup((char *) &sock_addr.sin_addr);
lData->dsock = sock_new;
/* dispatch the lookup thread */
pthread_create(&thread_lookup, &attr, &lookup_address, (void*) lData);
}
else sock_new->lookup_status++;
}
/* negotiate compression */
text_to_buffer(sock_new, (char *) compress_will2);
text_to_buffer(sock_new, (char *) compress_will);
/* send the greeting */
text_to_buffer(sock_new, bufferString(greeting));
/* everything went as it was supposed to */
return sock_new;
}
/*
* Close_socket()
*
* Will close one socket directly, freeing all
* resources and making the socket availably on
* the socket free_list.
*/
void close_socket(SOCKET_DATA *dsock, bool reconnect)
{
if (dsock->lookup_status > TSTATE_DONE) return;
dsock->lookup_status += 2;
/* remove the socket from the polling list */
FD_CLR(dsock->control, &fSet);
/* remove ourself from the list */
//
// NO! We don't want to remove ourself from the list just yet.
// We will do that the next time we show up in the game_loop.
// removing now will not close the socket completely. Why, I'm not
// entirely sure... but it doesn't ;)
// - Geoff, Dec26/04
//
// listRemove(socket_list, dsock);
//
// we have a character, and it's one that's
// not in the process of being created
if (dsock->player && charGetUID(dsock->player) != NOBODY) {
if (reconnect)
text_to_socket(dsock, "This connection has been taken over.\r\n");
else {
charSetSocket(dsock->player, NULL);
log_string("Closing link to %s", charGetName(dsock->player));
}
}
else if(dsock->player) {
charSetSocket(dsock->player, NULL);
extract_mobile(dsock->player);
}
if(dsock->account) {
if(account_exists(accountGetName(dsock->account)))
unreference_account(dsock->account);
else
deleteAccount(dsock->account);
}
/* set the closed state */
dsock->closed = TRUE;
// dsock->state = STATE_CLOSED;
}
/*
* Read_from_socket()
*
* Reads one line from the socket, storing it
* in a buffer for later use. Will also close
* the socket if it tries a buffer overflow.
*/
bool read_from_socket(SOCKET_DATA *dsock)
{
int size;
extern int errno;
/* check for buffer overflows, and drop connection in that case */
size = strlen(dsock->inbuf);
if (size >= sizeof(dsock->inbuf) - 2)
{
text_to_socket(dsock, "\n\r!!!! Input Overflow !!!!\n\r");
return FALSE;
}
/* start reading from the socket */
for (;;)
{
int sInput;
int wanted = sizeof(dsock->inbuf) - 2 - size;
sInput = read(dsock->control, dsock->inbuf + size, wanted);
if (sInput > 0)
{
size += sInput;
if (dsock->inbuf[size-1] == '\n' || dsock->inbuf[size-1] == '\r')
break;
}
else if (sInput == 0)
{
log_string("Read_from_socket: EOF");
return FALSE;
}
else if (errno == EAGAIN || sInput == wanted)
break;
else
{
perror("Read_from_socket");
return FALSE;
}
}
dsock->inbuf[size] = '\0';
return TRUE;
}
/*
* Text_to_socket()
*
* Sends text directly to the socket,
* will compress the data if needed.
*/
bool text_to_socket(SOCKET_DATA *dsock, const char *txt)
{
int iBlck, iPtr, iWrt = 0, length, control = dsock->control;
length = strlen(txt);
/* write compressed */
if (dsock && dsock->out_compress)
{
dsock->out_compress->next_in = (unsigned char *) txt;
dsock->out_compress->avail_in = length;
while (dsock->out_compress->avail_in)
{
dsock->out_compress->avail_out = COMPRESS_BUF_SIZE - (dsock->out_compress->next_out - dsock->out_compress_buf);
if (dsock->out_compress->avail_out)
{
int status = deflate(dsock->out_compress, Z_SYNC_FLUSH);
if (status != Z_OK)
return FALSE;
}
length = dsock->out_compress->next_out - dsock->out_compress_buf;
if (length > 0)
{
for (iPtr = 0; iPtr < length; iPtr += iWrt)
{
iBlck = UMIN(length - iPtr, 4096);
if ((iWrt = write(control, dsock->out_compress_buf + iPtr, iBlck)) < 0)
{
perror("Text_to_socket (compressed):");
return FALSE;
}
}
if (iWrt <= 0) break;
if (iPtr > 0)
{
if (iPtr < length)
memmove(dsock->out_compress_buf, dsock->out_compress_buf + iPtr, length - iPtr);
dsock->out_compress->next_out = dsock->out_compress_buf + length - iPtr;
}
}
}
return TRUE;
}
/* write uncompressed */
for (iPtr = 0; iPtr < length; iPtr += iWrt)
{
iBlck = UMIN(length - iPtr, 4096);
if ((iWrt = write(control, txt + iPtr, iBlck)) < 0)
{
perror("Text_to_socket:");
return FALSE;
}
}
return TRUE;
}
void send_to_socket( SOCKET_DATA *dsock, const char *format, ...) {
if(format && *format) {
static char buf[MAX_BUFFER];
va_list args;
va_start(args, format);
vsprintf(buf, format, args);
va_end(args);
text_to_buffer(dsock, buf);
}
}
/*
* Text_to_buffer()
*
* Stores outbound text in a buffer, where it will
* stay untill it is flushed in the gameloop.
*
* Will also parse ANSI colors and other tags.
*/
void text_to_buffer(SOCKET_DATA *dsock, const char *txt)
{
// if we're at the head of the outbuf and haven't entered a command,
// also copy a newline so we're not printing in front of the prompt
if(bufferLength(dsock->outbuf) == 0 && !dsock->bust_prompt)
bufferCat(dsock->outbuf, "\r\n");
/* add data to buffer */
bufferCat(dsock->outbuf, txt);
}
void next_cmd_from_buffer(SOCKET_DATA *dsock) {
// do we have stuff in our input list? If so, use that instead of inbuf
if(listSize(dsock->input) > 0) {
char *cmd = listPop(dsock->input);
strncpy(dsock->next_command, cmd, MAX_BUFFER);
dsock->cmd_read = TRUE;
dsock->bust_prompt = TRUE;
free(cmd);
}
else {
int size = 0, i = 0, j = 0, telopt = 0;
// if theres already a command ready, we return
if(dsock->next_command[0] != '\0')
return;
// if there is nothing pending, then return
if(dsock->inbuf[0] == '\0')
return;
// check how long the next command is
while(dsock->inbuf[size] != '\0' &&
dsock->inbuf[size] != '\n' && dsock->inbuf[size] != '\r')
size++;
/* we only deal with real commands */
if(dsock->inbuf[size] == '\0')
return;
// copy the next command into next_command
for(; i < size; i++) {
if(dsock->inbuf[i] == (signed char) IAC)
telopt = 1;
else if(telopt == 1 && (dsock->inbuf[i] == (signed char) DO ||
dsock->inbuf[i] == (signed char) DONT))
telopt = 2;
// check for compression format
else if(telopt == 2) {
unsigned char compress_opt = dsock->inbuf[i];
telopt = 0;
// check if we're using a valid compression
if(compress_opt == TELOPT_COMPRESS || compress_opt == TELOPT_COMPRESS2){
// start compressing
if(dsock->inbuf[i-1] == (signed char) DO)
compressStart(dsock, compress_opt);
// stop compressing
else if(dsock->inbuf[i-1] == (signed char) DONT)
compressEnd(dsock, compress_opt, FALSE);
}
}
else if(isprint(dsock->inbuf[i]) && isascii(dsock->inbuf[i])) {
dsock->next_command[j++] = dsock->inbuf[i];
}
}
dsock->next_command[j] = '\0';
// skip forward to the next line
while(dsock->inbuf[size] == '\n' || dsock->inbuf[size] == '\r') {
dsock->cmd_read = TRUE;
dsock->bust_prompt = TRUE; // seems like a good place to check
size++;
}
// use i as a static pointer
i = size;
// move the context of inbuf down
while(dsock->inbuf[size] != '\0') {
dsock->inbuf[size - i] = dsock->inbuf[size];
size++;
}
dsock->inbuf[size - i] = '\0';
}
}
bool flush_output(SOCKET_DATA *dsock) {
bool success = TRUE;
// quit if we have no output and don't need/can't have a prompt
if(bufferLength(dsock->outbuf) <= 0 &&
(!dsock->bust_prompt || !socketHasPrompt(dsock)))
return TRUE;
if(dsock->bust_prompt) {
socketShowPrompt(dsock);
dsock->bust_prompt = FALSE;
}
// process all of our outbound text for stuff like colors, etc
hookRun("process_outbound_text", hookBuildInfo("sk", dsock));
hookRun("finalize_outbound_text", hookBuildInfo("sk", dsock));
// Send the buffer, and return FALSE if the write fails. Clear our output
success = text_to_socket(dsock, bufferString(dsock->outbuf));
bufferClear(dsock->outbuf);
return success;
}
//*****************************************************************************
//
// SOCKET MAINTENANCE
//
// Functions below this point are mainly concerned with the upkeep and
// maintenance of sockets (making sure they are initialized, garbage collected,
// getting their addresses, etc...)
//
//*****************************************************************************
void deleteSocket(SOCKET_DATA *sock) {
if(sock->hostname) free(sock->hostname);
if(sock->page_string) free(sock->page_string);
if(sock->text_editor) deleteBuffer(sock->text_editor);
if(sock->outbuf) deleteBuffer(sock->outbuf);
if(sock->input_handlers) deleteListWith(sock->input_handlers, free);
if(sock->input) deleteListWith(sock->input, free);
if(sock->command_hist) deleteListWith(sock->command_hist, free);
if(sock->auxiliary) deleteAuxiliaryData(sock->auxiliary);
free(sock);
}
void clear_socket(SOCKET_DATA *sock_new, int sock)
{
if(sock_new->page_string) free(sock_new->page_string);
if(sock_new->text_editor) deleteBuffer(sock_new->text_editor);
if(sock_new->outbuf) deleteBuffer(sock_new->outbuf);
if(sock_new->input_handlers) deleteListWith(sock_new->input_handlers, free);
if(sock_new->auxiliary) deleteAuxiliaryData(sock_new->auxiliary);
if(sock_new->input) deleteListWith(sock_new->input, free);
if(sock_new->command_hist) deleteListWith(sock_new->command_hist, free);
bzero(sock_new, sizeof(*sock_new));
sock_new->auxiliary = newAuxiliaryData(AUXILIARY_TYPE_SOCKET);
sock_new->input_handlers = newList();
sock_new->input = newList();
sock_new->command_hist = newList();
sock_new->control = sock;
sock_new->lookup_status = TSTATE_LOOKUP;
sock_new->uid = next_sock_uid++;
sock_new->text_editor = newBuffer(MAX_BUFFER);
sock_new->outbuf = newBuffer(MAX_OUTPUT);
}
/* does the lookup, changes the hostname, and dies */
void *lookup_address(void *arg)
{
LOOKUP_DATA *lData = (LOOKUP_DATA *) arg;
struct hostent *from = 0;
// struct hostent ent;
// char buf[16384];
// int err;
/* do the lookup and store the result at &from */
from = gethostbyaddr(lData->buf, sizeof(lData->buf), AF_INET);
/* did we get anything ? */
if (from && from->h_name)
{
free(lData->dsock->hostname);
lData->dsock->hostname = strdup(from->h_name);
}
/* set it ready to be closed or used */
lData->dsock->lookup_status++;
/* free the lookup data */
free(lData->buf);
free(lData);
/* and kill the thread */
pthread_exit(0);
return NULL;
}
void recycle_sockets()
{
SOCKET_DATA *dsock;
LIST_ITERATOR *sock_i = newListIterator(socket_list);
ITERATE_LIST(dsock, sock_i) {
if (dsock->lookup_status != TSTATE_CLOSED)
continue;
/* remove the socket from the main list */
listRemove(socket_list, dsock);
propertyTableRemove(sock_table, dsock->uid);
/* close the socket */
close(dsock->control);
/* stop compression */
compressEnd(dsock, dsock->compressing, TRUE);
/* delete the socket from memory */
deleteSocket(dsock);
} deleteListIterator(sock_i);
}
/* reset all of the sockets' control values */
void reconnect_copyover_sockets() {
LIST_ITERATOR *sock_i = newListIterator(socket_list);
SOCKET_DATA *sock = NULL;
ITERATE_LIST(sock, sock_i)
FD_SET(sock->control, &fSet);
deleteListIterator(sock_i);
}
/* Recover from a copyover - load players */
void copyover_recover() {
CHAR_DATA *dMob;
ACCOUNT_DATA *account;
SOCKET_DATA *dsock;
FILE *fp;
char acct[100];
char name[100];
char host[MAX_BUFFER];
int desc;
log_string("Copyover recovery initiated");
if ((fp = fopen(COPYOVER_FILE, "r")) == NULL) {
log_string("Copyover file not found. Exitting.");
exit (1);
}
/* In case something crashes - doesn't prevent reading */
unlink(COPYOVER_FILE);
for (;;) {
fscanf(fp, "%d %s %s %s\n", &desc, acct, name, host);
if (desc == -1)
break;
// Many thanks to Rhaelar for the help in finding this bug; clear_socket
// does not like receiving freshly malloc'd data. We have to make sure
// everything is zeroed before we pass it to clear_socket
// dsock = malloc(sizeof(*dsock));
dsock = calloc(1, sizeof(*dsock));
clear_socket(dsock, desc);
dsock->hostname = strdup(host);
listPut(socket_list, dsock);
propertyTablePut(sock_table, dsock);
// load account data
if((account = get_account(acct)) != NULL)
socketSetAccount(dsock, account);
// no luck!
else {
close_socket(dsock, FALSE);
continue;
}
// load player data
if ((dMob = get_player(name)) != NULL) {
// attach to socket
charSetSocket(dMob, dsock);
socketSetChar(dsock, dMob);
// try putting the character into the game
// close the socket if we fail.
if(!try_enter_game(dMob)) {
// do not bother extracting, since we haven't entered the game yet
unreference_player(socketGetChar(dsock));
socketSetChar(dsock, NULL);
close_socket(dsock, FALSE);
continue;
}
}
// no luck
else {
close_socket(dsock, FALSE);
continue;
}
// Write something, and check if it goes error-free
if (!text_to_socket(dsock, "\n\r <*> And before you know it, everything has changed <*>\n\r")) {
close_socket(dsock, FALSE);
continue;
}
// make sure the socket can be used
dsock->bust_prompt = TRUE;
dsock->lookup_status = TSTATE_DONE;
// let our modules know we've finished copying over a socket
hookRun("copyover_complete", hookBuildInfo("sk", dsock));
// negotiate compression
text_to_buffer(dsock, (char *) compress_will2);
text_to_buffer(dsock, (char *) compress_will);
}
fclose(fp);
// now, set all of the sockets' control to the new fSet
reconnect_copyover_sockets();
}
void socket_handler() {
LIST_ITERATOR *sock_i = newListIterator(socket_list);
SOCKET_DATA *sock = NULL;
ITERATE_LIST(sock, sock_i) {
// Close sockects we are unable to read from, or if we have no handler
// to take in input
if ((FD_ISSET(sock->control, &rFd) && !read_from_socket(sock)) ||
listSize(sock->input_handlers) == 0) {
close_socket(sock, FALSE);
continue;
}
/* Ok, check for a new command */
next_cmd_from_buffer(sock);
/* Is there a new command pending ? */
if (sock->cmd_read) {
IH_PAIR *pair = listGet(sock->input_handlers, 0);
if(pair->python == FALSE) {
void (* handler)(SOCKET_DATA *, char *) = pair->handler;
handler(sock, sock->next_command);
}
else {
PyObject *arglist = Py_BuildValue("Os", socketGetPyFormBorrowed(sock),
sock->next_command);
PyObject *retval = PyEval_CallObject(pair->handler, arglist);
// check for an error:
if(retval == NULL) {
char *tb = getPythonTraceback();
if(tb != NULL) {
log_string("Error with a python input handler:\r\n%s\r\n", tb);
free(tb);
}
}
// garbage collection
Py_XDECREF(retval);
Py_XDECREF(arglist);
}
// append our last command to the command history. History buffer is
// 100 commands, so pop off the earliest command if we're going over
listPut(sock->command_hist, strdup(sock->next_command));
if(listSize(sock->command_hist) > 100)
free(listRemoveNum(sock->command_hist, 100));
sock->next_command[0] = '\0';
sock->cmd_read = FALSE;
}
#ifdef MODULE_ALIAS
// ACK!! this is so yucky, but I can't think of a better way to do it...
// if this command was put in place by an alias, decrement the alias_queue
// counter by one. This counter is in place mainly so aliases do not end
// up calling eachother and making us get stuck in an infinite loop.
if(sock->player) {
int alias_queue = charGetAliasesQueued(sock->player);
if(alias_queue > 0)
charSetAliasesQueued(sock->player, --alias_queue);
}
#endif
/* if the player quits or get's disconnected */
if(sock->closed)
continue;
/* Send all new data to the socket and close it if any errors occour */
if (!flush_output(sock))
close_socket(sock, FALSE);
} deleteListIterator(sock_i);
}
int count_pages(const char *string) {
int num_newlines = count_letters(string, '\n', strlen(string));
return ((num_newlines / NUM_LINES_PER_PAGE) +
(num_newlines % NUM_LINES_PER_PAGE != 0));
}
void show_page(SOCKET_DATA *dsock, int page_num) {
static char buf[MAX_BUFFER];
char *page = dsock->page_string;
*buf = '\0';
int buf_i = 0, newline_count = 0, curr_page = 1;
// skip to our page
while(*page && curr_page < page_num) {
if(*page == '\n')
newline_count++;
if(newline_count >= NUM_LINES_PER_PAGE) {
curr_page++;
newline_count = 0;
}
page++;
}
// copy our page to the buf
while(*page && newline_count < NUM_LINES_PER_PAGE) {
if(*page == '\n')
newline_count++;
buf[buf_i] = *page;
page++;
buf_i++;
}
buf[buf_i] = '\0';
text_to_buffer(dsock, buf);
if(dsock->tot_pages != 1)
send_to_socket(dsock, "{c[{ypage %d of %d. Use MORE to view next page or BACK to view previous{c]{n\r\n",
page_num, dsock->tot_pages);
}
void delete_page(SOCKET_DATA *dsock) {
if(dsock->page_string) free(dsock->page_string);
dsock->page_string = NULL;
dsock->tot_pages = 0;
dsock->curr_page = 0;
}
void page_string(SOCKET_DATA *dsock, const char *string) {
if(dsock->page_string)
free(dsock->page_string);
dsock->page_string = strdup(string);
dsock->tot_pages = count_pages(string);
dsock->curr_page = 1;
show_page(dsock, 1);
if(dsock->tot_pages == 1)
delete_page(dsock);
}
void page_back(SOCKET_DATA *dsock) {
dsock->curr_page--;
if(dsock->tot_pages > 0 && dsock->curr_page <= dsock->tot_pages &&
dsock->curr_page > 0)
show_page(dsock, dsock->curr_page);
else {
dsock->curr_page = MIN(dsock->tot_pages, 1);
text_to_buffer(dsock, "There is no more text in your page buffer.\r\n");
}
/*
if(dsock->curr_page >= dsock->tot_pages)
delete_page(dsock);
*/
}
void page_continue(SOCKET_DATA *dsock) {
dsock->curr_page++;
if(dsock->tot_pages > 0 && dsock->curr_page > 0 &&
dsock->curr_page <= dsock->tot_pages)
show_page(dsock, dsock->curr_page);
else {
dsock->curr_page = dsock->tot_pages;
text_to_buffer(dsock, "There is no more text in your page buffer.\r\n");
}
}
//
// the command handler for the reader
void read_handler(SOCKET_DATA *sock, char *input) {
if(!strncasecmp(input, "more", strlen(input)))
page_continue(sock);
else if(!strncasecmp(input, "back", strlen(input)))
page_back(sock);
else if(!strncasecmp(input, "quit", strlen(input)))
socketPopInputHandler(sock);
else
text_to_buffer(sock, "Invalid choice!\r\n");
}
//
// the prompt for reading text
void read_prompt(SOCKET_DATA *sock) {
text_to_buffer(sock, "\r\nQ to stop reading> ");
}
//
// a new handler that allows people to read long bits of text
void start_reader(SOCKET_DATA *dsock, const char *text) {
// add a new input handler to control the reading
socketPushInputHandler(dsock, read_handler, read_prompt);
// page the string
page_string(dsock, text);
}
void do_copyover(void) {
LIST_ITERATOR *sock_i = newListIterator(socket_list);
SOCKET_DATA *sock = NULL;
FILE *fp;
char buf[100];
char control_buf[20];
char port_buf[20];
if ((fp = fopen(COPYOVER_FILE, "w+")) == NULL)
return;
sprintf(buf, "\n\r <*> The world starts spinning <*>\n\r");
// For each playing descriptor, save its character and account
ITERATE_LIST(sock, sock_i) {
compressEnd(sock, sock->compressing, FALSE);
// kick off anyone who hasn't yet logged in a character
if (!socketGetChar(sock) || !charGetRoom(socketGetChar(sock))) {
text_to_socket(sock, "\r\nSorry, we are rebooting. Come back in a few minutes.\r\n");
close_socket(sock, FALSE);
}
// save account and player info to file
else {
fprintf(fp, "%d %s %s %s\n",
sock->control, accountGetName(sock->account),
charGetName(sock->player), sock->hostname);
// save the player
save_player(sock->player);
save_account(sock->account);
text_to_socket(sock, buf);
}
} deleteListIterator(sock_i);
fprintf (fp, "-1\n");
fclose (fp);
// close any pending sockets
recycle_sockets();
#ifdef MODULE_WEBSERVER
// if we have a webserver set up, finalize that
finalize_webserver();
#endif
// exec - descriptors are inherited
sprintf(control_buf, "%d", control);
sprintf(port_buf, "%d", mudport);
execl(EXE_FILE, "NakedMud", "-copyover", control_buf, port_buf, NULL);
}
//*****************************************************************************
// get and set functions
//*****************************************************************************
CHAR_DATA *socketGetChar ( SOCKET_DATA *dsock) {
return dsock->player;
}
void socketSetChar ( SOCKET_DATA *dsock, CHAR_DATA *ch) {
dsock->player = ch;
}
ACCOUNT_DATA *socketGetAccount ( SOCKET_DATA *dsock) {
return dsock->account;
}
void socketSetAccount (SOCKET_DATA *dsock, ACCOUNT_DATA *account) {
dsock->account = account;
}
BUFFER *socketGetTextEditor ( SOCKET_DATA *sock) {
return sock->text_editor;
}
BUFFER *socketGetOutbound ( SOCKET_DATA *sock) {
return sock->outbuf;
}
void socketPushInputHandler ( SOCKET_DATA *socket,
void handler(SOCKET_DATA *socket, char *input),
void prompt (SOCKET_DATA *socket)) {
IH_PAIR *pair = malloc(sizeof(IH_PAIR));
pair->handler = handler;
pair->prompt = prompt;
pair->python = FALSE;
listPush(socket->input_handlers, pair);
}
void socketPushPyInputHandler(SOCKET_DATA *sock, void *handler,void *prompt) {
IH_PAIR *pair = malloc(sizeof(IH_PAIR));
pair->handler = handler;
pair->prompt = prompt;
pair->python = TRUE;
Py_XINCREF((PyObject *)handler);
Py_XINCREF((PyObject *)prompt);
listPush(sock->input_handlers, pair);
}
void socketReplacePyInputHandler(SOCKET_DATA *sock, void *handler,void *prompt){
socketPopInputHandler(sock);
socketPushPyInputHandler(sock, handler, prompt);
}
const char *socketGetLastCmd(SOCKET_DATA *sock) {
if(listSize(sock->command_hist) == 0)
return "";
else
return listHead(sock->command_hist);
}
void socketPopInputHandler ( SOCKET_DATA *socket) {
IH_PAIR *pair = listPop(socket->input_handlers);
if(pair->python) {
Py_XDECREF((PyObject *)pair->handler);
Py_XDECREF((PyObject *)pair->prompt);
}
free(pair);
}
void socketReplaceInputHandler( SOCKET_DATA *socket,
void handler(SOCKET_DATA *socket, char *input),
void prompt (SOCKET_DATA *socket)) {
socketPopInputHandler(socket);
socketPushInputHandler(socket, handler, prompt);
}
void socketQueueCommand( SOCKET_DATA *sock, const char *cmd) {
listQueue(sock->input, strdup(cmd));
}
int socketGetUID( SOCKET_DATA *dsock) {
return dsock->uid;
}
bool socketHasPrompt(SOCKET_DATA *sock) {
IH_PAIR *pair = listGet(sock->input_handlers, 0);
return (pair != NULL && pair->prompt != NULL);
}
void socketShowPrompt( SOCKET_DATA *sock) {
IH_PAIR *pair = listGet(sock->input_handlers, 0);
if(pair == NULL || pair->prompt == NULL)
return;
else if(pair->python == FALSE) {
((void (*)(SOCKET_DATA *))pair->prompt)(sock);
}
else {
PyObject *arglist = Py_BuildValue("(O)", socketGetPyFormBorrowed(sock));
PyObject *retval = PyEval_CallObject(pair->prompt, arglist);
// check for an error:
if(retval == NULL) {
char *tb = getPythonTraceback();
if(tb != NULL) {
log_string("Error with a python prompt:\r\n%s\r\n", tb);
free(tb);
}
}
// garbage collection
Py_XDECREF(retval);
Py_XDECREF(arglist);
}
}
void *socketGetAuxiliaryData ( SOCKET_DATA *sock, const char *name) {
return hashGet(sock->auxiliary, name);
}
const char *socketGetHostname(SOCKET_DATA *sock) {
return sock->hostname;
}
int socketGetDNSLookupStatus(SOCKET_DATA *sock) {
return sock->lookup_status;
}
void socketBustPrompt(SOCKET_DATA *sock) {
sock->bust_prompt = TRUE;
}
//*****************************************************************************
// MCCP SUPPORT IS BELOW THIS LINE. NOTHING BUT MCCP SUPPORT SHOULD GO BELOW
// THIS LINE.
//*****************************************************************************
/*
* mccp.c - support functions for the Mud Client Compression Protocol
*
* see http://www.randomly.org/projects/MCCP/
*
* Copyright (c) 1999, Oliver Jowett <oliver@randomly.org>
*
* This code may be freely distributed and used if this copyright
* notice is retained intact.
*/
/*
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "mud.h"
#include "socket.h"
#include "utils.h"
*/
/* local functions */
bool processCompressed ( SOCKET_DATA *dsock );
const unsigned char enable_compress [] = { IAC, SB, TELOPT_COMPRESS, WILL, SE, 0 };
const unsigned char enable_compress2 [] = { IAC, SB, TELOPT_COMPRESS2, IAC, SE, 0 };
/*
* Memory management - zlib uses these hooks to allocate and free memory
* it needs
*/
void *zlib_alloc(void *opaque, unsigned int items, unsigned int size)
{
return calloc(items, size);
}
void zlib_free(void *opaque, void *address)
{
free(address);
}
/*
* Begin compressing data on `desc'
*/
bool compressStart(SOCKET_DATA *dsock, unsigned char teleopt)
{
z_stream *s;
/* already compressing */
if (dsock->out_compress)
return TRUE;
/* allocate and init stream, buffer */
s = (z_stream *) malloc(sizeof(*s));
dsock->out_compress_buf = (unsigned char *) malloc(COMPRESS_BUF_SIZE);
s->next_in = NULL;
s->avail_in = 0;
s->next_out = dsock->out_compress_buf;
s->avail_out = COMPRESS_BUF_SIZE;
s->zalloc = zlib_alloc;
s->zfree = zlib_free;
s->opaque = NULL;
if (deflateInit(s, 9) != Z_OK)
{
free(dsock->out_compress_buf);
free(s);
return FALSE;
}
/* version 1 or 2 support */
if (teleopt == TELOPT_COMPRESS)
text_to_socket(dsock, (char *) enable_compress);
else if (teleopt == TELOPT_COMPRESS2)
text_to_socket(dsock, (char *) enable_compress2);
else
{
bug("Bad teleoption %d passed", teleopt);
free(dsock->out_compress_buf);
free(s);
return FALSE;
}
/* now we're compressing */
dsock->compressing = teleopt;
dsock->out_compress = s;
/* success */
return TRUE;
}
/* Cleanly shut down compression on `desc' */
bool compressEnd(SOCKET_DATA *dsock, unsigned char teleopt, bool forced)
{
unsigned char dummy[1];
if (!dsock->out_compress)
return TRUE;
if (dsock->compressing != teleopt)
return FALSE;
dsock->out_compress->avail_in = 0;
dsock->out_compress->next_in = dummy;
/* No terminating signature is needed - receiver will get Z_STREAM_END */
if (deflate(dsock->out_compress, Z_FINISH) != Z_STREAM_END && !forced)
return FALSE;
/* try to send any residual data */
if (!processCompressed(dsock) && !forced)
return FALSE;
/* reset compression values */
deflateEnd(dsock->out_compress);
free(dsock->out_compress_buf);
free(dsock->out_compress);
dsock->compressing = 0;
dsock->out_compress = NULL;
dsock->out_compress_buf = NULL;
/* success */
return TRUE;
}
/* Try to send any pending compressed-but-not-sent data in `desc' */
bool processCompressed(SOCKET_DATA *dsock)
{
int iStart, nBlock, nWrite, len;
if (!dsock->out_compress)
return TRUE;
len = dsock->out_compress->next_out - dsock->out_compress_buf;
if (len > 0)
{
for (iStart = 0; iStart < len; iStart += nWrite)
{
nBlock = UMIN (len - iStart, 4096);
if ((nWrite = write(dsock->control, dsock->out_compress_buf + iStart, nBlock)) < 0)
{
if (errno == EAGAIN/* || errno == ENOSR*/)
break;
/* write error */
return FALSE;
}
if (nWrite <= 0)
break;
}
if (iStart)
{
if (iStart < len)
memmove(dsock->out_compress_buf, dsock->out_compress_buf+iStart, len - iStart);
dsock->out_compress->next_out = dsock->out_compress_buf + len - iStart;
}
}
/* success */
return TRUE;
}
//
// compress output
//
COMMAND(cmd_compress)
{
/* no socket, no compression */
if (!charGetSocket(ch))
return;
/* enable compression */
if (!charGetSocket(ch)->out_compress) {
text_to_char(ch, "Trying compression.\n\r");
text_to_buffer(charGetSocket(ch), (char *) compress_will2);
text_to_buffer(charGetSocket(ch), (char *) compress_will);
}
else /* disable compression */ {
if (!compressEnd(charGetSocket(ch), charGetSocket(ch)->compressing, FALSE)){
text_to_char(ch, "Failed.\n\r");
return;
}
text_to_char(ch, "Compression disabled.\n\r");
}
}
//*****************************************************************************
// IF YOU ARE PUTTING ANYTHING BELOW THIS LINE, YOU ARE PUTTING IT IN THE WRONG
// PLACE!! ALL SOCKET-RELATED STUFF SHOULD GO UP ABOVE THE MCCP SUPPORT STUFF!
//*****************************************************************************