summaryrefslogblamecommitdiffstats
path: root/main.c
blob: e38ec0a0367043e2c611aa9bc7783f1e99b4a8ac (plain) (tree)






































































































                                                                                                     
                                                                                       













































































































































                                                                                                      
                                                                                     




                                                                                               
                                                                     
















































                                                                                                                                                               

                       




                                                                                            



                                                                        



                                                                                                 



                                                                     























































































































































































                                                                                                       
                 



                                                



                                                                                                  

                         






                                                       
































































                                                                                                     
                                                                                                   











                                                           
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strophe.h>
#include <ctype.h>
#include <time.h>
#include <sys/select.h>
#include <errno.h>
#include <signal.h>
#include "ircxmpp.h"
void free_bridge (struct bridge ** bridge, const char * razlog) {
	if (!bridge || !*bridge)
		return;
	fprintf(stderr, "freeing bridge with reason: %s\n", razlog);
	if ((*bridge)->irc) {
		irc_cmd_quit((*bridge)->irc, razlog);
		irc_run_once(*bridge); // verjetno je to potrebno, da pošlje quit
		irc_destroy_session((*bridge)->irc);
	}
	if ((*bridge)->conn)
		xmpp_conn_release((*bridge)->conn); /* graceful disconnect, what is that? */
	free((*bridge)->identifier);
	for (size_t i = 0; i < (*bridge)->messages_length; i++)
		free((*bridge)->messages[i]);
	free((*bridge)->messages);
	if ((*bridge)->next)
		(*bridge)->next->prev = (*bridge)->prev;
	struct bridge * tofree = *bridge;
	if ((*bridge)->prev)
		(*bridge)->prev->next = (*bridge)->next;
	else
		*bridge = (*bridge)->next;
	free(tofree);
}
void free_bridges (struct bridge ** bridges) {
	while (*bridges) // enkrat bo *bridges NULL, ker ne bo nobenega več notri
		free_bridge(bridges, "vsi mostovi se podirajo, ker se ircxmpp izklaplja");
}

struct bridge ** find_bridge (struct bridge ** bridges, const char * identifier, enum side side) {
s:
	if (!bridges || !*bridges)
		return NULL;
	if ((*bridges)->side == side && !strcmp((*bridges)->identifier, identifier))
		return bridges;
	bridges = &((*bridges)->next);
	goto s;
}

