/*
firetalk.c - FireTalk wrapper definitions
Copyright (C) 2000 Ian Gulliver
Copyright 2002-2006 Daniel Reed <n@ml.org>

This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation.

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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <strings.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/stat.h>
#include <netdb.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>

#define FIRETALK

#include "firetalk-int.h"
#include "firetalk.h"

typedef void (*ptrtotoc)(void *, ...);
typedef void (*sighandler_t)(int);

/* Global variables */
fte_t	firetalkerror;
static struct s_firetalk_handle *handle_head = NULL;

static const firetalk_protocol_t **firetalk_protocols = NULL;
static int FP_MAX = 0;

fte_t	firetalk_register_protocol(const firetalk_protocol_t *const proto) {
	const firetalk_protocol_t **ptr;

	if (proto == NULL)
		abort();

	ptr = realloc(firetalk_protocols, sizeof(*firetalk_protocols)*(FP_MAX+1));
	if (ptr == NULL)
		return(FE_UNKNOWN);
	firetalk_protocols = ptr;
	firetalk_protocols[FP_MAX++] = proto;
	return(FE_SUCCESS);
}

static void firetalk_register_default_protocols(void) {
	extern const firetalk_protocol_t
		firetalk_protocol_irc,
		firetalk_protocol_slcp,
		firetalk_protocol_toc2;

	if (firetalk_register_protocol(&firetalk_protocol_irc) != FE_SUCCESS)
		abort();
	if (firetalk_register_protocol(&firetalk_protocol_slcp) != FE_SUCCESS)
		abort();
	if (firetalk_register_protocol(&firetalk_protocol_toc2) != FE_SUCCESS)
		abort();
}

int	firetalk_find_protocol(const char *strprotocol) {
	static int registered_defaults = 0;
	int	i;

	if (strprotocol == NULL)
		abort();

	for (i = 0; i < FP_MAX; i++)
		if (strcasecmp(strprotocol, firetalk_protocols[i]->strprotocol) == 0)
			return(i);
	if (!registered_defaults) {
		registered_defaults = 1;
		firetalk_register_default_protocols();
		for (i = 0; i < FP_MAX; i++)
			if (strcasecmp(strprotocol, firetalk_protocols[i]->strprotocol) == 0)
				return(i);
	}
	return(-1);
}

/* Internal function definitions */

/* firetalk_find_by_toc searches the firetalk handle list for the toc handle passed, and returns the firetalk handle */
firetalk_t firetalk_find_handle(client_t c) {
	struct s_firetalk_handle *iter;

	for (iter = handle_head; iter != NULL; iter = iter->next)
		if (iter->handle == c)
			return(iter);
	abort();
}

firetalk_t firetalk_find_clientstruct(void *clientstruct) {
	struct s_firetalk_handle *iter;

	for (iter = handle_head; iter != NULL; iter = iter->next)
		if (iter->clientstruct == clientstruct)
			return(iter);
	return(NULL);
}

#define DEBUG

#ifdef DEBUG
# define VERIFYCONN \
	do { \
		if (firetalk_check_handle(conn) != FE_SUCCESS) \
			abort(); \
	} while(0)

static fte_t firetalk_check_handle(firetalk_t c) {
	struct s_firetalk_handle *iter;

	for (iter = handle_head; iter != NULL; iter = iter->next)
		if (iter == c)
			return(FE_SUCCESS);
	return(FE_BADHANDLE);
}
#else
# define VERIFYCONN \
	do { \
	} while(0)
#endif

static char **firetalk_parse_subcode_args(char *string) {
	static char *args[256];
	int i,n;
	size_t l;

	l = strlen(string);
	args[0] = string;
	n = 1;
	for (i = 0; (size_t) i < l && n < 255; i++) {
		if (string[i] == ' ') {
			string[i++] = '\0';
			args[n++] = &string[i];
		}
	}
	args[n] = NULL;
	return(args);
}

static unsigned char firetalk_debase64_char(const char c) {
	if ((c >= 'A') && (c <= 'Z'))
		return((unsigned char)(c - 'A'));
	if ((c >= 'a') && (c <= 'z'))
		return((unsigned char)(26 + (c - 'a')));
	if ((c >= '0') && (c <= '9'))
		return((unsigned char)(52 + (c - '0')));
	if (c == '+')
		return((unsigned char)62);
	if (c == '/')
		return((unsigned char)63);
	return((unsigned char)0);
}

const char *firetalk_debase64(const char *const str) {
        static unsigned char out[256];
	int	s, o, len = strlen(str);

	for (o = s = 0; (s <= (len - 3)) && (o < (sizeof(out)-1)); s += 4, o += 3) {
		out[o]   = (firetalk_debase64_char(str[s])   << 2) | (firetalk_debase64_char(str[s+1]) >> 4);
		out[o+1] = (firetalk_debase64_char(str[s+1]) << 4) | (firetalk_debase64_char(str[s+2]) >> 2);
		out[o+2] = (firetalk_debase64_char(str[s+2]) << 6) |  firetalk_debase64_char(str[s+3]);
	}
	out[o] = 0;
	return((char *)out);
}


fte_t	firetalk_im_internal_add_deny(firetalk_t conn, const char *const nickname) {
	struct s_firetalk_deny *iter;

	VERIFYCONN;

	for (iter = conn->deny_head; iter != NULL; iter = iter->next)
		if (firetalk_protocols[conn->protocol]->comparenicks(iter->nickname, nickname) == FE_SUCCESS)
			break; /* not an error, user is in buddy list */

	if (iter == NULL) {
		iter = conn->deny_head;
		conn->deny_head = calloc(1, sizeof(struct s_firetalk_deny));
		if (conn->deny_head == NULL)
			abort();
		conn->deny_head->next = iter;
		conn->deny_head->nickname = strdup(nickname);
		if (conn->deny_head->nickname == NULL)
			abort();
	}

	firetalk_callback_im_buddyonline(conn->handle, nickname, 0);

	return(FE_SUCCESS);
}

#undef FIRETALK_FORK_RESOLV
#ifdef FIRETALK_FORK_RESOLV
fte_t	firetalk_internal_resolve4(const char *const host, struct in_addr *inet4_ip) {
	int	pi[2];

	if (pipe(pi) != 0)
		return(FE_NOTFOUND);

	switch(fork()) {
	  case -1:
		close(pi[0]);
		close(pi[1]);
		return(FE_NOTFOUND);
	  case 0: {
			struct hostent
				*he;

			close(pi[0]);
			if (((he = gethostbyaddr(host, strlen(host), AF_INET)) != NULL) && (he->h_addr_list != NULL))
				write(pi[1], he->h_addr_list[0], sizeof(inet4_ip->s_addr));
			close(pi[1]);
			_exit(2);
		}
	  default: {
			struct timeval
				tv;
			fd_set	readset;

			FD_ZERO(&readset);
			FD_SET(pi[0], &readset);
			close(pi[1]);
			tv.tv_sec = 5;
			tv.tv_usec = 0;
			if ((select(pi[0]+1, &readset, NULL, NULL, &tv) > 0)
				&& FD_ISSET(pi[0], &readset)
				&& (read(pi[0], &(inet4_ip->s_addr), sizeof(inet4_ip->s_addr)) == sizeof(inet4_ip->s_addr))) {
				close(pi[0]);
				return(FE_SUCCESS);
			}
			close(pi[0]);
			return(FE_NOTFOUND);
		}
	}
}
#else
fte_t	firetalk_internal_resolve4(const char *const host, struct in_addr *inet4_ip) {
	struct hostent *he;

	he = gethostbyname(host);
	if (he && he->h_addr_list) {
		memmove(&(inet4_ip->s_addr), he->h_addr_list[0], sizeof(inet4_ip->s_addr));
		return(FE_SUCCESS);
	}

	memset(&(inet4_ip->s_addr), 0, sizeof(inet4_ip->s_addr));

	return(FE_NOTFOUND);
}
#endif

struct sockaddr_in *firetalk_internal_remotehost4(client_t c) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	return(&(conn->remote_addr));
}

#ifdef _FC_USE_IPV6
fte_t	firetalk_internal_resolve6(const char *const host, struct in6_addr *inet6_ip) {
	struct addrinfo *addr = NULL;  // xxx generalize this so that we can use this with v6 and v4
	struct addrinfo hints = {0, PF_INET6, 0, 0, 0, NULL, NULL, NULL};

	if (getaddrinfo(host, NULL, &hints, &addr) == 0) {
		struct addrinfo *cur;

		for (cur = addr; cur != NULL; cur = cur->ai_next)
			if (cur->ai_family == PF_INET6) {
				memcpy(&(inet6_ip->s6_addr), ((struct sockaddr_in6 *)cur->ai_addr)->sin6_addr.s6_addr, 16);
				freeaddrinfo(addr);
				return(FE_SUCCESS);
			}
	}

	memset(&(inet6_ip->s6_addr), 0, sizeof(inet6_ip->s6_addr));

	if (addr != NULL)
		freeaddrinfo(addr);

	return(FE_NOTFOUND);
}

struct sockaddr_in6 *firetalk_internal_remotehost6(client_t c) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	return(&(conn->remote_addr6));
}
#endif

int	firetalk_internal_connect_host_addr(const char *const host, const uint16_t port, struct sockaddr_in *inet4
#ifdef _FC_USE_IPV6
		, struct sockaddr_in6 *inet6
#endif
) {
#ifdef _FC_USE_IPV6
	if (firetalk_internal_resolve6(host, &(inet6->sin6_addr)) == FE_SUCCESS) {
		inet6->sin6_port = htons(port);
		inet6->sin6_family = AF_INET6;
	} else
		inet6 = NULL;
#endif
	if (firetalk_internal_resolve4(host, &(inet4->sin_addr)) == FE_SUCCESS) {
		inet4->sin_port = htons(port);
		inet4->sin_family = AF_INET;
	} else
		inet4 = NULL;

	return firetalk_internal_connect(inet4
#ifdef _FC_USE_IPV6
	   , inet6
#endif
	   );
}

int	firetalk_internal_connect_host(const char *const host, const int port) {
	struct sockaddr_in myinet4;
#ifdef _FC_USE_IPV6
	struct sockaddr_in6 myinet6;
#endif

	return(firetalk_internal_connect_host_addr(host, port, &myinet4
#ifdef _FC_USE_IPV6
		, &myinet6
#endif
	));
}

int	firetalk_internal_connect(struct sockaddr_in *inet4_ip
#ifdef _FC_USE_IPV6
		, struct sockaddr_in6 *inet6_ip
#endif
		) {
	int s,i;

#ifdef _FC_USE_IPV6
	if (inet6_ip && (inet6_ip->sin6_addr.s6_addr[0] || inet6_ip->sin6_addr.s6_addr[1]
		|| inet6_ip->sin6_addr.s6_addr[2] || inet6_ip->sin6_addr.s6_addr[3]
		|| inet6_ip->sin6_addr.s6_addr[4] || inet6_ip->sin6_addr.s6_addr[5]
		|| inet6_ip->sin6_addr.s6_addr[6] || inet6_ip->sin6_addr.s6_addr[7]
		|| inet6_ip->sin6_addr.s6_addr[8] || inet6_ip->sin6_addr.s6_addr[9]
		|| inet6_ip->sin6_addr.s6_addr[10] || inet6_ip->sin6_addr.s6_addr[11]
		|| inet6_ip->sin6_addr.s6_addr[12] || inet6_ip->sin6_addr.s6_addr[13]
		|| inet6_ip->sin6_addr.s6_addr[14] || inet6_ip->sin6_addr.s6_addr[15])) {
		h_errno = 0;
		s = socket(PF_INET6, SOCK_STREAM, 0);
		if ((s != -1) && (fcntl(s, F_SETFL, O_NONBLOCK) == 0)) {
			i = connect(s, (const struct sockaddr *)inet6_ip, sizeof(struct sockaddr_in6));
			if ((i == 0) || (errno == EINPROGRESS))
				return(s);
		}
	}
#endif

	if (inet4_ip && inet4_ip->sin_addr.s_addr) {
		h_errno = 0;
		s = socket(PF_INET, SOCK_STREAM, 0);
		if ((s != -1) && (fcntl(s, F_SETFL, O_NONBLOCK) == 0)) {
			i = connect(s, (const struct sockaddr *)inet4_ip, sizeof(struct sockaddr_in));
			if ((i == 0) || (errno == EINPROGRESS))
				return(s);
		}
	}

	firetalkerror = FE_CONNECT;
	return(-1);
}