void jid2ircnick (char * jid) { /* edits a jid into an irc-compatible nick. libera trims nicks. */
	char * c = strchr(jid, '/');
	if (c) {
		c++;
		memmove(jid, c, strlen(c)+1);
	}
	if (*jid && isdigit(*jid))
		*jid = '_';
#define VALID_NICK "{}[]_^`|\\"
	while (*++jid != '\0')
		if (!(isalnum(*jid) || memchr(VALID_NICK, *jid, strlen(VALID_NICK))))
			*jid = '_';
}
void jid2ircuser (char * jid) {
	char * c = strchr(jid, '/');
	if (c) {
		c++;
		memmove(jid, c, strlen(c)+1);
	}
	if (*jid && isdigit(*jid)) {
		*jid = '_';
	}
#define VALID_USER ""
	while (*++jid != '\0')
		if (!(isalnum(*jid) || memchr(VALID_USER, *jid, strlen(VALID_USER))))
			*jid = 'x';
}
void bridge_forward (const char * from, const char * msg, struct ircxmpp * ircxmpp, enum side side) {
	struct bridge ** bridge_resp = find_bridge(&ircxmpp->bridges, from, !side);
	if (strstr(from, "ircxmpp_") || (ircxmpp->irchost && strstr(from, ircxmpp->irchost)))
		return;
	fprintf(stderr, "sending text from %s to %s: %s\n", from, side == IRC ? "IRC" : "XMPP",
			msg ? msg : "[join only]");
	struct bridge * bridge;
	if (!bridge_resp) {
		bridge = calloc(1, sizeof(struct bridge));
		if (ircxmpp->bridges)
			ircxmpp->bridges->prev = bridge;
		bridge->next = ircxmpp->bridges;
		ircxmpp->bridges = bridge;
		bridge->identifier = strdup(from);
		bridge->ircxmpp = ircxmpp;
		bridge->side = !side;
		if (side == IRC)
			init_irc(bridge);
		else {
			bridge->conn = xmpp_conn_new(bridge->ircxmpp->ctx);
			xmpp_conn_set_jid(bridge->conn, bridge->ircxmpp->jid);
			xmpp_conn_set_pass(bridge->conn, bridge->ircxmpp->password);
			xmpp_connect_client(bridge->conn, NULL, 0, conn_handler_bridge, bridge);
		}
	} else
		bridge = *bridge_resp;
	if (side == IRC) {
		irc_cmd_join(bridge->irc, ircxmpp->channel, ircxmpp->channel_password);
		irc_run_once(bridge);
		if (msg)
			irc_cmd_msg(bridge->irc, ircxmpp->channel, msg);
		irc_run_once(bridge);
	} else if (msg) {
			bridge->messages = realloc(bridge->messages, bridge->messages_length+1);
			bridge->messages[bridge->messages_length++] = strdup(msg);
		}
} /* msg can be NULL, in that case we only join. */
int message_handler (xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata) {
	if (!conn) /* just to get rid of -Wunused-parameter */
		return 1;
	struct ircxmpp * ircxmpp = (struct ircxmpp *) userdata;
	xmpp_stanza_t * body;
	const char * type;
	char * intext;
	body = xmpp_stanza_get_child_by_name(stanza, "body");
	if (body == NULL)
		return 1;
	type = xmpp_stanza_get_type(stanza);
	if (type != NULL && strcmp(type, "error") == 0)
		return 1;
	if (!strncmp(ircxmpp->jid, xmpp_stanza_get_from(stanza), strlen(ircxmpp->jid)))
		return 1; // this is our message
	if (xmpp_stanza_get_child_by_name_and_ns(stanza, "delay", "urn:xmpp:delay"))
		return 1; // MUC MAM history
	intext = xmpp_stanza_get_text(body);
	printf("Incoming message from %s: %s\n", xmpp_stanza_get_from(stanza), intext);
	bridge_forward(xmpp_stanza_get_from(stanza), intext, ircxmpp, IRC);
	xmpp_free(ircxmpp->ctx, intext);
	return 1;
}
int presence_handler (xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const ud) {
	struct ircxmpp * ircxmpp = (struct ircxmpp *) ud;
	if (!conn || !xmpp_stanza_get_from(stanza) || !strchr(xmpp_stanza_get_from(stanza), '/'))
		return 1;
	if (xmpp_stanza_get_type(stanza) && !strcmp("unavailable", xmpp_stanza_get_type(stanza))) {
		fprintf(stderr, "sogovornik %s je zapustil MUC\n", xmpp_stanza_get_from(stanza));
		free_bridge(find_bridge(&ircxmpp->bridges, xmpp_stanza_get_from(stanza), XMPP),
				"ircxmpp: sogovornik je zapustil XMPP MUC sobo");
	}
	if (!xmpp_stanza_get_type(stanza)) {
		fprintf(stderr, "sogovornik %s se je pridružil MUC\n", xmpp_stanza_get_from(stanza));
		bridge_forward(xmpp_stanza_get_from(stanza), NULL, ircxmpp, IRC);
	}
	return 1;
}