enum firetalk_connectstate firetalk_internal_get_connectstate(client_t c) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	return(conn->connected);
}

void firetalk_internal_set_connectstate(client_t c, enum firetalk_connectstate fcs) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	conn->connected = fcs;
}

void firetalk_internal_send_data(firetalk_t c, const char *const data, const int length) {
	if (c->connected == FCS_NOTCONNECTED || c->connected == FCS_WAITING_SYNACK)
		return;

	if (send(c->fd,data,length,MSG_DONTWAIT|MSG_NOSIGNAL) != length) {
		/* disconnect client (we probably overran the queue, or the other end is gone) */
		firetalk_callback_disconnect(c->handle,FE_PACKET);
	}

	/* request ratelimit info */
	/* immediate send or queue? */
}

fte_t	firetalk_user_visible(firetalk_t conn, const char *const nickname) {
	struct s_firetalk_room *iter;

	VERIFYCONN;

	for (iter = conn->room_head; iter != NULL; iter = iter->next) {
		struct s_firetalk_member *mem;

		for (mem = iter->member_head; mem != NULL; mem = mem->next)
			if (firetalk_protocols[conn->protocol]->comparenicks(mem->nickname, nickname) == FE_SUCCESS)
				return(FE_SUCCESS);
	}
	return(FE_NOMATCH);
}

fte_t	firetalk_user_visible_but(firetalk_t conn, const char *const room, const char *const nickname) {
	struct s_firetalk_room *iter;

	VERIFYCONN;

	for (iter = conn->room_head; iter != NULL; iter = iter->next) {
		struct s_firetalk_member *mem;

		if (firetalk_protocols[conn->protocol]->comparenicks(iter->name, room) == FE_SUCCESS)
			continue;
		for (mem = iter->member_head; mem != NULL; mem = mem->next)
			if (firetalk_protocols[conn->protocol]->comparenicks(mem->nickname, nickname) == FE_SUCCESS)
				return(FE_SUCCESS);
	}
	return(FE_NOMATCH);
}

fte_t	firetalk_chat_internal_add_room(firetalk_t conn, const char *const name) {
	struct s_firetalk_room *iter;

	VERIFYCONN;

	for (iter = conn->room_head; iter != NULL; iter = iter->next)
		if (firetalk_protocols[conn->protocol]->comparenicks(iter->name, name) == FE_SUCCESS)
			return(FE_DUPEROOM); /* not an error, we're already in room */

	iter = conn->room_head;
	conn->room_head = calloc(1, sizeof(struct s_firetalk_room));
	if (conn->room_head == NULL)
		abort();
	conn->room_head->next = iter;
	conn->room_head->name = strdup(name);
	if (conn->room_head->name == NULL)
		abort();

	return(FE_SUCCESS);
}

fte_t	firetalk_chat_internal_add_member(firetalk_t conn, const char *const room, const char *const nickname) {
	struct s_firetalk_room *iter;
	struct s_firetalk_member *memberiter;

	VERIFYCONN;

	for (iter = conn->room_head; iter != NULL; iter = iter->next)
		if (firetalk_protocols[conn->protocol]->comparenicks(iter->name, room) == FE_SUCCESS)
			break;

	if (iter == NULL) /* we don't know about that room */
		return(FE_NOTFOUND);

	for (memberiter = iter->member_head; memberiter != NULL; memberiter = memberiter->next)
		if (firetalk_protocols[conn->protocol]->comparenicks(memberiter->nickname, nickname) == FE_SUCCESS)
			return(FE_SUCCESS);

	memberiter = iter->member_head;
	iter->member_head = calloc(1, sizeof(struct s_firetalk_member));
	if (iter->member_head == NULL)
		abort();
	iter->member_head->next = memberiter;
	iter->member_head->nickname = strdup(nickname);
	if (iter->member_head->nickname == NULL)
		abort();

	return(FE_SUCCESS);
}

static void firetalk_im_delete_buddy(firetalk_t conn, const char *const nickname) {
	struct s_firetalk_buddy *iter, *prev;

	for (prev = NULL, iter = conn->buddy_head; iter != NULL; prev = iter, iter = iter->next) {
		assert(iter->nickname != NULL);
		assert(iter->group != NULL);

		if (firetalk_protocols[conn->protocol]->comparenicks(nickname, iter->nickname) == FE_SUCCESS)
			break;
	}
	if (iter == NULL)
		return;

	if (prev != NULL)
		prev->next = iter->next;
	else
		conn->buddy_head = iter->next;
	free(iter->nickname);
	iter->nickname = NULL;
	free(iter->group);
	iter->group = NULL;
	if (iter->friendly != NULL) {
		free(iter->friendly);
		iter->friendly = NULL;
	}
	if (iter->capabilities != NULL) {
		free(iter->capabilities);
		iter->capabilities = NULL;
	}
	free(iter);
	iter = NULL;
}

static struct s_firetalk_buddy *firetalk_im_find_buddy(firetalk_t conn, const char *const name) {
	struct s_firetalk_buddy *iter;

	for (iter = conn->buddy_head; iter != NULL; iter = iter->next) {
		assert(iter->nickname != NULL);
		assert(iter->group != NULL);

		if (firetalk_protocols[conn->protocol]->comparenicks(iter->nickname, name) == FE_SUCCESS)
			return(iter);
	}
	return(NULL);
}

fte_t	firetalk_im_remove_buddy(firetalk_t conn, const char *const name) {
	struct s_firetalk_buddy *iter;

	VERIFYCONN;

	if ((iter = firetalk_im_find_buddy(conn, name)) == NULL)
		return(FE_NOTFOUND);

	if (conn->connected != FCS_NOTCONNECTED) {
		int	ret;

		ret = firetalk_protocols[conn->protocol]->im_remove_buddy(conn->handle, iter->nickname, iter->group);
		if (ret != FE_SUCCESS)
			return(ret);
	}

	firetalk_im_delete_buddy(conn, name);

	return(FE_SUCCESS);
}

fte_t	firetalk_im_internal_remove_deny(firetalk_t conn, const char *const nickname) {
	struct s_firetalk_deny *iter;
	struct s_firetalk_deny *prev;

	VERIFYCONN;

	prev = NULL;
	for (iter = conn->deny_head; iter != NULL; iter = iter->next) {
		if (firetalk_protocols[conn->protocol]->comparenicks(nickname, iter->nickname) == FE_SUCCESS) {
			if (prev)
				prev->next = iter->next;
			else
				conn->deny_head = iter->next;
			free(iter->nickname);
			iter->nickname = NULL;
			free(iter);
			return(FE_SUCCESS);
		}
		prev = iter;
	}

	return(FE_NOTFOUND);
}

fte_t	firetalk_chat_internal_remove_room(firetalk_t conn, const char *const name) {
	struct s_firetalk_room *iter;
	struct s_firetalk_room *prev;
	struct s_firetalk_member *memberiter;
	struct s_firetalk_member *membernext;

	VERIFYCONN;

	prev = NULL;
	for (iter = conn->room_head; iter != NULL; iter = iter->next) {
		if (firetalk_protocols[conn->protocol]->comparenicks(name, iter->name) == FE_SUCCESS) {
			for (memberiter = iter->member_head; memberiter != NULL; memberiter = membernext) {
				membernext = memberiter->next;
				free(memberiter->nickname);
				memberiter->nickname = NULL;
				free(memberiter);
			}
			iter->member_head = NULL;
			if (prev)
				prev->next = iter->next;
			else
				conn->room_head = iter->next;
			if (iter->name) {
				free(iter->name);
				iter->name = NULL;
			}
			free(iter);
			return(FE_SUCCESS);
		}
		prev = iter;
	}

	return(FE_NOTFOUND);
}

fte_t	firetalk_chat_internal_remove_member(firetalk_t conn, const char *const room, const char *const nickname) {
	struct s_firetalk_room *iter;
	struct s_firetalk_member *memberiter;
	struct s_firetalk_member *memberprev;

	VERIFYCONN;

	for (iter = conn->room_head; iter != NULL; iter = iter->next)
		if (firetalk_protocols[conn->protocol]->comparenicks(iter->name, room) == FE_SUCCESS)
			break;

	if (iter == NULL) /* we don't know about that room */
		return(FE_NOTFOUND);

	memberprev = NULL;
	for (memberiter = iter->member_head; memberiter != NULL; memberiter = memberiter->next) {
		if (firetalk_protocols[conn->protocol]->comparenicks(memberiter->nickname,nickname) == FE_SUCCESS) {
			if (memberprev)
				memberprev->next = memberiter->next;
			else
				iter->member_head = memberiter->next;
			if (memberiter->nickname) {
				free(memberiter->nickname);
				memberiter->nickname = NULL;
			}
			free(memberiter);
			return(FE_SUCCESS);
		}
		memberprev = memberiter;
	}

	return(FE_SUCCESS);
}

struct s_firetalk_room *firetalk_find_room(firetalk_t c, const char *const room) {
	struct s_firetalk_room *roomiter;
	const char *normalroom;

	normalroom = firetalk_protocols[c->protocol]->room_normalize(room);
	for (roomiter = c->room_head; roomiter != NULL; roomiter = roomiter->next)
		if (firetalk_protocols[c->protocol]->comparenicks(roomiter->name, normalroom) == FE_SUCCESS)
			return(roomiter);

	firetalkerror = FE_NOTFOUND;
	return(NULL);
}

static struct s_firetalk_member *firetalk_find_member(firetalk_t c, struct s_firetalk_room *r, const char *const name) {
	struct s_firetalk_member *memberiter;

	for (memberiter = r->member_head; memberiter != NULL; memberiter = memberiter->next)
		if (firetalk_protocols[c->protocol]->comparenicks(memberiter->nickname, name) == FE_SUCCESS)
			return(memberiter);

	firetalkerror = FE_NOTFOUND;
	return(NULL);
}

void firetalk_callback_needpass(client_t c, char *pass, const int size) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->callbacks[FC_NEEDPASS])
		conn->callbacks[FC_NEEDPASS](conn, conn->clientstruct, pass, size);
}

static const char *isonline_hack = NULL;

void firetalk_callback_im_getmessage(client_t c, const char *const sender, const int automessage, const char *const message) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_deny *iter;

	if (strstr(message, "<a href=\"http://www.candidclicks.com/cgi-bin/enter.cgi?") != NULL) {
		firetalk_im_evil(conn, sender);
		return;
	}
	if (conn->callbacks[FC_IM_GETMESSAGE]) {
		for (iter = conn->deny_head; iter != NULL; iter = iter->next)
			if (firetalk_protocols[conn->protocol]->comparenicks(sender, iter->nickname) == FE_SUCCESS)
				return;
		isonline_hack = sender;
		conn->callbacks[FC_IM_GETMESSAGE](conn, conn->clientstruct, sender, automessage, message);
		isonline_hack = NULL;
	}
}

void firetalk_callback_im_getaction(client_t c, const char *const sender, const int automessage, const char *const message) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_deny *iter;

	if (conn->callbacks[FC_IM_GETACTION]) {
		for (iter = conn->deny_head; iter != NULL; iter = iter->next)
			if (firetalk_protocols[conn->protocol]->comparenicks(sender, iter->nickname) == FE_SUCCESS)
				return;
		isonline_hack = sender;
		conn->callbacks[FC_IM_GETACTION](conn, conn->clientstruct, sender, automessage, message);
		isonline_hack = NULL;
	}
}

void firetalk_callback_im_buddyonline(client_t c, const char *const nickname, int online) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_buddy *buddyiter;

	online = online?1:0;

	if ((buddyiter = firetalk_im_find_buddy(conn, nickname)) != NULL)
		if (buddyiter->online != online) {
			buddyiter->online = online;

			if (online != 0) {
				assert(buddyiter->away == 0);
				assert(buddyiter->typing == 0);
				assert(buddyiter->warnval == 0);
				assert(buddyiter->idletime == 0);
				if (strcmp(buddyiter->nickname, nickname) != 0) {
					free(buddyiter->nickname);
					buddyiter->nickname = strdup(nickname);
					if (buddyiter->nickname == NULL)
						abort();
				}
				if (conn->callbacks[FC_IM_BUDDYONLINE] != NULL)
					conn->callbacks[FC_IM_BUDDYONLINE](conn, conn->clientstruct, nickname);
			} else {
				buddyiter->away = 0;
				buddyiter->typing = 0;
				buddyiter->warnval = 0;
				buddyiter->idletime = 0;
				if (conn->callbacks[FC_IM_BUDDYOFFLINE] != NULL)
					conn->callbacks[FC_IM_BUDDYOFFLINE](conn, conn->clientstruct, nickname);
			}
		}
}

void firetalk_callback_im_buddyflags(client_t c, const char *const nickname, const int flags) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_buddy *buddyiter;

	if ((buddyiter = firetalk_im_find_buddy(conn, nickname)) != NULL)
		if ((buddyiter->flags != flags) && (buddyiter->online == 1)) {
			buddyiter->flags = flags;
			if (conn->callbacks[FC_IM_BUDDYFLAGS] != NULL)
				conn->callbacks[FC_IM_BUDDYFLAGS](conn, conn->clientstruct, nickname, flags);
		}
}

void firetalk_callback_im_buddyaway(client_t c, const char *const nickname, const int away) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_buddy *buddyiter;

	if ((buddyiter = firetalk_im_find_buddy(conn, nickname)) != NULL)
		if ((buddyiter->away != away) && (buddyiter->online == 1)) {
			buddyiter->away = away;
			if ((away == 1) && (conn->callbacks[FC_IM_BUDDYAWAY] != NULL))
				conn->callbacks[FC_IM_BUDDYAWAY](conn, conn->clientstruct, nickname);
			else if ((away == 0) && (conn->callbacks[FC_IM_BUDDYUNAWAY] != NULL))
				conn->callbacks[FC_IM_BUDDYUNAWAY](conn, conn->clientstruct, nickname);
		}
}

static void firetalk_im_insert_buddy(firetalk_t conn, const char *const name, const char *const group, const char *const friendly) {
	struct s_firetalk_buddy *newiter;

	newiter = calloc(1, sizeof(*newiter));
	if (newiter == NULL)
		abort();
	newiter->next = conn->buddy_head;
	conn->buddy_head = newiter;
	newiter->nickname = strdup(name);
	if (newiter->nickname == NULL)
		abort();
	newiter->group = strdup(group);
	if (newiter->group == NULL)
		abort();
	if (friendly == NULL)
		newiter->friendly = NULL;
	else {
		newiter->friendly = strdup(friendly);
		if (newiter->friendly == NULL)
			abort();
	}
}

void	firetalk_callback_buddyadded(client_t c, const char *const name, const char *const group, const char *const friendly) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (firetalk_im_find_buddy(conn, name) == NULL) {
		firetalk_im_insert_buddy(conn, name, group, friendly);
		if (conn->callbacks[FC_IM_BUDDYADDED] != NULL)
			conn->callbacks[FC_IM_BUDDYADDED](conn, conn->clientstruct, name, group, friendly);
	}
}

void	firetalk_callback_buddyremoved(client_t c, const char *const name, const char *const group) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_buddy *iter;

	if (((iter = firetalk_im_find_buddy(conn, name)) != NULL) && ((group == NULL) || (strcmp(iter->group, group) == 0))) {
		firetalk_im_delete_buddy(conn, name);
		if (conn->callbacks[FC_IM_BUDDYREMOVED] != NULL)
			conn->callbacks[FC_IM_BUDDYREMOVED](conn, conn->clientstruct, name);
	}
}

void	firetalk_callback_typing(client_t c, const char *const name, const int typing) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_buddy *buddyiter;

	assert(conn->username != NULL);
	assert(name != NULL);
	assert(typing >= 0);

	if (!conn->callbacks[FC_IM_TYPINGINFO])
		return;

	if ((buddyiter = firetalk_im_find_buddy(conn, name)) != NULL)
		if (buddyiter->online && (buddyiter->typing != typing)) {
			buddyiter->typing = typing;
			conn->callbacks[FC_IM_TYPINGINFO](conn, conn->clientstruct, buddyiter->nickname, typing);
		}
}

void	firetalk_callback_capabilities(client_t c, const char *const nickname, const char *const caps) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_buddy *buddyiter;

	if (!conn->callbacks[FC_IM_CAPABILITIES])
		return;

	if ((buddyiter = firetalk_im_find_buddy(conn, nickname)) != NULL)
		if ((buddyiter->capabilities == NULL) || (strcmp(buddyiter->capabilities, caps) != 0)) {
			free(buddyiter->capabilities);
			buddyiter->capabilities = strdup(caps);
			conn->callbacks[FC_IM_CAPABILITIES](conn, conn->clientstruct, nickname, caps);
		}
}

void	firetalk_callback_warninfo(client_t c, const char *const nickname, const long warnval) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_buddy *buddyiter;

	if (!conn->callbacks[FC_IM_EVILINFO])
		return;

	if ((buddyiter = firetalk_im_find_buddy(conn, nickname)) != NULL)
		if ((buddyiter->warnval != warnval) && (buddyiter->online == 1)) {
			buddyiter->warnval = warnval;
			conn->callbacks[FC_IM_EVILINFO](conn, conn->clientstruct, nickname, warnval);
		}
}

void firetalk_callback_error(client_t c, const int error, const char *const roomoruser, const char *const description) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->callbacks[FC_ERROR])
		conn->callbacks[FC_ERROR](conn, conn->clientstruct, error, roomoruser, description);
}

void firetalk_callback_connectfailed(client_t c, const int error, const char *const description) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->connected == FCS_NOTCONNECTED)
		return;

	close(conn->fd);
	conn->connected = FCS_NOTCONNECTED;
	if (conn->callbacks[FC_CONNECTFAILED])
		conn->callbacks[FC_CONNECTFAILED](conn, conn->clientstruct, error, description);
}

void firetalk_callback_disconnect(client_t c, const int error) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_buddy *buddyiter, *buddynext;
	struct s_firetalk_deny *denyiter, *denynext;
	struct s_firetalk_room *roomiter, *roomnext;

	if (conn->connected == FCS_NOTCONNECTED)
		return;
	close(conn->fd);

	if (conn->username != NULL) {
		free(conn->username);
		conn->username = NULL;
	}

	for (buddyiter = conn->buddy_head; buddyiter != NULL; buddyiter = buddynext) {
		buddynext = buddyiter->next;
		buddyiter->next = NULL;
		free(buddyiter->nickname);
		buddyiter->nickname = NULL;
		free(buddyiter->group);
		buddyiter->group = NULL;
		free(buddyiter->friendly);
		buddyiter->friendly = NULL;
		if (buddyiter->capabilities != NULL) {
			free(buddyiter->capabilities);
			buddyiter->capabilities = NULL;
		}
		free(buddyiter);
	}
	conn->buddy_head = NULL;

	for (denyiter = conn->deny_head; denyiter != NULL; denyiter = denynext) {
		denynext = denyiter->next;
		denyiter->next = NULL;
		free(denyiter->nickname);
		denyiter->nickname = NULL;
		free(denyiter);
	}
	conn->deny_head = NULL;

	for (roomiter = conn->room_head; roomiter != NULL; roomiter = roomnext) {
		struct s_firetalk_member *memberiter;
		struct s_firetalk_member *membernext;

		roomnext = roomiter->next;
		roomiter->next = NULL;
		for (memberiter = roomiter->member_head; memberiter != NULL; memberiter = membernext) {
			membernext = memberiter->next;
			memberiter->next = NULL;
			free(memberiter->nickname);
			memberiter->nickname = NULL;
			free(memberiter);
		}
		roomiter->member_head = NULL;
		free(roomiter->name);
		roomiter->name = NULL;
		free(roomiter);
	}
	conn->room_head = NULL;

	if (conn->connected == FCS_ACTIVE) {
		conn->connected = FCS_NOTCONNECTED;
		if (conn->callbacks[FC_DISCONNECT])
			conn->callbacks[FC_DISCONNECT](conn, conn->clientstruct, error);
	} else
		conn->connected = FCS_NOTCONNECTED;
}

void firetalk_callback_gotinfo(client_t c, const char *const nickname, const char *const info, const int warning, const long online, const long idle, const int flags) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->callbacks[FC_IM_GOTINFO])
		conn->callbacks[FC_IM_GOTINFO](conn, conn->clientstruct, nickname, info, warning, online, idle, flags);
}

void firetalk_callback_idleinfo(client_t c, const char *const nickname, const long idletime) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_buddy *buddyiter;

	if (!conn->callbacks[FC_IM_IDLEINFO])
		return;

	if ((buddyiter = firetalk_im_find_buddy(conn, nickname)) != NULL)
		if ((buddyiter->idletime != idletime) && (buddyiter->online == 1)) {
			buddyiter->idletime = idletime;
			conn->callbacks[FC_IM_IDLEINFO](conn, conn->clientstruct, nickname, idletime);
		}
}

void firetalk_callback_statusinfo(client_t c, const char *const nickname, const char *const message) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_buddy *buddyiter;

	if (conn->callbacks[FC_IM_STATUSINFO])
		conn->callbacks[FC_IM_STATUSINFO](conn, conn->clientstruct, nickname, message);
}

void firetalk_callback_doinit(client_t c, const char *const nickname) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->callbacks[FC_DOINIT])
		conn->callbacks[FC_DOINIT](conn, conn->clientstruct, nickname);
}

void firetalk_callback_setidle(client_t c, long *const idle) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->callbacks[FC_SETIDLE])
		conn->callbacks[FC_SETIDLE](conn, conn->clientstruct, idle);
}

void firetalk_callback_eviled(client_t c, const int newevil, const char *const eviler) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->callbacks[FC_EVILED])
		conn->callbacks[FC_EVILED](conn, conn->clientstruct, newevil, eviler);
}

void firetalk_callback_newnick(client_t c, const char *const nickname) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->callbacks[FC_NEWNICK])
		conn->callbacks[FC_NEWNICK](conn, conn->clientstruct, nickname);
}

void firetalk_callback_passchanged(client_t c) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->callbacks[FC_PASSCHANGED])
		conn->callbacks[FC_PASSCHANGED](conn, conn->clientstruct);
}

void firetalk_callback_user_nickchanged(client_t c, const char *const oldnick, const char *const newnick) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_buddy *buddyiter;
	struct s_firetalk_room *roomiter;
	struct s_firetalk_member *memberiter;
	char *tempstr;

	if ((buddyiter = firetalk_im_find_buddy(conn, oldnick)) != NULL)
		if (strcmp(buddyiter->nickname, newnick) != 0) {
			tempstr = buddyiter->nickname;
			buddyiter->nickname = strdup(newnick);
			if (buddyiter->nickname == NULL)
				abort();
			if (conn->callbacks[FC_IM_USER_NICKCHANGED])
				conn->callbacks[FC_IM_USER_NICKCHANGED](conn, conn->clientstruct, tempstr, newnick);
			free(tempstr);
		}

	for (roomiter = conn->room_head; roomiter != NULL; roomiter = roomiter->next)
		for (memberiter = roomiter->member_head; memberiter != NULL; memberiter = memberiter->next)
			if (firetalk_protocols[conn->protocol]->comparenicks(memberiter->nickname, oldnick) == FE_SUCCESS) {
				if (strcmp(memberiter->nickname, newnick) != 0) {
					tempstr = memberiter->nickname;
					memberiter->nickname = strdup(newnick);
					if (memberiter->nickname == NULL)
						abort();
					if (conn->callbacks[FC_CHAT_USER_NICKCHANGED])
						conn->callbacks[FC_CHAT_USER_NICKCHANGED](conn, conn->clientstruct, roomiter->name, tempstr, newnick);
					free(tempstr);
				}
				break;
			}
}

void firetalk_callback_chat_joined(client_t c, const char *const room) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (firetalk_chat_internal_add_room(conn, room) != FE_SUCCESS)
		return;
}

void firetalk_callback_chat_left(client_t c, const char *const room) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (firetalk_chat_internal_remove_room(conn, room) != FE_SUCCESS)
		return;
	if (conn->callbacks[FC_CHAT_LEFT])
		conn->callbacks[FC_CHAT_LEFT](conn, conn->clientstruct, room);
}

void firetalk_callback_chat_kicked(client_t c, const char *const room, const char *const by, const char *const reason) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (firetalk_chat_internal_remove_room(conn, room) != FE_SUCCESS)
		return;
	if (conn->callbacks[FC_CHAT_KICKED])
		conn->callbacks[FC_CHAT_KICKED](conn, conn->clientstruct, room, by, reason);
}