void conn_handler (xmpp_conn_t * const conn, const xmpp_conn_event_t status, const int error,
		xmpp_stream_error_t * const stream_error, void * const userdata) {
	struct ircxmpp * ircxmpp = (struct ircxmpp *) userdata;
	if (stream_error) /* just for -Wunused-parameter */
		fprintf(stderr, "stream_error in conn_handler, error = %d\n", error);
	if (status == XMPP_CONN_CONNECT) {
		xmpp_stanza_t * pres;
		fprintf(stderr, "DEBUG: connected.\n");
		xmpp_handler_add(conn, message_handler, NULL, "message", NULL, ircxmpp);
		xmpp_handler_add(conn, presence_handler, NULL, "presence", NULL, ircxmpp);
		/* Send initial <presence/> so that we appear online to contacts */
		// pres = xmpp_presence_new(ctx); // somehow send can only be called once?
		// xmpp_send(conn, pres);
		// xmpp_stanza_release(pres);
		pres = xmpp_presence_new(ircxmpp->ctx);	// joining a MUC
		char b[512];
		snprintf(b, 512, "%s/ircxmpp_%x", ircxmpp->muc, rand());
		xmpp_stanza_set_to(pres, b);
		xmpp_stanza_t * x = xmpp_stanza_new(ircxmpp->ctx);
		xmpp_stanza_set_name(x, "x");
		xmpp_stanza_set_ns(x, "http://jabber.org/protocol/muc");
		xmpp_stanza_add_child(pres, x);
		xmpp_stanza_release(x);
		xmpp_send(conn, pres);
		xmpp_stanza_release(pres);
	} else {
		fprintf(stderr, "DEBUG: disconnected\n");
		// xmpp_stop(ircxmpp->ctx);
	}
}
void conn_handler_bridge (xmpp_conn_t * const conn, const xmpp_conn_event_t status, const int error,
		xmpp_stream_error_t * const stream_error, void * const userdata) {
	struct bridge * bridge = (struct bridge *) userdata;
	if (stream_error) /* just for -Wunused-parameter */
		fprintf(stderr, "stream_error in conn_handler, error = %d\n", error);
	if (!conn) /* just for -Wunused-parameter */
		return;
	if (status == XMPP_CONN_CONNECT) {
		char stanzato[512];
		snprintf(stanzato, 512, "%s/%s ircxmpp_%x", bridge->ircxmpp->muc,
				bridge->identifier, rand());
		fprintf(stderr, "joining muc %s (to)\n", stanzato);
		xmpp_stanza_t * pres = xmpp_presence_new(bridge->ircxmpp->ctx);	// joining a MUC
		xmpp_stanza_set_to(pres, stanzato);
		xmpp_stanza_t * x = xmpp_stanza_new(bridge->ircxmpp->ctx);
		xmpp_stanza_set_name(x, "x");
		xmpp_stanza_set_ns(x, "http://jabber.org/protocol/muc");
		xmpp_stanza_add_child(pres, x);
		xmpp_stanza_release(x);
		xmpp_send(bridge->conn, pres);
		xmpp_stanza_release(pres);
		/* xmpp_stanza_t * pres;
		fprintf(stderr, "DEBUG: connected.\n");
		xmpp_handler_add(conn, message_handler, NULL, "message", NULL, bridge);
		xmpp_handler_add(conn, presence_handler, NULL, "presence", NULL, bridge);
		// Send initial <presence/> so that we appear online to contacts
		// pres = xmpp_presence_new(ctx); // somehow send can only be called once?
		// xmpp_send(conn, pres);
		// xmpp_stanza_release(pres);
		pres = xmpp_presence_new(bridge->ircxmpp->ctx);	// joining a MUC
		char b[512];
		bridge->identifier();
		snprintf(b, 512, "%s/ircxmpp_%x", ircxmpp->muc, rand());
		xmpp_stanza_set_to(pres, b);
		xmpp_stanza_t * x = xmpp_stanza_new(ircxmpp->ctx);
		xmpp_stanza_set_name(x, "x");
		xmpp_stanza_set_ns(x, "http://jabber.org/protocol/muc");
		xmpp_stanza_add_child(pres, x);
		xmpp_stanza_release(x);
		xmpp_send(conn, pres);
		xmpp_stanza_release(pres);
		*/
	} else {
		fprintf(stderr, "DEBUG: disconnected\n");
		// xmpp_stop(ircxmpp->ctx);
	}

}