void firetalk_callback_chat_getmessage(client_t c, const char *const room, const char *const from, const int automessage, const char *const message) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->callbacks[FC_CHAT_GETMESSAGE])
		conn->callbacks[FC_CHAT_GETMESSAGE](conn, conn->clientstruct, room, from, automessage, message);
}

void firetalk_callback_chat_getaction(client_t c, const char *const room, const char *const from, const int automessage, const char *const message) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->callbacks[FC_CHAT_GETACTION])
		conn->callbacks[FC_CHAT_GETACTION](conn, conn->clientstruct, room, from, automessage, message);
}

void firetalk_callback_chat_invited(client_t c, const char *const room, const char *const from, const char *const message) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->callbacks[FC_CHAT_INVITED])
		conn->callbacks[FC_CHAT_INVITED](conn, conn->clientstruct, room, from, message);
}

void firetalk_callback_chat_user_joined(client_t c, const char *const room, const char *const who, const char *const extra) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_room *iter;

	iter = firetalk_find_room(conn, room);
	if (iter == NULL)
		return;

	if (who == NULL) {
		if (iter->sentjoin == 0) {
			iter->sentjoin = 1;
			if (conn->callbacks[FC_CHAT_JOINED])
				conn->callbacks[FC_CHAT_JOINED](conn, conn->clientstruct, room);
		}
	} else {
		if (firetalk_chat_internal_add_member(conn, room, who) != FE_SUCCESS)
			return;
		if (iter->sentjoin == 1)
			if (conn->callbacks[FC_CHAT_USER_JOINED])
				conn->callbacks[FC_CHAT_USER_JOINED](conn, conn->clientstruct, room, who, extra);
	}
}

void firetalk_callback_chat_user_left(client_t c, const char *const room, const char *const who, const char *const reason) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (firetalk_chat_internal_remove_member(conn, room, who) != FE_SUCCESS)
		return;
	if (conn->callbacks[FC_CHAT_USER_LEFT])
		conn->callbacks[FC_CHAT_USER_LEFT](conn, conn->clientstruct, room, who, reason);
}

void firetalk_callback_chat_user_quit(client_t c, const char *const who, const char *const reason) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_room *roomiter;
	struct s_firetalk_member *memberiter, *membernext;
	
	for (roomiter = conn->room_head; roomiter != NULL; roomiter = roomiter->next)
		for (memberiter = roomiter->member_head; memberiter != NULL; memberiter = membernext) {
			membernext = memberiter->next;
			if (firetalk_protocols[conn->protocol]->comparenicks(memberiter->nickname, who) == FE_SUCCESS)
				firetalk_callback_chat_user_left(c, roomiter->name, who, reason);
		}
}

void firetalk_callback_chat_gottopic(client_t c, const char *const room, const char *const topic, const char *const author) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_room *r;

	r = firetalk_find_room(conn, room);
	if (r != NULL)
		if (conn->callbacks[FC_CHAT_GOTTOPIC])
			conn->callbacks[FC_CHAT_GOTTOPIC](conn, conn->clientstruct, room, topic, author);
}

#ifdef RAWIRCMODES
void firetalk_callback_chat_modechanged(client_t c, const char *const room, const char *const mode, const char *const by) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->callbacks[FC_CHAT_MODECHANGED])
		conn->callbacks[FC_CHAT_MODECHANGED](conn, conn->clientstruct, room, mode, by);
}
#endif

void firetalk_callback_chat_user_opped(client_t c, const char *const room, const char *const who, const char *const by) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_room *r;
	struct s_firetalk_member *m;

	r = firetalk_find_room(conn, room);
	if (r == NULL)
		return;
	m = firetalk_find_member(conn, r, who);
	if (m == NULL)
		return;
	if (m->admin == 0) {
		m->admin = 1;
		if (conn->callbacks[FC_CHAT_USER_OPPED])
			conn->callbacks[FC_CHAT_USER_OPPED](conn, conn->clientstruct, room, who, by);
	}
}

void firetalk_callback_chat_user_deopped(client_t c, const char *const room, const char *const who, const char *const by) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_room *r;
	struct s_firetalk_member *m;

	r = firetalk_find_room(conn, room);
	if (r == NULL)
		return;
	m = firetalk_find_member(conn, r, who);
	if (m == NULL)
		return;
	if (m->admin == 1) {
		m->admin = 0;
		if (conn->callbacks[FC_CHAT_USER_DEOPPED])
			conn->callbacks[FC_CHAT_USER_DEOPPED](conn, conn->clientstruct, room, who, by);
	}
}

void firetalk_callback_chat_keychanged(client_t c, const char *const room, const char *const what, const char *const by) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (conn->callbacks[FC_CHAT_KEYCHANGED])
		conn->callbacks[FC_CHAT_KEYCHANGED](conn, conn->clientstruct, room, what, by);
}

void firetalk_callback_chat_opped(client_t c, const char *const room, const char *const by) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_room *r;

	r = firetalk_find_room(conn,room);
	if (r == NULL)
		return;
	if (r->admin == 0)
		r->admin = 1;
	else
		return;
	if (conn->callbacks[FC_CHAT_OPPED])
		conn->callbacks[FC_CHAT_OPPED](conn, conn->clientstruct, room, by);
}

void firetalk_callback_chat_deopped(client_t c, const char *const room, const char *const by) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_room *r;

	r = firetalk_find_room(conn,room);
	if (r == NULL)
		return;
	if (r->admin == 1)
		r->admin = 0;
	else
		return;
	if (conn->callbacks[FC_CHAT_DEOPPED])
		conn->callbacks[FC_CHAT_DEOPPED](conn, conn->clientstruct, room, by);
}

void firetalk_callback_chat_user_kicked(client_t c, const char *const room, const char *const who, const char *const by, const char *const reason) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	if (firetalk_chat_internal_remove_member(conn, room, who) != FE_SUCCESS)
		return;
	if (conn->callbacks[FC_CHAT_USER_KICKED])
		conn->callbacks[FC_CHAT_USER_KICKED](conn, conn->clientstruct, room, who, by, reason);
}

const char *firetalk_subcode_get_request_reply(client_t c, const char *const command) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_subcode_callback *iter;

	for (iter = conn->subcode_request_head; iter != NULL; iter = iter->next)
		if (strcmp(command, iter->command) == 0)
			if (iter->staticresp != NULL)
				return(iter->staticresp);
	return(NULL);
}

void firetalk_callback_subcode_request(client_t c, const char *const from, const char *const command, char *args) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_subcode_callback *iter;
	enum firetalk_connectstate connectedsave = conn->connected; /* nasty hack: some IRC servers send CTCP VERSION requests during signon, before 001, and demand a reply; idiots */

	conn->connected = FCS_ACTIVE;

	for (iter = conn->subcode_request_head; iter != NULL; iter = iter->next)
		if (strcmp(command, iter->command) == 0) {
			if (iter->staticresp != NULL)
				firetalk_subcode_send_reply(conn, from, command, iter->staticresp);
			else {
				isonline_hack = from;
				iter->callback(conn, conn->clientstruct, from, command, args);
				isonline_hack = NULL;
			}

			conn->connected = connectedsave;

			return;
		}

	if (strcmp(command, "ACTION") == 0)
		/* we don't support chatroom subcodes, so we're just going to assume that this is a private ACTION and let the protocol code handle the other case */
		firetalk_callback_im_getaction(c, from, 0, args);
	else if (strcmp(command, "VERSION") == 0)
		firetalk_subcode_send_reply(conn, from, "VERSION", PACKAGE_NAME ":" PACKAGE_VERSION ":unknown");
	else if (strcmp(command, "CLIENTINFO") == 0)
		firetalk_subcode_send_reply(conn, from, "CLIENTINFO", "CLIENTINFO PING SOURCE TIME VERSION");
	else if (strcmp(command, "PING") == 0) {
		if (args != NULL)
			firetalk_subcode_send_reply(conn, from, "PING", args);
		else
			firetalk_subcode_send_reply(conn, from, "PING", "");
	} else if (strcmp(command, "TIME") == 0) {
		time_t	temptime;
		char	tempbuf[64];
		size_t	s;

		time(&temptime);
		strncpy(tempbuf, ctime(&temptime), sizeof(tempbuf)-1);
		tempbuf[sizeof(tempbuf)-1] = 0;
		s = strlen(tempbuf);
		if (s > 0)
			tempbuf[s-1] = '\0';
		firetalk_subcode_send_reply(conn, from, "TIME", tempbuf);
	} else if ((strcmp(command,"DCC") == 0) && (args != NULL) && (strncasecmp(args, "SEND ", 5) == 0)) {
		/* DCC send */
		struct in_addr addr;
		unsigned long ip;
		long	size = -1;
		uint16_t port;
		char	**myargs;
#ifdef _FC_USE_IPV6
		struct in6_addr addr6;
		struct in6_addr *sendaddr6 = NULL;
#endif
		myargs = firetalk_parse_subcode_args(&args[5]);
		if ((myargs[0] != NULL) && (myargs[1] != NULL) && (myargs[2] != NULL)) {
			/* valid dcc send */
			if (myargs[3] != NULL) {
				size = atol(myargs[3]);
#ifdef _FC_USE_IPV6
				if (myargs[4] != NULL) {
					/* ipv6-enabled dcc */
					inet_pton(AF_INET6,myargs[4],&addr6);
					sendaddr6 = &addr6;
				}
#endif
			}
			sscanf(myargs[1], "%lu", &ip);
			ip = htonl(ip);
			memcpy(&addr.s_addr, &ip, 4);
			port = (uint16_t)atoi(myargs[2]);
			firetalk_callback_file_offer(c, from, myargs[0], size, inet_ntoa(addr), NULL, port, FF_TYPE_DCC);
		}
	} else if (conn->subcode_request_default != NULL)
		conn->subcode_request_default->callback(conn, conn->clientstruct, from, command, args);

	conn->connected = connectedsave;
}

void firetalk_callback_subcode_reply(client_t c, const char *const from, const char *const command, const char *const args) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_subcode_callback *iter;

	for (iter = conn->subcode_reply_head; iter != NULL; iter = iter->next)
		if (strcmp(command, iter->command) == 0) {
			isonline_hack = from;
			iter->callback(conn, conn->clientstruct, from, command, args);
			isonline_hack = NULL;
			return;
		}

	if (conn->subcode_reply_default != NULL)
		conn->subcode_reply_default->callback(conn, conn->clientstruct, from, command, args);
}

/* size may be -1 if unknown (0 is valid) */
void firetalk_callback_file_offer(client_t c, const char *const from, const char *const filename, const long size, const char *const ipstring, const char *const ip6string, const uint16_t port, const int type) {
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);
	struct s_firetalk_file *iter;

	iter = conn->file_head;
	conn->file_head = calloc(1, sizeof(struct s_firetalk_file));
	if (conn->file_head == NULL)
		abort();
	conn->file_head->who = strdup(from);
	if (conn->file_head->who == NULL)
		abort();
	conn->file_head->filename = strdup(filename);
	if (conn->file_head->filename == NULL)
		abort();
	conn->file_head->size = size;
	conn->file_head->bytes = 0;
	conn->file_head->acked = 0;
	conn->file_head->state = FF_STATE_WAITLOCAL;
	conn->file_head->direction = FF_DIRECTION_RECEIVING;
	conn->file_head->sockfd = -1;
	conn->file_head->filefd = -1;
	conn->file_head->port = htons(port);
	conn->file_head->type = type;
	conn->file_head->next = iter;
	conn->file_head->clientfilestruct = NULL;
	if (inet_aton(ipstring, &conn->file_head->inet_ip) == 0) {
		firetalk_file_cancel(c, conn->file_head);
		return;
	}
#ifdef _FC_USE_IPV6
	conn->file_head->tryinet6 = 0;
	if (ip6string)
		if (inet_pton(AF_INET6, ip6string, &conn->file_head->inet6_ip) != 0)
			conn->file_head->tryinet6 = 1;
#endif
	if (conn->callbacks[FC_FILE_OFFER])
		conn->callbacks[FC_FILE_OFFER](conn, conn->clientstruct, (void *)conn->file_head, from, filename, size);
}

void firetalk_handle_receive(struct s_firetalk_handle *c, struct s_firetalk_file *filestruct) {
	/* we have to copy from sockfd to filefd until we run out, then send the packet */
	static char buffer[4096];
	unsigned long netbytes;
	ssize_t s;

	while ((s = recv(filestruct->sockfd, buffer, 4096, MSG_DONTWAIT)) == 4096) {
		if (write(filestruct->filefd, buffer, 4096) != 4096) {
			if (c->callbacks[FC_FILE_ERROR])
				c->callbacks[FC_FILE_ERROR](c, c->clientstruct, filestruct, filestruct->clientfilestruct, FE_IOERROR);
			firetalk_file_cancel(c, filestruct);
			return;
		}
		filestruct->bytes += 4096;
	}
	if (s != -1) {
		if (write(filestruct->filefd, buffer, (size_t)s) != s) {
			if (c->callbacks[FC_FILE_ERROR])
				c->callbacks[FC_FILE_ERROR](c, c->clientstruct, filestruct, filestruct->clientfilestruct, FE_IOERROR);
			firetalk_file_cancel(c, filestruct);
			return;
		}
		filestruct->bytes += s;
	}
	if (filestruct->type == FF_TYPE_DCC) {
		netbytes = htonl((uint32_t)filestruct->bytes);
		if (write(filestruct->sockfd, &netbytes, 4) != 4) {
			if (c->callbacks[FC_FILE_ERROR])
				c->callbacks[FC_FILE_ERROR](c, c->clientstruct, filestruct, filestruct->clientfilestruct, FE_IOERROR);
			firetalk_file_cancel(c, filestruct);
			return;
		}
	}
	if (c->callbacks[FC_FILE_PROGRESS])
		c->callbacks[FC_FILE_PROGRESS](c, c->clientstruct, filestruct, filestruct->clientfilestruct, filestruct->bytes, filestruct->size);
	if (filestruct->bytes == filestruct->size) {
		if (c->callbacks[FC_FILE_FINISH])
			c->callbacks[FC_FILE_FINISH](c, c->clientstruct, filestruct, filestruct->clientfilestruct, filestruct->size);
		firetalk_file_cancel(c, filestruct);
	}
}

void firetalk_handle_send(struct s_firetalk_handle *c, struct s_firetalk_file *filestruct) {
	/* we have to copy from filefd to sockfd until we run out or sockfd refuses the data */
	static char buffer[4096];
	ssize_t s;

	while ((s = read(filestruct->filefd, buffer, 4096)) == 4096) {
		if ((s = send(filestruct->sockfd, buffer, 4096, MSG_DONTWAIT|MSG_NOSIGNAL)) != 4096) {
			lseek(filestruct->filefd, -(4096 - s), SEEK_CUR);
			filestruct->bytes += s;
			if (c->callbacks[FC_FILE_PROGRESS])
				c->callbacks[FC_FILE_PROGRESS](c, c->clientstruct, filestruct, filestruct->clientfilestruct, filestruct->bytes, filestruct->size);
			return;
		}
		filestruct->bytes += s;
		if (c->callbacks[FC_FILE_PROGRESS])
			c->callbacks[FC_FILE_PROGRESS](c, c->clientstruct, filestruct, filestruct->clientfilestruct, filestruct->bytes, filestruct->size);
		if (filestruct->type == FF_TYPE_DCC) {
			uint32_t acked = 0;

			while (recv(filestruct->sockfd, &acked, 4, MSG_DONTWAIT) == 4)
				filestruct->acked = ntohl(acked);
		}
	}
	if (send(filestruct->sockfd, buffer, s, MSG_NOSIGNAL) != s) {
		if (c->callbacks[FC_FILE_ERROR])
			c->callbacks[FC_FILE_ERROR](c, c->clientstruct, filestruct, filestruct->clientfilestruct, FE_IOERROR);
		firetalk_file_cancel(c, filestruct);
		return;
	}
	filestruct->bytes += s;
	if (filestruct->type == FF_TYPE_DCC) {
		while (filestruct->acked < (uint32_t)filestruct->bytes) {
			uint32_t acked = 0;

			if (recv(filestruct->sockfd, &acked, 4, 0) == 4)
				filestruct->acked = ntohl(acked);
		}
	}
	if (c->callbacks[FC_FILE_PROGRESS])
		c->callbacks[FC_FILE_PROGRESS](c, c->clientstruct, filestruct, filestruct->clientfilestruct, filestruct->bytes, filestruct->size);
	if (c->callbacks[FC_FILE_FINISH])
		c->callbacks[FC_FILE_FINISH](c, c->clientstruct, filestruct, filestruct->clientfilestruct, filestruct->bytes);
	firetalk_file_cancel(c, filestruct);
}

/* External function definitions */

const char *firetalk_strprotocol(const enum firetalk_protocol p) {
	if ((p >= 0) && (p < FP_MAX))
		return(firetalk_protocols[p]->strprotocol);
	return(NULL);
}

const char *firetalk_strerror(const fte_t e) {
	switch (e) {
		case FE_SUCCESS:
			return("Success");
		case FE_CONNECT:
			return("Connection failed");
		case FE_NOMATCH:
			return("Usernames do not match");
		case FE_PACKET:
			return("Packet transfer error");
		case FE_RECONNECTING:
			return("Server wants us to reconnect elsewhere");
		case FE_BADUSERPASS:
			return("Invalid username or password");
		case FE_SEQUENCE:
			return("Invalid sequence number from server");
		case FE_FRAMETYPE:
			return("Invalid frame type from server");
		case FE_PACKETSIZE:
			return("Packet too long");
		case FE_SERVER:
			return("Server problem; try again later");
		case FE_UNKNOWN:
			return("Unknown error");
		case FE_BLOCKED:
			return("You are blocked");
		case FE_WEIRDPACKET:
			return("Unknown packet received from server");
		case FE_CALLBACKNUM:
			return("Invalid callback number");
		case FE_BADUSER:
			return("Invalid username");
		case FE_NOTFOUND:
			return("Username not found in list");
		case FE_DISCONNECT:
			return("Server disconnected");
		case FE_SOCKET:
			return("Unable to create socket");
		case FE_RESOLV:
			return("Unable to resolve hostname");
		case FE_VERSION:
			return("Wrong server version");
		case FE_USERUNAVAILABLE:
			return("User is currently unavailable");
		case FE_USERINFOUNAVAILABLE:
			return("User information is currently unavailable");
		case FE_TOOFAST:
			return("You are sending messages too fast; last message was dropped");
		case FE_ROOMUNAVAILABLE:
			return("Chat room is currently unavailable");
		case FE_INCOMINGERROR:
			return("Incoming message delivery failure");
		case FE_USERDISCONNECT:
			return("User disconnected");
		case FE_INVALIDFORMAT:
			return("Server response was formatted incorrectly");
		case FE_IDLEFAST:
			return("You have requested idle to be reset too fast");
		case FE_BADROOM:
			return("Invalid room name");
		case FE_BADMESSAGE:
			return("Invalid message (too long?)");
		case FE_MESSAGETRUNCATED:
			return("Message truncated");
		case FE_BADPROTO:
			return("Invalid protocol");
		case FE_NOTCONNECTED:
			return("Not connected");
		case FE_BADCONNECTION:
			return("Invalid connection number");
		case FE_NOPERMS:
			return("No permission to perform operation");
		case FE_NOCHANGEPASS:
			return("Unable to change password");
		case FE_DUPEROOM:
			return("Room already in list");
		case FE_IOERROR:
        		return("Input/output error");
		case FE_BADHANDLE:
        		return("Invalid handle");
		case FE_TIMEOUT:
			return("Operation timed out");
		default:
			return("Invalid error number");
	}
}

firetalk_t firetalk_create_handle(const int protocol, void *clientstruct) {
	struct s_firetalk_handle *c;

	if ((protocol < 0) || (protocol >= FP_MAX)) {
		firetalkerror = FE_BADPROTO;
		return(NULL);
	}
	c = handle_head;
	handle_head = calloc(1, sizeof(*handle_head));
	if (handle_head == NULL)
		abort();
	handle_head->buffer = calloc(1, firetalk_protocols[protocol]->default_buffersize);
	if (handle_head->buffer == NULL)
		abort();
	handle_head->clientstruct = clientstruct;
	handle_head->next = c;
	handle_head->connected = FCS_NOTCONNECTED;
	handle_head->protocol = protocol;
	handle_head->handle = firetalk_protocols[protocol]->create_handle();
	return(handle_head);
}

void firetalk_destroy_handle(firetalk_t conn) {
	VERIFYCONN;

	assert(conn->deleted == 0);
	assert(conn->handle != NULL);

	firetalk_protocols[conn->protocol]->destroy_handle(conn->handle);
	conn->handle = NULL;
	conn->deleted = 1;
}

fte_t	firetalk_disconnect(firetalk_t conn) {
	VERIFYCONN;

	if (conn->connected == FCS_NOTCONNECTED)
		return(FE_NOTCONNECTED);

	return(firetalk_protocols[conn->protocol]->disconnect(conn->handle));
}

fte_t	firetalk_signon(firetalk_t conn, const char *server, short port, const char *const username) {
	VERIFYCONN;

	if (conn->connected != FCS_NOTCONNECTED) {
		firetalk_disconnect(conn);
		conn->connected = FCS_NOTCONNECTED;
	}

	free(conn->username);
	conn->username = strdup(username);
	if (conn->username == NULL)
		abort();
	conn->bufferpos = 0;

	if (server == NULL)
		server = firetalk_protocols[conn->protocol]->default_server;

	if (port == 0)
		port = firetalk_protocols[conn->protocol]->default_port;

	errno = 0;
	conn->fd = firetalk_internal_connect_host_addr(server, port, &(conn->remote_addr)
#ifdef _FC_USE_IPV6
		, &(conn->remote_addr6)
#endif
	);

	if (conn->fd != -1) {
		conn->connected = FCS_WAITING_SYNACK;
		return(FE_SUCCESS);
	} else
		return(firetalkerror);
}

fte_t	firetalk_handle_synack(firetalk_t conn) {
	int i;
	unsigned int o = sizeof(int);

	if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &i, &o)) {
		close(conn->fd);
		conn->connected = FCS_NOTCONNECTED;
		if (conn->callbacks[FC_CONNECTFAILED])
			conn->callbacks[FC_CONNECTFAILED](conn, conn->clientstruct, FE_SOCKET, strerror(errno));
		return(FE_SOCKET);
	}

	if (i != 0) {
		close(conn->fd);
		conn->connected = FCS_NOTCONNECTED;
		if (conn->callbacks[FC_CONNECTFAILED])
			conn->callbacks[FC_CONNECTFAILED](conn, conn->clientstruct, FE_CONNECT, strerror(i));
		return(FE_CONNECT);
	}

	conn->connected = FCS_WAITING_SIGNON;
	i = firetalk_protocols[conn->protocol]->signon(conn->handle, conn->username);
	if (i != FE_SUCCESS)
		return(i);

	return(FE_SUCCESS);
}

void firetalk_callback_connected(client_t c) {
	unsigned int l;
	struct sockaddr_in addr;
	struct s_firetalk_handle
		*conn = firetalk_find_handle(c);

	conn->connected = FCS_ACTIVE;
	l = (unsigned int)sizeof(struct sockaddr_in);
	getsockname(conn->fd, (struct sockaddr *)&addr, &l);
	memcpy(&conn->localip, &addr.sin_addr.s_addr, 4);
	conn->localip = htonl((uint32_t)conn->localip);

	if (conn->callbacks[FC_CONNECTED])
		conn->callbacks[FC_CONNECTED](conn, conn->clientstruct);
}

fte_t	firetalk_handle_file_synack(firetalk_t conn, struct s_firetalk_file *file) {
	int i;
	unsigned int o = sizeof(int);

	if (getsockopt(file->sockfd, SOL_SOCKET, SO_ERROR, &i, &o)) {
		firetalk_file_cancel(conn, file);
		return(FE_SOCKET);
	}

	if (i != 0) {
		firetalk_file_cancel(conn, file);
		return(FE_CONNECT);
	}

	file->state = FF_STATE_TRANSFERRING;

	if (conn->callbacks[FC_FILE_START])
		conn->callbacks[FC_FILE_START](conn, conn->clientstruct, file, file->clientfilestruct);
	return(FE_SUCCESS);
}