/* IRC */
void dump_event (irc_session_t * s, const char * e, const char * o, const char ** p, unsigned c) {
	if (!s) /* just for -Wunused-parameter */
		return;
	fprintf(stderr, "Event %s, origin %s, params %d [", e, o ? o : "NULL", c);
	for (unsigned int i = 0; i < c; i++) {
		if (i)
			fprintf(stderr, "|");
		fprintf(stderr, p[i]);
	}
	fprintf(stderr, "]\n");
}
void event_connect (irc_session_t * s, const char * e, const char * o, const char ** p, unsigned c) {
	dump_event(s, e, o, p, c);
	struct bridge * bridge = (struct bridge *) irc_get_ctx(s);
	irc_cmd_join(s, bridge->ircxmpp->channel, bridge->ircxmpp->channel_password);
}
void event_connect_control (irc_session_t * s, const char * e, const char * o, const char ** p,
		unsigned c) {
	dump_event(s, e, o, p, c);
	struct ircxmpp * ircxmpp = (struct ircxmpp *) irc_get_ctx(s);
	irc_cmd_join(s, ircxmpp->channel, ircxmpp->channel_password);
}
void event_privmsg (irc_session_t * s, const char * e, const char * o, const char ** p, unsigned c) {
	dump_event(s, e, o, p, c); /* SAME FOR _control. do NOT use irc_get_ctx here!!! */
	char nickbuf[512];
	irc_target_get_nick(o, nickbuf, sizeof(nickbuf));
	irc_cmd_msg(s, nickbuf, "ircxmpp (še) ne podpira zasebnih sporočil xmpp uporabnikom. ircxmpp se razvija na http://git.sijanec.eu/sijanec/ircxmpp/.");
}
void event_partquit_control (irc_session_t * s, const char * e, const char * o, const char ** p,
		unsigned c) {
	dump_event(s, e, o, p, c);
	struct ircxmpp * ircxmpp = (struct ircxmpp *) irc_get_ctx(s);
	struct bridge ** bridge = find_bridge(&ircxmpp->bridges, o /* indeed n!u@h */, IRC);
	free_bridge(bridge, "part or quit from irc");
} /* part and quit events */
void event_channel_control (irc_session_t * s, const char * e, const char * o, const char ** p,
		unsigned c) {
	dump_event(s, e, o, p, c);		/* o je avtor, p[0] je kanal p[1] je besedilo */
	if (c != 2) /* no message text */
		return;
	struct ircxmpp * ircxmpp = (struct ircxmpp *) irc_get_ctx(s);
	bridge_forward(o /* indeed n!u@h */, p[1], ircxmpp, XMPP);
}
void event_join_control (irc_session_t * s, const char * e, const char * o, const char ** p,
		unsigned c) {
	dump_event(s, e, o, p, c);
	struct ircxmpp * ircxmpp = (struct ircxmpp *) irc_get_ctx(s);
	char buf[512];
	strncpy(buf, o, 512-1);
	buf[511] = '\0';
	if (!strlen(buf))
		strcpy(buf, "a");
	char * cp = strchr(buf, '!');
	if (cp)
		*cp = '\0';
	else
		cp = buf;
	if (ircxmpp->ircnick && !strcmp(buf, ircxmpp->ircnick)) {
		cp = strchr(++cp, '@');
		if (cp) {
			free(ircxmpp->irchost);
			ircxmpp->irchost = strdup(++cp);
			fprintf(stderr, "FOUND OUR HOSTNAME: %s\n", ircxmpp->irchost);
		}
	}
	bridge_forward(o /* indeed n!u@h */, NULL, ircxmpp, XMPP);
}
void event_nick_control (irc_session_t * s, const char * e, const char * o, const char ** p,
		unsigned c) {
	dump_event(s, e, o, p, c);	/* o je originalen nick, p[0] je nov nick */
	if (!c)
		return;
	struct ircxmpp * ircxmpp = (struct ircxmpp *) irc_get_ctx(s);
	struct bridge ** bridge = find_bridge(&ircxmpp->bridges, o /* indeed n!u@h */, IRC);
	if (!bridge || !*bridge)
		return;
	free_bridge(bridge, "nick change from irc");
	char buf[512];
	snprintf(buf, 512, "%s%s", p[0], strchr(o, '!') ? strchr(o, '!')
			: "neznan uporabnik@neznan strežnik");
	bridge_forward(buf, NULL, ircxmpp, XMPP); /* and now connect */
}
void event_topic_control (irc_session_t * s, const char * e, const char * o, const char ** p,
		unsigned c) {
	dump_event(s, e, o, p, c);	/* o je avtor, p[0] je kanal, p[1] je nova tema/zadeva */
	struct ircxmpp * ircxmpp = (struct ircxmpp *) irc_get_ctx(s);
	char buf[1024];
	snprintf(buf, 1024, "/me je nastavil IRC temo na: %s", p[1]);
	bridge_forward(o, buf, ircxmpp, XMPP);
} /* TODO */
void event_numeric (irc_session_t * s, unsigned int e, const char * o, const char ** p, unsigned c) {
	char b[512];
	struct bridge * bridge = (struct bridge *) irc_get_ctx(s);
	sprintf(b, "%d", e);
	dump_event(s, b, o, p, c);
	switch (e) {
		case ERR_NONICKNAMEGIVEN:
		case ERR_ERRONEUSNICKNAME:
		case ERR_NICKNAMEINUSE:
		case ERR_NICKCOLLISION:
			strncpy(b, bridge->identifier, sizeof(b));
			b[511] = '\0';
			jid2ircnick(b);	/* RFC 1495 says that nicks are at least 9 characters */
			if (strlen(b))
				sprintf(strlen(b) >= 7 ? b+7 : b+strlen(b), "_%x", rand());
			else
				sprintf(b, "xmpp_%x", rand());
			irc_cmd_nick(s, b);
			break;
	}
}
void event_numeric_control (irc_session_t * s, unsigned int e, const char * o, const char ** p,
		unsigned c) {
	char b[512];
	struct ircxmpp * ircxmpp = (struct ircxmpp *) irc_get_ctx(s);
	sprintf(b, "%d", e);
	dump_event(s, b, o, p, c);
	switch (e) {
		case ERR_NONICKNAMEGIVEN:
		case ERR_ERRONEUSNICKNAME:
		case ERR_NICKNAMEINUSE:
		case ERR_NICKCOLLISION:
			sprintf(b, "ircxmpp%X", rand());
			free(ircxmpp->ircnick);
			ircxmpp->ircnick = strdup(b);
			irc_cmd_nick(s, b);
			break;
	}
}
int irc_run_once (struct bridge * bridge) { /* returns nonzero if connection failed (dropped brid) */
	if (!irc_is_connected(bridge->irc)) {
		char b[512], u[512];
		strncpy(b, bridge->identifier, sizeof(b)-1);
		b[511] = '\0';
		strcpy(u, b);
		jid2ircnick(b);
		jid2ircuser(u);
		if (!strlen(b))
			sprintf(b, "xmpp_%x", rand());
		if (!strlen(u))
			strcpy(u, "ircxmpp");
		fprintf(stderr, "CONNECTING bridge %s!%s@host\n", b, u);
		if (irc_connect(bridge->irc, bridge->ircxmpp->hostname, bridge->ircxmpp->port,
					NULL, b, u, bridge->identifier)) {
			fprintf(stderr, "could not connect: %s\n",
					irc_strerror(irc_errno(bridge->irc)));
			return 1;
		}
	}
	struct timeval tv;
	fd_set in_set, out_set;
	int maxfd = 0;
	tv.tv_usec = 0; /* polling */
	tv.tv_sec = 0;
	FD_ZERO(&in_set);
	FD_ZERO(&out_set);
	irc_add_select_descriptors(bridge->irc, &in_set, &out_set, &maxfd);
	if (select(maxfd+1, &in_set, &out_set, NULL, &tv) < 0) {
		if (errno == EINTR)
			return 0;
		fprintf(stderr, "SELECT failed in bridge!\n");
		return 1;
	}
	if (irc_process_select_descriptors(bridge->irc, &in_set, &out_set)) {
		fprintf(stderr, "PROCESS_SELECT failed in bridge!\n");
		return 1;
	}
	return 0;
}
int irc_run_once_control (struct ircxmpp * ircxmpp) { /* returns nonzero if failed (dropped conne) */
	if (!irc_is_connected(ircxmpp->irc)) {
		char b[512];
		sprintf(b, "ircxmpp_%X", rand());
		fprintf(stderr, "CONNECTING control %s!ircxmpp@host\n", b);
		free(ircxmpp->ircnick);
		ircxmpp->ircnick = strdup(b);
		if (irc_connect(ircxmpp->irc, ircxmpp->hostname, ircxmpp->port,
					NULL, b, "ircxmpp", "http://git.sijanec.eu/sijanec/ircxmpp")) {
			fprintf(stderr, "could not connect: %s\n",
					irc_strerror(irc_errno(ircxmpp->irc)));
			return 1;
		}
	}
	struct timeval tv;
	fd_set in_set, out_set;
	int maxfd = 0;
	tv.tv_usec = 0; /* polling */
	tv.tv_sec = 0;
	FD_ZERO(&in_set);
	FD_ZERO(&out_set);
	irc_add_select_descriptors(ircxmpp->irc, &in_set, &out_set, &maxfd);
	if (select(maxfd+1, &in_set, &out_set, NULL, &tv) < 0) {
		if (errno == EINTR)
			return 0;
		return 1;
	}
	if (irc_process_select_descriptors(ircxmpp->irc, &in_set, &out_set))
		return 1;
	return 0;
}
void init_irc (struct bridge * bridge) {
	irc_callbacks_t callbacks;
	irc_session_t * s;
	memset(&callbacks, 0, sizeof(callbacks));
	callbacks.event_connect = event_connect;
	callbacks.event_nick = dump_event;
	callbacks.event_quit = dump_event;
	callbacks.event_join = dump_event;
	callbacks.event_part = dump_event;
	callbacks.event_mode = dump_event;
	callbacks.event_umode = dump_event;
	callbacks.event_topic = dump_event;
	callbacks.event_kick = dump_event;
	callbacks.event_channel = dump_event;
	callbacks.event_privmsg = event_privmsg;
	callbacks.event_notice = dump_event;
	callbacks.event_channel_notice = dump_event;
	callbacks.event_invite = dump_event;
	callbacks.event_ctcp_req = dump_event;
	callbacks.event_ctcp_rep = dump_event;
	callbacks.event_ctcp_action = dump_event;
	callbacks.event_unknown = dump_event;
	callbacks.event_numeric = event_numeric;
	// callbacks.event_dcc_chat_req = dump_event;
	// callbacks.event_dcc_send_req = dump_event;
	if (!(s = irc_create_session(&callbacks))) {
		fprintf(stderr, "could not create IRC session!\n");
		return;
	}
	bridge->irc = s;
	irc_set_ctx(s, bridge);
	irc_run_once(bridge);
	return;
}
void init_irc_control (struct ircxmpp * ircxmpp) {
	irc_callbacks_t callbacks;
	irc_session_t * s;
	memset(&callbacks, 0, sizeof(callbacks));
	callbacks.event_connect = event_connect_control;
	callbacks.event_nick = event_nick_control;
	callbacks.event_quit = event_partquit_control;
	callbacks.event_join = event_join_control;
	callbacks.event_part = event_partquit_control;
	callbacks.event_mode = dump_event;
	callbacks.event_umode = dump_event;
	callbacks.event_topic = event_topic_control;
	callbacks.event_kick = dump_event;
	callbacks.event_channel = event_channel_control;
	callbacks.event_privmsg = event_privmsg;
	callbacks.event_notice = dump_event;
	callbacks.event_channel_notice = dump_event;
	callbacks.event_invite = dump_event;
	callbacks.event_ctcp_req = dump_event;
	callbacks.event_ctcp_rep = dump_event;
	callbacks.event_ctcp_action = dump_event;
	callbacks.event_unknown = dump_event;
	callbacks.event_numeric = event_numeric_control;
	// callbacks.event_dcc_chat_req = dump_event;
	// callbacks.event_dcc_send_req = dump_event;
	if (!(s = irc_create_session(&callbacks))) {
		fprintf(stderr, "could not create IRC control session!\n");
		return;
	}
	ircxmpp->irc = s;
	irc_set_ctx(s, ircxmpp);
	irc_run_once_control(ircxmpp);
	return;
}
/* /IRC */ /* irc_is_connected(irc_session_t * session): 1 ali 0 */ /* TODO: if nick is used */
int shouldexit = 0;
void signalhandler (int s) {
	shouldexit += s+1; /* only for -Wunused-parameter */
}
int main (void) {
	srand(time(NULL));
	struct ircxmpp ircxmpp;
	memset(&ircxmpp, '\0', sizeof(ircxmpp));
	xmpp_log_t *log;
	if (!getenv("IX_JID") || !getenv("IX_PASS") || !getenv("IX_HOST") || !getenv("IX_PORT")
			|| !getenv("IX_CHANNEL") || !getenv("IX_MUC")) {
		fprintf(stderr, "Usage: IX_JID=jid@xmpp.server IX_PASS=pass IX_HOST=irc.server "
				"IX_PORT=6666 IX_CHANNEL=#channel IX_MUC=muc@xmpp.srv ircxmpp\n");
		return 1;
	}
	ircxmpp.jid = getenv("IX_JID");
	ircxmpp.password = getenv("IX_PASS");
	ircxmpp.hostname = getenv("IX_HOST");
	ircxmpp.port = atoi(getenv("IX_PORT"));
	ircxmpp.channel = getenv("IX_CHANNEL");
	ircxmpp.muc = getenv("IX_MUC");
	ircxmpp.channel_password = getenv("IX_CHPASS");
	xmpp_initialize();
	log = xmpp_get_default_logger(XMPP_LEVEL_DEBUG);
	init_irc_control(&ircxmpp);
c:
	ircxmpp.ctx = xmpp_ctx_new(NULL, log);
	ircxmpp.conn = xmpp_conn_new(ircxmpp.ctx);
	xmpp_conn_set_jid(ircxmpp.conn, ircxmpp.jid);
	xmpp_conn_set_pass(ircxmpp.conn, ircxmpp.password);
	xmpp_connect_client(ircxmpp.conn, NULL, 0, conn_handler, &ircxmpp);
	signal(SIGTERM, signalhandler);
	signal(SIGINT, signalhandler);
	// signal(SIGPIPE, SIG_IGN);
	while (!shouldexit) { /* TEH EVENT LOOP */
		xmpp_conn_is_disconnected(ircxmpp.conn);
		xmpp_run_once(ircxmpp.ctx, 1);
		struct bridge ** bridge = &ircxmpp.bridges;
		while (*bridge) {
			struct bridge ** next = &((*bridge)->next);
			if ((*bridge)->irc && irc_run_once(*bridge)) {
				free_bridge(bridge, "irc connection dropped");
				goto cont;
			}
			if ((*bridge)->conn)
				xmpp_run_once(xmpp_conn_get_context((*bridge)->conn), 1);
			if ((*bridge)->conn && !xmpp_conn_is_connected((*bridge)->conn)
					&& !xmpp_conn_is_connecting(((*bridge)->conn))) {
				if ((*bridge)->side == IRC) {
					fprintf(stderr, "RECONNECTING BRIDGE BECAUSE IT DIED!");
					xmpp_connect_client((*bridge)->conn, NULL, 0,
							conn_handler_bridge, *bridge);
				} else
					free_bridge(bridge, "xmpp connection dropped");
				goto cont;
			}
			while ((*bridge)->conn && xmpp_conn_is_connected((*bridge)->conn)
					&& (*bridge)->messages_length
					&& xmpp_conn_get_bound_jid((*bridge)->conn)) {
				char id[64];
				fprintf(stderr, "sending xmpp msg from %s\n", (*bridge)->identifier);
				sprintf(id, "ircxmpp-%x", rand());
				xmpp_stanza_t * stan = xmpp_message_new((*bridge)->ircxmpp->ctx,
						"groupchat", (*bridge)->ircxmpp->muc, id);
				xmpp_message_set_body(stan,
						(*bridge)->messages[(*bridge)->messages_length-1]);
				xmpp_stanza_set_from(stan, xmpp_conn_get_bound_jid((*bridge)->conn));
				xmpp_send((*bridge)->conn, stan);
				xmpp_stanza_release(stan);
				free((*bridge)->messages[--(*bridge)->messages_length]);
			}
cont:
			bridge = next;
		}
		if (!xmpp_conn_is_connected(ircxmpp.conn) && !xmpp_conn_is_connecting(ircxmpp.conn)){
			fprintf(stderr, "XMPP control DISCONNECTED! RECONNECTING!\n");
			xmpp_conn_release(ircxmpp.conn);
			xmpp_ctx_free(ircxmpp.ctx);
			goto c;
		}
		if (irc_run_once_control(&ircxmpp)) {
			fprintf(stderr, "IRC control DISCONNECTED! RECONNECTING!\n");
			irc_destroy_session(ircxmpp.irc);
			init_irc_control(&ircxmpp);
		}
		struct timespec ts = {
			.tv_sec = 0,
			.tv_nsec = getenv("IX_LOOPDELAY") ? atoi(getenv("IX_LOOPDELAY"))/1000 : 1e8
		};
		nanosleep(&ts, NULL);
	}
	fprintf(stderr, "signal received, cleaning up!\n");
	free_bridges(&ircxmpp.bridges);
	xmpp_conn_release(ircxmpp.conn);
	xmpp_ctx_free(ircxmpp.ctx);
	xmpp_shutdown();
	free(ircxmpp.ircnick);
	free(ircxmpp.irchost);
	return 0;
}