enum firetalk_protocol firetalk_get_protocol(firetalk_t conn) {
	VERIFYCONN;

	return(conn->protocol);
}

fte_t	firetalk_register_callback(firetalk_t conn, const int type, void (*function)(firetalk_t, void *, ...)) {
	VERIFYCONN;

	if (type < 0 || type >= FC_MAX)
		return(FE_CALLBACKNUM);
	conn->callbacks[type] = function;
	return(FE_SUCCESS);
}

fte_t	firetalk_im_add_buddy(firetalk_t conn, const char *const name, const char *const group, const char *const friendly) {
	struct s_firetalk_buddy *iter;

	VERIFYCONN;

	if ((iter = firetalk_im_find_buddy(conn, name)) != NULL) {
		if (!((strcmp(iter->group, group) == 0) && (((iter->friendly == NULL) && (friendly == NULL)) || ((iter->friendly != NULL) && (friendly != NULL) && (strcmp(iter->friendly, friendly) == 0))))) {
			/* user is in buddy list somewhere other than where the clients wants it */
			if (conn->connected != FCS_NOTCONNECTED) {
				fte_t	ret;

				ret = firetalk_protocols[conn->protocol]->im_remove_buddy(conn->handle, iter->nickname, iter->group);
				if (ret != FE_SUCCESS)
					return(ret);
			}
			free(iter->group);
			iter->group = strdup(group);
			if (iter->group == NULL)
				abort();
			free(iter->friendly);
			if (friendly == NULL)
				iter->friendly = NULL;
			else {
				iter->friendly = strdup(friendly);
				if (iter->friendly == NULL)
					abort();
			}
		}
	} else
		firetalk_im_insert_buddy(conn, name, group, friendly);

        if (conn->connected != FCS_NOTCONNECTED) {
		fte_t	ret;

		ret = firetalk_protocols[conn->protocol]->im_add_buddy(conn->handle, name, group, friendly);
		if (ret != FE_SUCCESS)
			return(ret);
	}

	if ((isonline_hack != NULL) && (firetalk_protocols[conn->protocol]->comparenicks(name, isonline_hack) == FE_SUCCESS))
		firetalk_callback_im_buddyonline(conn->handle, isonline_hack, 1);

	return(FE_SUCCESS);
}

fte_t	firetalk_im_add_deny(firetalk_t conn, const char *const nickname) {
	int ret;

	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	ret = firetalk_im_internal_add_deny(conn,nickname);
	if (ret != FE_SUCCESS)
		return(ret);

	return(firetalk_protocols[conn->protocol]->im_add_deny(conn->handle,nickname));
}

fte_t	firetalk_im_remove_deny(firetalk_t conn, const char *const nickname) {
	int ret;

	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	ret = firetalk_im_internal_remove_deny(conn,nickname);
	if (ret != FE_SUCCESS)
		return(ret);

	return(firetalk_protocols[conn->protocol]->im_remove_deny(conn->handle,nickname));
}

fte_t	firetalk_im_upload_buddies(firetalk_t conn) {
	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	return(firetalk_protocols[conn->protocol]->im_upload_buddies(conn->handle));
}

fte_t	firetalk_im_upload_denies(firetalk_t conn) {
	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	return(firetalk_protocols[conn->protocol]->im_upload_denies(conn->handle));
}

fte_t	firetalk_im_send_message(firetalk_t conn, const char *const dest, const char *const message, const int auto_flag) {
	fte_t	e;

	VERIFYCONN;

	if ((conn->connected != FCS_ACTIVE) && (strcasecmp(dest, ":RAW") != 0))
		return(FE_NOTCONNECTED);

	e = firetalk_protocols[conn->protocol]->im_send_message(conn->handle, dest, message, auto_flag);
	if (e != FE_SUCCESS)
		return(e);

	e = firetalk_protocols[conn->protocol]->periodic(conn);
	if (e != FE_SUCCESS && e != FE_IDLEFAST)
		return(e);

	return(FE_SUCCESS);
}

fte_t	firetalk_im_send_action(firetalk_t conn, const char *const dest, const char *const message, const int auto_flag) {
	fte_t	e;

	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	e = firetalk_protocols[conn->protocol]->im_send_action(conn->handle, dest, message, auto_flag);
	if (e != FE_SUCCESS)
		return(e);

	e = firetalk_protocols[conn->protocol]->periodic(conn);
	if (e != FE_SUCCESS && e != FE_IDLEFAST)
		return(e);

	return(FE_SUCCESS);
}

fte_t	firetalk_im_get_info(firetalk_t conn, const char *const nickname) {
	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	return(firetalk_protocols[conn->protocol]->get_info(conn->handle, nickname));
}

fte_t	firetalk_set_info(firetalk_t conn, const char *const info) {
	VERIFYCONN;

	if (conn->connected == FCS_NOTCONNECTED)
		return(FE_NOTCONNECTED);

	return(firetalk_protocols[conn->protocol]->set_info(conn->handle, info));
}

fte_t	firetalk_im_list_buddies(firetalk_t conn) {
	struct s_firetalk_buddy *buddyiter;

	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	if (!conn->callbacks[FC_IM_LISTBUDDY])
		return(FE_SUCCESS);

	for (buddyiter = conn->buddy_head; buddyiter != NULL; buddyiter = buddyiter->next)
		conn->callbacks[FC_IM_LISTBUDDY](conn, conn->clientstruct, buddyiter->nickname, buddyiter->group, buddyiter->friendly, buddyiter->online, buddyiter->away, buddyiter->idletime);

	return(FE_SUCCESS);
}

fte_t	firetalk_chat_listmembers(firetalk_t conn, const char *const roomname) {
	struct s_firetalk_room *room;
	struct s_firetalk_member *memberiter;

	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	if (!conn->callbacks[FC_CHAT_LISTMEMBER])
		return(FE_SUCCESS);

	room = firetalk_find_room(conn, roomname);
	if (room == NULL)
		return(firetalkerror);

	for (memberiter = room->member_head; memberiter != NULL; memberiter = memberiter->next)
		conn->callbacks[FC_CHAT_LISTMEMBER](conn, conn->clientstruct, room->name, memberiter->nickname, memberiter->admin);

	return(FE_SUCCESS);
}

const char *firetalk_chat_normalize(firetalk_t conn, const char *const room) {
	return(firetalk_protocols[conn->protocol]->room_normalize(room));
}

fte_t	firetalk_set_away(firetalk_t conn, const char *const message, const int auto_flag) {
	VERIFYCONN;

	if (conn->connected == FCS_NOTCONNECTED)
		return(FE_NOTCONNECTED);

	return(firetalk_protocols[conn->protocol]->set_away(conn->handle, message, auto_flag));
}

fte_t	firetalk_set_nickname(firetalk_t conn, const char *const nickname) {
	VERIFYCONN;

	if (conn->connected == FCS_NOTCONNECTED)
		return(FE_NOTCONNECTED);

	return(firetalk_protocols[conn->protocol]->set_nickname(conn->handle, nickname));
}

fte_t	firetalk_set_password(firetalk_t conn, const char *const oldpass, const char *const newpass) {
	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	return(firetalk_protocols[conn->protocol]->set_password(conn->handle, oldpass, newpass));
}

fte_t	firetalk_set_privacy(firetalk_t conn, const char *const mode) {
	VERIFYCONN;

	assert(mode != NULL);

	if (conn->connected == FCS_NOTCONNECTED)
		return(FE_NOTCONNECTED);

	return(firetalk_protocols[conn->protocol]->set_privacy(conn->handle, mode));
}

fte_t	firetalk_im_evil(firetalk_t conn, const char *const who) {
	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	return(firetalk_protocols[conn->protocol]->im_evil(conn->handle, who));
}

fte_t	firetalk_chat_join(firetalk_t conn, const char *const room) {
	const char *normalroom;

	VERIFYCONN;

	if (conn->connected == FCS_NOTCONNECTED)
		return(FE_NOTCONNECTED);

	normalroom = firetalk_protocols[conn->protocol]->room_normalize(room);
	if (!normalroom)
		return(FE_ROOMUNAVAILABLE);

	return(firetalk_protocols[conn->protocol]->chat_join(conn->handle, normalroom));
}

fte_t	firetalk_chat_part(firetalk_t conn, const char *const room) {
	const char *normalroom;

	VERIFYCONN;

	if (conn->connected == FCS_NOTCONNECTED)
		return(FE_NOTCONNECTED);

	normalroom = firetalk_protocols[conn->protocol]->room_normalize(room);
	if (!normalroom)
		return(FE_ROOMUNAVAILABLE);

	return(firetalk_protocols[conn->protocol]->chat_part(conn->handle, normalroom));
}

fte_t	firetalk_chat_send_message(firetalk_t conn, const char *const room, const char *const message, const int auto_flag) {
	const char *normalroom;

	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	if (*room == ':')
		normalroom = room;
	else
		normalroom = firetalk_protocols[conn->protocol]->room_normalize(room);
	if (!normalroom)
		return(FE_ROOMUNAVAILABLE);

	return(firetalk_protocols[conn->protocol]->chat_send_message(conn->handle, normalroom, message, auto_flag));
}

fte_t	firetalk_chat_send_action(firetalk_t conn, const char *const room, const char *const message, const int auto_flag) {
	const char *normalroom;

	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	normalroom = firetalk_protocols[conn->protocol]->room_normalize(room);
	if (!normalroom)
		return(FE_ROOMUNAVAILABLE);

	return(firetalk_protocols[conn->protocol]->chat_send_action(conn->handle, normalroom, message, auto_flag));
}

fte_t	firetalk_chat_invite(firetalk_t conn, const char *const room, const char *const who, const char *const message) {
	const char *normalroom;

	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	normalroom = firetalk_protocols[conn->protocol]->room_normalize(room);
	if (!normalroom)
		return(FE_ROOMUNAVAILABLE);

	return(firetalk_protocols[conn->protocol]->chat_invite(conn->handle, normalroom, who, message));
}

fte_t	firetalk_chat_set_topic(firetalk_t conn, const char *const room, const char *const topic) {
	const char *normalroom;

	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	normalroom = firetalk_protocols[conn->protocol]->room_normalize(room);
	if (!normalroom)
		return(FE_ROOMUNAVAILABLE);

	return(firetalk_protocols[conn->protocol]->chat_set_topic(conn->handle, normalroom, topic));
}

fte_t	firetalk_chat_op(firetalk_t conn, const char *const room, const char *const who) {
	const char *normalroom;

	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	normalroom = firetalk_protocols[conn->protocol]->room_normalize(room);
	if (!normalroom)
		return(FE_ROOMUNAVAILABLE);

	return(firetalk_protocols[conn->protocol]->chat_op(conn->handle, normalroom, who));
}

fte_t	firetalk_chat_deop(firetalk_t conn, const char *const room, const char *const who) {
	const char *normalroom;

	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	normalroom = firetalk_protocols[conn->protocol]->room_normalize(room);
	if (!normalroom)
		return(FE_ROOMUNAVAILABLE);

	return(firetalk_protocols[conn->protocol]->chat_deop(conn->handle, normalroom, who));
}

fte_t	firetalk_chat_kick(firetalk_t conn, const char *const room, const char *const who, const char *const reason) {
	const char *normalroom;

	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

	normalroom = firetalk_protocols[conn->protocol]->room_normalize(room);
	if (!normalroom)
		return(FE_ROOMUNAVAILABLE);

	return(firetalk_protocols[conn->protocol]->chat_kick(conn->handle, normalroom, who, reason));
}

fte_t	firetalk_subcode_send_request(firetalk_t conn, const char *const to, const char *const command, const char *const args) {
	VERIFYCONN;

	if (conn->connected != FCS_ACTIVE)
		return(FE_NOTCONNECTED);

//	return(firetalk_protocols[conn->protocol]->subcode_send_request(conn->handle, to, command, args));
	firetalk_enqueue(&conn->subcode_requests, to, firetalk_protocols[conn->protocol]->subcode_encode(conn->handle, command, args));
	return(FE_SUCCESS);
}

fte_t	firetalk_subcode_send_reply(firetalk_t conn, const char *const to, const char *const command, const char *const args) {
	VERIFYCONN;

	if ((conn->connected != FCS_ACTIVE) && (*to != ':'))
		return(FE_NOTCONNECTED);

//	return(firetalk_protocols[conn->protocol]->subcode_send_reply(conn->handle, to, command, args));
	firetalk_enqueue(&conn->subcode_replies, to, firetalk_protocols[conn->protocol]->subcode_encode(conn->handle, command, args));
	return(FE_SUCCESS);
}

fte_t	firetalk_subcode_register_request_callback(firetalk_t conn, const char *const command, void (*callback)(firetalk_t, void *, const char *const, const char *const, const char *const)) {
	struct s_firetalk_subcode_callback *iter;

	VERIFYCONN;

	if (command == NULL) {
		if (conn->subcode_request_default)
			free(conn->subcode_request_default);
		conn->subcode_request_default = calloc(1, sizeof(struct s_firetalk_subcode_callback));
		if (conn->subcode_request_default == NULL)
			abort();
		conn->subcode_request_default->callback = (ptrtofnct)callback;
	} else {
		iter = conn->subcode_request_head;
		conn->subcode_request_head = calloc(1, sizeof(struct s_firetalk_subcode_callback));
		if (conn->subcode_request_head == NULL)
			abort();
		conn->subcode_request_head->next = iter;
		conn->subcode_request_head->command = strdup(command);
		if (conn->subcode_request_head->command == NULL)
			abort();
		conn->subcode_request_head->callback = (ptrtofnct)callback;
	}
	return(FE_SUCCESS);
}

fte_t	firetalk_subcode_register_request_reply(firetalk_t conn, const char *const command, const char *const reply) {
	struct s_firetalk_subcode_callback *iter;

	VERIFYCONN;

	if (command == NULL) {
		if (conn->subcode_request_default)
			free(conn->subcode_request_default);
		conn->subcode_request_default = calloc(1, sizeof(struct s_firetalk_subcode_callback));
		if (conn->subcode_request_default == NULL)
			abort();
		conn->subcode_request_default->staticresp = strdup(reply);
		if (conn->subcode_request_default->staticresp == NULL)
			abort();
	} else {
		iter = conn->subcode_request_head;
		conn->subcode_request_head = calloc(1, sizeof(struct s_firetalk_subcode_callback));
		if (conn->subcode_request_head == NULL)
			abort();
		conn->subcode_request_head->next = iter;
		conn->subcode_request_head->command = strdup(command);
		if (conn->subcode_request_head->command == NULL)
			abort();
		conn->subcode_request_head->staticresp = strdup(reply);
		if (conn->subcode_request_head->staticresp == NULL)
			abort();
	}
	return(FE_SUCCESS);
}

fte_t	firetalk_subcode_register_reply_callback(firetalk_t conn, const char *const command, void (*callback)(firetalk_t, void *, const char *const, const char *const, const char *const)) {
	struct s_firetalk_subcode_callback *iter;

	VERIFYCONN;

	if (command == NULL) {
		if (conn->subcode_reply_default)
			free(conn->subcode_reply_default);
		conn->subcode_reply_default = calloc(1, sizeof(struct s_firetalk_subcode_callback));
		if (conn->subcode_reply_default == NULL)
			abort();
		conn->subcode_reply_default->callback = (ptrtofnct)callback;
	} else {
		iter = conn->subcode_reply_head;
		conn->subcode_reply_head = calloc(1, sizeof(struct s_firetalk_subcode_callback));
		if (conn->subcode_reply_head == NULL)
			abort();
		conn->subcode_reply_head->next = iter;
		conn->subcode_reply_head->command = strdup(command);
		if (conn->subcode_reply_head->command == NULL)
			abort();
		conn->subcode_reply_head->callback = (ptrtofnct)callback;
	}
	return(FE_SUCCESS);
}

fte_t	firetalk_file_offer(firetalk_t conn, const char *const nickname, const char *const filename, void *clientfilestruct) {
	struct s_firetalk_file *iter;
	struct stat s;
	struct sockaddr_in addr;
	char args[256];
	unsigned int l;

	VERIFYCONN;

	iter = conn->file_head;
	conn->file_head = calloc(1, sizeof(struct s_firetalk_file));
	if (conn->file_head == NULL)
		abort();
	conn->file_head->who = strdup(nickname);
	if (conn->file_head->who == NULL)
		abort();
	conn->file_head->filename = strdup(filename);
	if (conn->file_head->filename == NULL)
		abort();
	conn->file_head->sockfd = -1;
	conn->file_head->clientfilestruct = clientfilestruct;

	conn->file_head->filefd = open(filename, O_RDONLY);
	if (conn->file_head->filefd == -1) {
		firetalk_file_cancel(conn, conn->file_head);
		return(FE_IOERROR);
	}

	if (fstat(conn->file_head->filefd, &s) != 0) {
		firetalk_file_cancel(conn, conn->file_head);
		return(FE_IOERROR);
	}

	conn->file_head->size = (long)s.st_size;

	conn->file_head->sockfd = socket(PF_INET, SOCK_STREAM, 0);
	if (conn->file_head->sockfd == -1) {
		firetalk_file_cancel(conn, conn->file_head);
		return(FE_SOCKET);
	}

	addr.sin_family = AF_INET;
	addr.sin_port = 0;
	addr.sin_addr.s_addr = INADDR_ANY;
	if (bind(conn->file_head->sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) != 0) {
		firetalk_file_cancel(conn, conn->file_head);
		return(FE_SOCKET);
	}

	if (listen(conn->file_head->sockfd, 1) != 0) {
		firetalk_file_cancel(conn, conn->file_head);
		return(FE_SOCKET);
	}

	l = (unsigned int)sizeof(struct sockaddr_in);
	if (getsockname(conn->file_head->sockfd, (struct sockaddr *)&addr, &l) != 0) {
		firetalk_file_cancel(conn, conn->file_head);
		return(FE_SOCKET);
	}

	conn->file_head->bytes = 0;
	conn->file_head->state = FF_STATE_WAITREMOTE;
	conn->file_head->direction = FF_DIRECTION_SENDING;
	conn->file_head->port = ntohs(addr.sin_port);
	conn->file_head->next = iter;
	conn->file_head->type = FF_TYPE_DCC;
	snprintf(args, sizeof(args), "SEND %s %lu %u %ld", conn->file_head->filename, conn->localip, conn->file_head->port, conn->file_head->size);
	return(firetalk_subcode_send_request(conn, nickname, "DCC", args));
}

fte_t	firetalk_file_accept(firetalk_t conn, void *filehandle, void *clientfilestruct, const char *const localfile) {
	struct s_firetalk_file *fileiter;
	struct sockaddr_in addr;

	VERIFYCONN;

	fileiter = filehandle;
	fileiter->clientfilestruct = clientfilestruct;

	fileiter->filefd = open(localfile, O_WRONLY|O_CREAT|O_EXCL, S_IRWXU);
	if (fileiter->filefd == -1)
		return(FE_NOPERMS);

	addr.sin_family = AF_INET;
	addr.sin_port = fileiter->port;
	memcpy(&addr.sin_addr.s_addr, &fileiter->inet_ip, 4);
	fileiter->sockfd = firetalk_internal_connect(&addr
#ifdef _FC_USE_IPV6
	, NULL
#endif
	);
	if (fileiter->sockfd == -1) {
		firetalk_file_cancel(conn, filehandle);
		return(FE_SOCKET);
	}
	fileiter->state = FF_STATE_WAITSYNACK;
	return(FE_SUCCESS);
}

fte_t	firetalk_file_cancel(firetalk_t conn, void *filehandle) {
	struct s_firetalk_file *fileiter, *prev;

	VERIFYCONN;

	prev = NULL;
	for (fileiter = conn->file_head; fileiter != NULL; fileiter = fileiter->next) {
		if (fileiter == filehandle) {
			if (prev != NULL)
				prev->next = fileiter->next;
			else
				conn->file_head = fileiter->next;
			if (fileiter->who) {
				free(fileiter->who);
				fileiter->who = NULL;
			}
			if (fileiter->filename) {
				free(fileiter->filename);
				fileiter->filename = NULL;
			}
			if (fileiter->sockfd >= 0) {
				close(fileiter->sockfd);
				fileiter->sockfd = -1;
			}
			if (fileiter->filefd >= 0) {
				close(fileiter->filefd);
				fileiter->filefd = -1;
			}
			free(fileiter);
			return(FE_SUCCESS);
		}
		prev = fileiter;
	}
	return(FE_NOTFOUND);
}

fte_t	firetalk_file_refuse(firetalk_t conn, void *filehandle) {
	return(firetalk_file_cancel(conn, filehandle));
}

fte_t	firetalk_compare_nicks(firetalk_t conn, const char *const nick1, const char *const nick2) {
	VERIFYCONN;

	return(firetalk_protocols[conn->protocol]->comparenicks(nick1, nick2));
}

fte_t	firetalk_isprint(firetalk_t conn, const int c) {
	VERIFYCONN;

	return(firetalk_protocols[conn->protocol]->isprintable(c));
}

fte_t	firetalk_select() {
	return(firetalk_select_custom(0, NULL, NULL, NULL, NULL));
}

fte_t	firetalk_select_custom(int n, fd_set *fd_read, fd_set *fd_write, fd_set *fd_except, struct timeval *timeout) {
	int ret;
	fd_set *my_read, *my_write, *my_except;
	fd_set internal_read, internal_write, internal_except;
	struct timeval internal_timeout, *my_timeout;
	struct s_firetalk_handle *fchandle;

	my_read = fd_read;
	my_write = fd_write;
	my_except = fd_except;
	my_timeout = timeout;

	if (!my_read) {
		my_read = &internal_read;
		FD_ZERO(my_read);
	}

	if (!my_write) {
		my_write = &internal_write;
		FD_ZERO(my_write);
	}

	if (!my_except) {
		my_except = &internal_except;
		FD_ZERO(my_except);
	}

	if (!my_timeout) {
		my_timeout = &internal_timeout;
		my_timeout->tv_sec = 15;
		my_timeout->tv_usec = 0;
	}

	if (my_timeout->tv_sec > 15)
		my_timeout->tv_sec = 15;

	/* internal preselect */
	for (fchandle = handle_head; fchandle != NULL; fchandle = fchandle->next) {
		struct s_firetalk_file *fileiter;

		if (fchandle->deleted)
			continue;

		if (fchandle->connected == FCS_NOTCONNECTED)
			continue;

		while (fchandle->subcode_requests.count > 0) {
			int	count = fchandle->subcode_requests.count;
			char	*key = strdup(fchandle->subcode_requests.keys[0]);

			firetalk_protocols[fchandle->protocol]->im_send_message(fchandle->handle, key, "", 0);
			free(key);
			assert(fchandle->subcode_requests.count < count);
		}

		while (fchandle->subcode_replies.count > 0) {
			int	count = fchandle->subcode_replies.count;
			char	*key = strdup(fchandle->subcode_replies.keys[0]);

			firetalk_protocols[fchandle->protocol]->im_send_message(fchandle->handle, key, "", 1);
			free(key);
			assert(fchandle->subcode_replies.count < count);
		}

		firetalk_protocols[fchandle->protocol]->periodic(fchandle);
		if (fchandle->connected == FCS_NOTCONNECTED)
			continue;

		if (fchandle->fd >= n)
			n = fchandle->fd + 1;
		assert(fchandle->fd >= 0);
		FD_SET(fchandle->fd, my_except);
		if (fchandle->connected == FCS_WAITING_SYNACK)
			FD_SET(fchandle->fd, my_write);
		else
			FD_SET(fchandle->fd, my_read);

		for (fileiter = fchandle->file_head; fileiter != NULL; fileiter = fileiter->next) {
			if (fileiter->state == FF_STATE_TRANSFERRING) {
				if (fileiter->sockfd >= n)
					n = fileiter->sockfd + 1;
				switch (fileiter->direction) {
				  case FF_DIRECTION_SENDING:
					assert(fileiter->sockfd >= 0);
					FD_SET(fileiter->sockfd, my_write);
					FD_SET(fileiter->sockfd, my_except);
					break;
				  case FF_DIRECTION_RECEIVING:
					assert(fileiter->sockfd >= 0);
					FD_SET(fileiter->sockfd, my_read);
					FD_SET(fileiter->sockfd, my_except);
					break;
				}
			} else if (fileiter->state == FF_STATE_WAITREMOTE) {
				assert(fileiter->sockfd >= 0);
				if (fileiter->sockfd >= n)
					n = fileiter->sockfd + 1;
				FD_SET(fileiter->sockfd, my_read);
				FD_SET(fileiter->sockfd, my_except);
			} else if (fileiter->state == FF_STATE_WAITSYNACK) {
				assert(fileiter->sockfd >= 0);
				if (fileiter->sockfd >= n)
					n = fileiter->sockfd + 1;
				FD_SET(fileiter->sockfd, my_write);
				FD_SET(fileiter->sockfd, my_except);
			}
		}
	}

	/* per-protocol preselect, UI prepoll */
	for (fchandle = handle_head; fchandle != NULL; fchandle = fchandle->next) {
		if (fchandle->deleted)
			continue;
		firetalk_protocols[fchandle->protocol]->preselect(fchandle->handle, my_read, my_write, my_except, &n);
		if (fchandle->callbacks[FC_PRESELECT])
			fchandle->callbacks[FC_PRESELECT](fchandle, fchandle->clientstruct);
	}

	/* select */
	if (n > 0) {
		ret = select(n, my_read, my_write, my_except, my_timeout);
		if (ret == -1)
			return(FE_PACKET);
	}

	/* per-protocol postselect, UI postpoll */
	for (fchandle = handle_head; fchandle != NULL; fchandle = fchandle->next) {
		if (fchandle->deleted)
			continue;

		firetalk_protocols[fchandle->protocol]->postselect(fchandle->handle, my_read, my_write, my_except);
		if (fchandle->callbacks[FC_POSTSELECT])
			fchandle->callbacks[FC_POSTSELECT](fchandle, fchandle->clientstruct);
	}

	/* internal postpoll */
	for (fchandle = handle_head; fchandle != NULL; fchandle = fchandle->next) {
		struct s_firetalk_file *fileiter, *filenext;

		if (fchandle->deleted)
			continue;

		if (fchandle->connected == FCS_NOTCONNECTED)
			continue;
		assert(fchandle->fd >= 0);
		if (FD_ISSET(fchandle->fd, my_except))
			firetalk_protocols[fchandle->protocol]->disconnect(fchandle->handle);
		else if (FD_ISSET(fchandle->fd, my_read)) {
			short	length;

			/* read data into handle buffer */
			length = recv(fchandle->fd, &fchandle->buffer[fchandle->bufferpos], firetalk_protocols[fchandle->protocol]->default_buffersize - fchandle->bufferpos, MSG_DONTWAIT);

			if (length < 1)
				firetalk_callback_disconnect(fchandle->handle, FE_DISCONNECT);
			else {
				fchandle->bufferpos += length;
				if (fchandle->connected == FCS_ACTIVE)
					firetalk_protocols[fchandle->protocol]->got_data(fchandle->handle, fchandle->buffer, &fchandle->bufferpos);
				else
					firetalk_protocols[fchandle->protocol]->got_data_connecting(fchandle->handle, fchandle->buffer, &fchandle->bufferpos);
				if (fchandle->bufferpos == firetalk_protocols[fchandle->protocol]->default_buffersize)
					firetalk_callback_disconnect(fchandle->handle, FE_PACKETSIZE);
			}
		} else if (FD_ISSET(fchandle->fd, my_write))
			firetalk_handle_synack(fchandle);

		for (fileiter = fchandle->file_head; fileiter != NULL; fileiter = filenext) {
			filenext = fileiter->next;
			if (fileiter->state == FF_STATE_TRANSFERRING) {
				assert(fileiter->sockfd >= 0);
				if (FD_ISSET(fileiter->sockfd, my_write))
					firetalk_handle_send(fchandle, fileiter);
				if ((fileiter->sockfd != -1) && FD_ISSET(fileiter->sockfd, my_read))
					firetalk_handle_receive(fchandle, fileiter);
				if ((fileiter->sockfd != -1) && FD_ISSET(fileiter->sockfd, my_except)) {
					if (fchandle->callbacks[FC_FILE_ERROR])
						fchandle->callbacks[FC_FILE_ERROR](fchandle, fchandle->clientstruct, fileiter, fileiter->clientfilestruct, FE_IOERROR);
					firetalk_file_cancel(fchandle, fileiter);
				}
			} else if (fileiter->state == FF_STATE_WAITREMOTE) {
				assert(fileiter->sockfd >= 0);
				if (FD_ISSET(fileiter->sockfd, my_read)) {
					unsigned int l = sizeof(struct sockaddr_in);
					struct sockaddr_in addr;
					int s;

					s = accept(fileiter->sockfd, (struct sockaddr *)&addr, &l);
					if (s == -1) {
						if (fchandle->callbacks[FC_FILE_ERROR])
							fchandle->callbacks[FC_FILE_ERROR](fchandle, fchandle->clientstruct, fileiter, fileiter->clientfilestruct, FE_SOCKET);
						firetalk_file_cancel(fchandle, fileiter);
					} else {
						close(fileiter->sockfd);
						fileiter->sockfd = s;
						fileiter->state = FF_STATE_TRANSFERRING;
						if (fchandle->callbacks[FC_FILE_START])
							fchandle->callbacks[FC_FILE_START](fchandle, fchandle->clientstruct, fileiter, fileiter->clientfilestruct);
					}
				} else if (FD_ISSET(fileiter->sockfd, my_except)) {
					if (fchandle->callbacks[FC_FILE_ERROR])
						fchandle->callbacks[FC_FILE_ERROR](fchandle, fchandle->clientstruct, fileiter, fileiter->clientfilestruct, FE_IOERROR);
					firetalk_file_cancel(fchandle, fileiter);
				}
			} else if (fileiter->state == FF_STATE_WAITSYNACK) {
				assert(fileiter->sockfd >= 0);
				if (FD_ISSET(fileiter->sockfd, my_write))
					firetalk_handle_file_synack(fchandle, fileiter);
				if (FD_ISSET(fileiter->sockfd, my_except))
					firetalk_file_cancel(fchandle, fileiter);
			}
		}
	}

	/* handle deleted connections */
	{
		struct s_firetalk_handle *fchandleprev, *fchandlenext;

		fchandleprev = NULL;
		for (fchandle = handle_head; fchandle != NULL; fchandle = fchandlenext) {
			fchandlenext = fchandle->next;
			if (fchandle->deleted == 1) {
				assert(fchandle->handle == NULL);
				if (fchandle->buddy_head != NULL) {
					struct s_firetalk_buddy *iter, *iternext;

					for (iter = fchandle->buddy_head; iter != NULL; iter = iternext) {
						iternext = iter->next;
						if (iter->nickname != NULL) {
							free(iter->nickname);
							iter->nickname = NULL;
						}
						if (iter->group != NULL) {
							free(iter->group);
							iter->group = NULL;
						}
						if (iter->capabilities != NULL) {
							free(iter->capabilities);
							iter->capabilities = NULL;
						}
						free(iter);
					}
					fchandle->buddy_head = NULL;
				}
				if (fchandle->deny_head != NULL) {
					struct s_firetalk_deny *iter, *iternext;

					for (iter = fchandle->deny_head; iter != NULL; iter = iternext) {
						iternext = iter->next;
						if (iter->nickname != NULL) {
							free(iter->nickname);
							iter->nickname = NULL;
						}
						free(iter);
					}
					fchandle->deny_head = NULL;
				}
				if (fchandle->room_head != NULL) {
					struct s_firetalk_room *iter, *iternext;

					for (iter = fchandle->room_head; iter != NULL; iter = iternext) {
						struct s_firetalk_member *memberiter, *memberiternext;

						for (memberiter = iter->member_head; memberiter != NULL; memberiter = memberiternext) {
							memberiternext = memberiter->next;
							if (memberiter->nickname != NULL) {
								free(memberiter->nickname);
								memberiter->nickname = NULL;
							}
							free(memberiter);
						}
						iter->member_head = NULL;
						iternext = iter->next;
						if (iter->name != NULL) {
							free(iter->name);
							iter->name = NULL;
						}
						free(iter);
					}
					fchandle->room_head = NULL;
				}
				if (fchandle->file_head != NULL) {
					struct s_firetalk_file *iter, *iternext;

					for (iter = fchandle->file_head; iter != NULL; iter = iternext) {
						iternext = iter->next;
						if (iter->who != NULL) {
							free(iter->who);
							iter->who = NULL;
						}
						if (iter->filename != NULL) {
							free(iter->filename);
							iter->filename = NULL;
						}
						free(iter);
					}
					fchandle->file_head = NULL;
				}
				if (fchandle->subcode_request_head != NULL) {
					struct s_firetalk_subcode_callback *iter, *iternext;

					for (iter = fchandle->subcode_request_head; iter != NULL; iter = iternext) {
						iternext = iter->next;
						if (iter->command != NULL) {
							free(iter->command);
							iter->command = NULL;
						}
						if (iter->staticresp != NULL) {
							free(iter->staticresp);
							iter->staticresp = NULL;
						}
						free(iter);
					}
					fchandle->subcode_request_head = NULL;
				}
				if (fchandle->subcode_request_default != NULL) {
					if (fchandle->subcode_request_default->command != NULL) {
						free(fchandle->subcode_request_default->command);
						fchandle->subcode_request_default->command = NULL;
					}
					free(fchandle->subcode_request_default);
					fchandle->subcode_request_default = NULL;
				}
				if (fchandle->subcode_reply_head != NULL) {
					struct s_firetalk_subcode_callback *iter, *iternext;

					for (iter = fchandle->subcode_reply_head; iter != NULL; iter = iternext) {
						iternext = iter->next;
						free(iter->command);
						free(iter);
					}
					fchandle->subcode_reply_head = NULL;
				}
				if (fchandle->subcode_reply_default != NULL) {
					if (fchandle->subcode_reply_default->command != NULL) {
						free(fchandle->subcode_reply_default->command);
						fchandle->subcode_reply_default->command = NULL;
					}
					free(fchandle->subcode_reply_default);
					fchandle->subcode_reply_default = NULL;
				}
				if (fchandle->username != NULL) {
					free(fchandle->username);
					fchandle->username = NULL;
				}
				if (fchandle->buffer != NULL) {
					free(fchandle->buffer);
					fchandle->buffer = NULL;
				}
				if (fchandleprev == NULL) {
					assert(fchandle == handle_head);
					handle_head = fchandlenext;
				} else {
					assert(fchandle != handle_head);
					fchandleprev->next = fchandlenext;
				}

				free(fchandle);
			} else
				fchandleprev = fchandle;
		}
	}

	return(FE_SUCCESS);
}

void	firetalk_enqueue(firetalk_queue_t *queue, const char *const key, void *data) {
	queue->count++;
	queue->keys = realloc(queue->keys, (queue->count)*sizeof(*(queue->keys)));
	queue->data = realloc(queue->data, (queue->count)*sizeof(*(queue->data)));
	queue->keys[queue->count-1] = strdup(key);
	if (queue->keys[queue->count-1] == NULL)
		abort();
	queue->data[queue->count-1] = data;
}

const void *firetalk_peek(firetalk_queue_t *queue, const char *const key) {
	int	i;

	assert(queue != NULL);
	assert(key != NULL);

	for (i = 0; i < queue->count; i++)
		if (strcmp(queue->keys[i], key) == 0)
			return(queue->data[i]);
	return(NULL);
}

void	*firetalk_dequeue(firetalk_queue_t *queue, const char *const key) {
	int	i;

	assert(queue != NULL);
	assert(key != NULL);

	for (i = 0; i < queue->count; i++)
		if (strcmp(queue->keys[i], key) == 0) {
			void	*data = queue->data[i];

			free(queue->keys[i]);
			queue->keys[i] = NULL;
			memmove(queue->keys+i, queue->keys+i+1, (queue->count-i-1)*sizeof(*(queue->keys)));
			memmove(queue->data+i, queue->data+i+1, (queue->count-i-1)*sizeof(*(queue->data)));
			queue->count--;
			queue->keys = realloc(queue->keys, (queue->count)*sizeof(*(queue->keys)));
			queue->data = realloc(queue->data, (queue->count)*sizeof(*(queue->data)));
			return(data);
		}
	return(NULL);
}

void	firetalk_queue_append(char *buf, int buflen, firetalk_queue_t *queue, const char *const key) {
	const char *data;

	while ((data = firetalk_peek(queue, key)) != NULL) {
		if (strlen(buf)+strlen(data) >= buflen-1)
			break;
		strcat(buf, data);
		free(firetalk_dequeue(queue, key));
	}
}
