summaryrefslogblamecommitdiffstats
path: root/ircxmpp.c
blob: d435797a9056641a766f2bc17da07eeddace3e43 (plain) (tree)
1
2
3
4
5
6
7
8
9
                   


                   




                       

                   
                    

                                                               

                                                                                                

                             
                                                                                                  




                                                                          







                                                              
              



                                      
         
 



























                                                                                                     
         







                                                                                      

 















                                                                                               
                            
                      

 
                                                                                              











                                                                                     
                                      












                                                                                     
                                                                                                    
                                                                       
                                                                                       
                       
                                                                                                   
                                               
                      
                                                          
                                               
                                          
                                  
                                                                   
                             






                                                                                                
         
                       
                                                                                       
                                     









                                                                                               
                                     
                       

                                                                                                 
                                                                                
                 
                                                 

                                                                          
                                                         

                             
                  









                                                                                       



                                                                                                     

                 
                                                                                                    
                                                         
                                                                                              

                                                                                                   
                                                                                             



                                                                                               
                                                                                                     




                                                                                 
                                                                                   


                                                                                 
                                                               

                                          
                                                                         
















                                                                                          

                                                                              
 



                                                                                        
                                                            



                                                                                 
                                                                                           








                                                                                                

                                                                                      



         



















                                                                                                 

                                                                                                 






                                                                  
                       


                                                                              

                                              

                                               
         

                                                                                     
 

                                                                                                 
                                                                  
                                  
                                                                                     
 

                                                                                                 
                                                                     
                                          
                                                                     
 

                                                                                                 







                                                                                                                                                               



                                                                                                                                                               

                                                                                                 
                                          
                                                                     
                                                                                     
                            

                                                                                                 
                                                                                                        




                                                                     

                                                                                                 
                                          















                                                                     
                                                                                                



                                                                  

                                                                                                 
                                                                                            

                       
                                                                     
                                                                                    



                                                                        
 

                                                                                                 
                                                                                                         



                                                                     
            

                                                                                                 



















                                                                                                

                                                                                                 


                                                                     
                                          











                                                        
                                                                                             










                                                            
                                                                                         

                                                                                              
                                                                                           














                                                                              
                                                                               


                                                                             
                                                                                       



                         


                                                                                            


                                                 
                                                                                    


                                                                               
                                                                                                     
                                                                                    
                                                                               
                                 
                 
                         











                                                                            
                         

                                                                            
                         

                 
                                               
























                                                     
                                                                                            






                               
                                                         







                                                        

                                                   
                                                    
                                                  
                                                        







                                                            



                                                        
                                                                                     






                                      


















                                                                                            
                                                                   



















                                                                                                 

                                      












                                                                                     



























                                                                                             





























                                                                                                    


                                                                                      
                                                                                        



                                                         
                                                                         







                                                                                               
                                                                                                  



                                                          
                                                



















                                                                                  
                   

                                                    
 


                                         
                 
                          






































                                                                                                     
         



                                                  

                                                           

                                      
                                                                                                   



                                                           


                                                   

                 
      
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <sys/select.h>
#include <errno.h>
#include <signal.h>
#include <stdarg.h>
#include <search.h>
#include "ircxmpp.h"
#ifdef IX_LIB
#define BUG(...) bug(__FILE__, __func__, __LINE__, __VA_ARGS__)
__attribute__((noreturn)) __attribute__((unused))
static void bug (const char * file, const char * function, int line, const char * format, ...) {
	va_list ap;
	va_start(ap, format);
	fprintf(stderr, "BUG in ircxmpp %s()@%s:%d REPORT TO anton@šijanec.eu and attach core.\n"
			"additional description: ", file, function, line);
	vfprintf(stderr, format, ap);
	abort();
	va_end(ap); /* this is never called */
}
static int bridge_compare (const void * ap, const void * bp) {
	const struct bridge * a = (const struct bridge *) ap;
	const struct bridge * b = (const struct bridge *) bp;
	int i = strcmp(a->identifier, b->identifier);
	if (i)
		return i;
	if (a->side == b->side)
		return 0;
	else {
		if (a->side > b->side)
			return -1;
		else
			return 1;
	}
}
#define LOG(ircxmpp, level, ...) logwrite(ircxmpp, level, __FILE__, __LINE__, __func__, __VA_ARGS__);
static void logwrite (struct ircxmpp * ircxmpp, enum ircxmpp_loglevel level, const char * file,
		int line, const char * function, const char * format, ...) {
	va_list ap_len, ap_print;
	va_start(ap_len, format);
	va_copy(ap_print, ap_len);
#define LOCATION_FORMAT "%s()@%s:%d "
#define LOCATION_ARGS function, file, line
	int location_len = snprintf(NULL, 0, LOCATION_FORMAT, LOCATION_ARGS);
	int len = vsnprintf(NULL, 0, format, ap_len) + 1 + location_len;
	if (len > 65535)
		return;
	char buf[len];
	char * c = buf;
	c += sprintf(c, LOCATION_FORMAT, LOCATION_ARGS);
	vsprintf(c, format, ap_print);
	ircxmpp->log_handler(ircxmpp->log_userdata, level, "ircxmpp", buf);
	va_end(ap_len);
	va_end(ap_print);
}
static void free_bridge (struct bridge * bridge, const char * razlog) {
	if (!bridge)
		return;
	LOG(bridge->ircxmpp, IRCXMPP_DEBUG, "freeing bridge with reason: %s", 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?
	tdelete(bridge, bridge->ircxmpp->bridges, bridge_compare);
	free(bridge->identifier);
	for (size_t i = 0; i < bridge->messages_length; i++)
		free(bridge->messages[i]);
	free(bridge->messages);
	free(bridge);
}

static char * free_reason = "global readdson not set since startup. this is a bug. report it.";
static void free_bridge_global_reason (void * bridge) {
	free_bridge((struct bridge *) bridge, free_reason);
}
static void free_bridges (void ** bridges) {
	free_reason = "vsi mostovi se podirajo, ker se ircxmpp izklaplja";
	tdestroy(*bridges, free_bridge_global_reason);
}

static struct bridge * find_bridge (void ** bridges, const char * id, enum side side) {
	struct bridge bridge = {
		.side = side,
		.identifier = (char *) id
	};
	struct bridge ** found = tfind(&bridge, bridges, bridge_compare);
	if (!found)
		return NULL;
	return *found;
}

static void jid2ircnick (char * jid) { /* edits a jid into an irc 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 = '_';
}
static 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';
}
static void bridge_forward (const char * f, const char * m, struct ircxmpp * ircxmpp, enum side s) {
	struct bridge * bridge = find_bridge(&ircxmpp->bridges, f, !s);
	if (strstr(f, "ircxmpp_") || (ircxmpp->irchost && strstr(f, ircxmpp->irchost)))
		return;
	LOG(ircxmpp, IRCXMPP_DEBUG, "sending text from %s to %s: %s", f, s == IRC ? "IRC" : "XMPP",
			m ? m : "[join only]");
	if (!bridge) {
		bridge = calloc(1, sizeof(struct bridge));
		bridge->identifier = strdup(f);
		bridge->ircxmpp = ircxmpp;
		bridge->side = !s;
		tsearch(bridge, &ircxmpp->bridges, bridge_compare);
		if (s == 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);
		}
	}
	if (s == IRC) {
		irc_cmd_join(bridge->irc, ircxmpp->channel, ircxmpp->channel_password);
		irc_run_once(bridge);
		if (m) {
			char b[1024];
			strncpy(b, m, 1023);
			b[1023] = '\0';
			char * c = b;
			while (*++c != '\0')
				if (!(isprint(*c) || * (unsigned char *) c > 0x7F /* UTF-8 */))
					*c = ' '; /* to prevent newline message smuggling */
			irc_cmd_msg(bridge->irc, ircxmpp->channel, m);
		}
		irc_run_once(bridge);
	} else if (m) {
			bridge->messages = realloc(bridge->messages,
					sizeof(*bridge->messages) * (bridge->messages_length+1));
			bridge->messages[bridge->messages_length++] = strdup(m);
		}
} /* m can be NULL, in that case we only join. */
static int message_handler (xmpp_conn_t * const c __attribute__((unused)),
		xmpp_stanza_t * const stanza, void * const ud) {
	struct ircxmpp * ircxmpp = (struct ircxmpp *) ud;
	xmpp_stanza_t * body;
	const char * type;
	char * tx;
	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
	tx = xmpp_stanza_get_text(body);
	LOG(ircxmpp, IRCXMPP_INFO, "Incoming message from %s: %s", xmpp_stanza_get_from(stanza), tx);
	bridge_forward(xmpp_stanza_get_from(stanza), tx, ircxmpp, IRC);
	xmpp_free(ircxmpp->ctx, tx);
	return 1;
}
static int presence_handler (xmpp_conn_t * const c, xmpp_stanza_t * const stanza, void * const ud) {
	struct ircxmpp * ircxmpp = (struct ircxmpp *) ud;
	if (!c || !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))) {
		LOG(ircxmpp, IRCXMPP_INFO, "user %s left MUC", 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)) {
		LOG(ircxmpp, IRCXMPP_INFO, "user %s connected to MUC", xmpp_stanza_get_from(stanza));
		bridge_forward(xmpp_stanza_get_from(stanza), NULL, ircxmpp, IRC);
	}
	return 1;
}

static void conn_handler (xmpp_conn_t * const conn, const xmpp_conn_event_t status,
		const int error __attribute__((unused)),
		xmpp_stream_error_t * const stream_error __attribute__((unused)),
		void * const userdata) {
	struct ircxmpp * ircxmpp = (struct ircxmpp *) userdata;
	if (status == XMPP_CONN_CONNECT) {
		xmpp_stanza_t * pres;
		LOG(ircxmpp, IRCXMPP_INFO, "control connected to XMPP.");
		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
		LOG(ircxmpp, IRCXMPP_WARN, "control disconnected from XMPP.");
}
static void conn_handler_bridge (xmpp_conn_t * const conn __attribute__((unused)),
		const xmpp_conn_event_t status, const int error __attribute__((unused)),
		xmpp_stream_error_t * const stream_error __attribute__((unused)),
		void * const userdata) {
	struct bridge * bridge = (struct bridge *) userdata;
	if (status == XMPP_CONN_CONNECT) {
		char stanzato[512];
		snprintf(stanzato, 512, "%s/%s ircxmpp_%x", bridge->ircxmpp->muc,
				bridge->identifier, rand());
		LOG(bridge->ircxmpp, IRCXMPP_INFO, "bridge joining muc %s (to)", 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);
	} else
		LOG(bridge->ircxmpp, IRCXMPP_WARN, "control disconnected from XMPP.");

}

/* IRC */
static void dump_event_control (
		irc_session_t * s, const char * e, const char * o, const char ** p, unsigned c) {
	struct ircxmpp * ircxmpp = (struct ircxmpp *) irc_get_ctx(s);
	int requiredbuf = 256;
	for (unsigned int i = 0; i < c; i++) {
		requiredbuf += strlen(p[c])+256;
	}
	if (requiredbuf > 65535)
		return;
	char buf[requiredbuf];
	char * cp = buf;
	cp += sprintf(cp, "%s, origin %s, params %d [", e, o ? o : "NULL", c);
	for (unsigned int i = 0; i < c; i++) {
		if (i)
			cp += sprintf(cp, "|");
		cp += sprintf(cp, "%s", p[i]);
	}
	cp += sprintf(cp, "]");
	ircxmpp->log_handler(ircxmpp->log_userdata, IRCXMPP_DEBUG, "irc_event_control", buf);
}
static void dump_event (
		irc_session_t * s, const char * e, const char * o, const char ** p, unsigned c) {
	struct bridge * bridge = (struct bridge *) irc_get_ctx(s);
	struct ircxmpp * ircxmpp = bridge->ircxmpp;
	int requiredbuf = 256;
	for (unsigned int i = 0; i < c; i++) {
		requiredbuf += strlen(p[c])+256;
	}
	if (requiredbuf > 65535)
		return;
	char buf[requiredbuf];
	char * cp = buf;
	cp += sprintf(cp, "%s, origin %s, params %d [", e, o ? o : "NULL", c);
	for (unsigned int i = 0; i < c; i++) {
		if (i)
			cp += sprintf(cp, "|");
		cp += sprintf(cp, "%s", p[i]);
	}
	cp += sprintf(cp, "]");
	ircxmpp->log_handler(ircxmpp->log_userdata, IRCXMPP_DEBUG, "irc_event", buf);
}
static void event_connect (
		irc_session_t * s, const char * e, const char * o, const char ** p, unsigned c) {
	struct bridge * bridge = (struct bridge *) irc_get_ctx(s);
	dump_event(s, e, o, p, c);
	irc_cmd_join(s, bridge->ircxmpp->channel, bridge->ircxmpp->channel_password);
}
static void event_connect_control (
		irc_session_t * s, const char * e, const char * o, const char ** p, unsigned c) {
	struct ircxmpp * ircxmpp = (struct ircxmpp *) irc_get_ctx(s);
	dump_event_control(s, e, o, p, c);
	irc_cmd_join(s, ircxmpp->channel, ircxmpp->channel_password);
}
static 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);
	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/.");
}
static void event_privmsg_control (
		irc_session_t * s, const char * e, const char * o, const char ** p, unsigned c) {
	dump_event_control(s, e, o, p, c);
	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/.");
}
static void event_partquit_control (
		irc_session_t * s, const char * e, const char * o, const char ** p, unsigned c) {
	dump_event_control(s, e, o, p, c);
	struct ircxmpp * ircxmpp = (struct ircxmpp *) irc_get_ctx(s);
	free_bridge(find_bridge(&ircxmpp->bridges, o, IRC), "part or quit from irc");
} /* part and quit events */
static void event_channel_control (
		irc_session_t * s, const char * e, const char * o, const char ** p, unsigned c) {
	dump_event_control(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);
}
static void event_join_control (
		irc_session_t * s, const char * e, const char * o, const char ** p, unsigned c) {
	dump_event_control(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);
			LOG(ircxmpp, IRCXMPP_DEBUG, "FOUND OUR HOSTNAME: %s", ircxmpp->irchost);
		}
	}
	bridge_forward(o /* indeed n!u@h */, NULL, ircxmpp, XMPP);
}
static void event_nick_control (
		irc_session_t * s, const char * e, const char * o, const char ** p, unsigned c) {
	dump_event_control(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);
	free_bridge(find_bridge(&ircxmpp->bridges, o, IRC), "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 */
}
static void event_topic_control (
		irc_session_t * s, const char * e, const char * o, const char ** p, unsigned c) {
	dump_event_control(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 */
static 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;
	}
}
static 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_control(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;
	}
}
static int irc_run_once (struct bridge * bridge) { /* returns nonzero if connection failed */
	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");
		LOG(bridge->ircxmpp, IRCXMPP_INFO, "CONNECTING bridge %s!%s@host", b, u);
		if (irc_connect(bridge->irc, bridge->ircxmpp->hostname, bridge->ircxmpp->port,
					NULL, b, u, bridge->identifier)) {
			LOG(bridge->ircxmpp, IRCXMPP_ERROR, "bridge could not connect: %s",
					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;
		LOG(bridge->ircxmpp, IRCXMPP_WARN, "SELECT failed in bridge!");
		return 1;
	}
	if (irc_process_select_descriptors(bridge->irc, &in_set, &out_set)) {
		LOG(bridge->ircxmpp, IRCXMPP_WARN, "PROCESS_SELECT failed in bridge!");
		return 1;
	}
	return 0;
}
static int irc_run_once_control (struct ircxmpp * ircxmpp) { /* returns nonzero if failed */
	if (!ircxmpp->irc)
		return 1;
	if (!irc_is_connected(ircxmpp->irc)) {
		char b[512];
		sprintf(b, "ircxmpp_%X", rand());
		LOG(ircxmpp, IRCXMPP_INFO, "CONNECTING control %s!ircxmpp@host", 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")) {
			LOG(ircxmpp, IRCXMPP_ERROR, "control could not connect: %s",
					irc_strerror(irc_errno(ircxmpp->irc)));
			return 2;
		}
		return 0;
	}
	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 3;
	}
	if (irc_process_select_descriptors(ircxmpp->irc, &in_set, &out_set))
		return 4;
	return 0;
}
static 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))) {
		LOG(bridge->ircxmpp, IRCXMPP_ERROR, "could not create IRC bridge session!");
		return;
	}
	bridge->irc = s;
	irc_set_ctx(s, bridge);
	irc_run_once(bridge);
	return;
}
static 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_control;
	callbacks.event_umode = dump_event_control;
	callbacks.event_topic = event_topic_control;
	callbacks.event_kick = dump_event_control;
	callbacks.event_channel = event_channel_control;
	callbacks.event_privmsg = event_privmsg_control;
	callbacks.event_notice = dump_event_control;
	callbacks.event_channel_notice = dump_event_control;
	callbacks.event_invite = dump_event_control;
	callbacks.event_ctcp_req = dump_event_control;
	callbacks.event_ctcp_rep = dump_event_control;
	callbacks.event_ctcp_action = dump_event_control;
	callbacks.event_unknown = dump_event_control;
	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))) {
		LOG(ircxmpp, IRCXMPP_ERROR, "could not create IRC control session!");
		return;
	}
	ircxmpp->irc = s;
	irc_set_ctx(s, ircxmpp);
	irc_run_once_control(ircxmpp);
	return;
}
static void ircxmpp_default_logger (void * const u __attribute__((unused)),
		const enum ircxmpp_loglevel l, const char * const a, const char * const m) {
	char * t = "unspec";
	switch (l) {
		case IRCXMPP_DEBUG:
			t = "DEBUG";
			break;
		case IRCXMPP_INFO:
			t = "INFO";
			break;
		case IRCXMPP_WARN:
			t = "WARN";
			break;
		case IRCXMPP_ERROR:
			t = "ERROR";
			break;
	}
	fprintf(stderr, "[ircxmpp %s]	%s: %s\n", t, a, m);
}
/* /IRC */ /* irc_is_connected(irc_session_t * session): 1 ali 0 */
static void send_xmpp_logs_to_me (void * const u, const xmpp_log_level_t l, const char * const a,
		const char * const m) {
	enum ircxmpp_loglevel loglevel = IRCXMPP_ERROR;
	switch (l) {
		case XMPP_LEVEL_DEBUG:
			loglevel = IRCXMPP_DEBUG;
			break;
		case XMPP_LEVEL_INFO:
			loglevel = IRCXMPP_INFO;
			break;
		case XMPP_LEVEL_WARN:
			loglevel = IRCXMPP_WARN;
			break;
		case XMPP_LEVEL_ERROR:
			loglevel = IRCXMPP_ERROR;
			break;
	}
	struct ircxmpp * ircxmpp = (struct ircxmpp *) u;
	ircxmpp->log_handler(ircxmpp->log_userdata, loglevel, a, m);
}
struct ircxmpp * ircxmpp_init (void) {
	xmpp_initialize();
	struct ircxmpp * ircxmpp = calloc(1, sizeof(struct ircxmpp));
	ircxmpp->log_handler = ircxmpp_default_logger;
	ircxmpp->xmpp_logger.handler = send_xmpp_logs_to_me;
	ircxmpp->xmpp_logger.userdata = ircxmpp;
	return ircxmpp;
}
void ircxmpp_set_log_handler (struct ircxmpp * ircxmpp, ircxmpp_logger log_handler) {
	if (!log_handler)
		ircxmpp->log_handler = ircxmpp_default_logger;
	ircxmpp->log_handler = log_handler;
}
void ircxmpp_set_log_userdata (struct ircxmpp * ircxmpp, void * log_userdata) {
	ircxmpp->log_userdata = log_userdata;
}
void ircxmpp_set_jid (struct ircxmpp * ircxmpp, const char * jid) {
	free(ircxmpp->jid);
	ircxmpp->jid = strdup(jid);
}
void ircxmpp_set_password (struct ircxmpp * ircxmpp, const char * password) {
	free(ircxmpp->password);
	ircxmpp->password = strdup(password);
}
void ircxmpp_set_hostname (struct ircxmpp * ircxmpp, const char * hostname) {
	free(ircxmpp->hostname);
	ircxmpp->hostname = strdup(hostname);
}
void ircxmpp_set_port (struct ircxmpp * ircxmpp, unsigned short int port) {
	ircxmpp->port = port;
}
void ircxmpp_set_channel (struct ircxmpp * ircxmpp, const char * channel) {
	free(ircxmpp->channel);
	ircxmpp->channel = strdup(channel);
}
void ircxmpp_set_muc (struct ircxmpp * ircxmpp, const char * muc) {
	free(ircxmpp->muc);
	ircxmpp->muc = strdup(muc);
}
void ircxmpp_set_channel_password (struct ircxmpp * ircxmpp, const char * channel_password) {
	free(ircxmpp->channel_password);
	ircxmpp->channel_password = strdup(channel_password);
}
static void obdelaj_bridge (const void * nodep, VISIT which __attribute__((unused)),
		int depth __attribute__((unused))) {
	struct bridge * bridge = *(struct bridge **) nodep;
	if (bridge->irc && irc_run_once(bridge)) {
		free_bridge(bridge, "irc connection dropped");
		return;
	}
	if (bridge->conn && !xmpp_conn_is_connected(bridge->conn)
			&& !xmpp_conn_is_connecting((bridge->conn))) {
		if (bridge->side == IRC && bridge->messages_length) {
			LOG(bridge->ircxmpp, IRCXMPP_WARN, "RECONNECTING dead BRIDGE with msgs!");
			xmpp_connect_client(bridge->conn, NULL, 0, conn_handler_bridge, bridge);
		} else
			free_bridge(bridge, "xmpp connection dropped");
		return;
	}
	while (bridge->conn && xmpp_conn_is_connected(bridge->conn) && bridge->messages_length
			&& xmpp_conn_get_bound_jid(bridge->conn)) {
		char id[64];
		LOG(bridge->ircxmpp, IRCXMPP_DEBUG, "sending xmpp msg from %s", 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]);
	}
}
void ircxmpp_run_once (struct ircxmpp * ircxmpp) {
	if (!ircxmpp->ctx || !ircxmpp->conn || (!xmpp_conn_is_connected(ircxmpp->conn)
				&& !xmpp_conn_is_connecting(ircxmpp->conn))) {
		LOG(ircxmpp, IRCXMPP_WARN, "XMPP control is DISCONNECTED! CONNECTING!");
		if (ircxmpp->conn)
			xmpp_conn_release(ircxmpp->conn);
		if (ircxmpp->ctx)
			xmpp_ctx_free(ircxmpp->ctx);
		ircxmpp->ctx = xmpp_ctx_new(NULL, &ircxmpp->xmpp_logger);
		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);
	}
	xmpp_run_once(ircxmpp->ctx, 1); /* no need to run for bridges, as they share the ctx */
	int ret = 0;
	if ((ret = irc_run_once_control(ircxmpp))) {
		LOG(ircxmpp, IRCXMPP_WARN, "IRC control DISCONNECTED, code %d. CONNECTING!", ret);
		if (ircxmpp->irc)
			irc_destroy_session(ircxmpp->irc);
		init_irc_control(ircxmpp);
	}
	twalk(ircxmpp->bridges, obdelaj_bridge);
}
void ircxmpp_free (struct ircxmpp * ircxmpp) {
	free_bridges(&ircxmpp->bridges);
	xmpp_conn_release(ircxmpp->conn);
	xmpp_ctx_free(ircxmpp->ctx);
	xmpp_shutdown();
	irc_cmd_quit(ircxmpp->irc, "vsesplošni izklop programa");
	irc_run_once_control(ircxmpp); // verjetno je to potrebno, da pošlje quit
	irc_destroy_session(ircxmpp->irc);
	free(ircxmpp->ircnick);
	free(ircxmpp->irchost);
	free(ircxmpp->jid);
	free(ircxmpp->password);
	free(ircxmpp->hostname);
	free(ircxmpp->channel);
	free(ircxmpp->muc);
	free(ircxmpp->channel_password);
	free(ircxmpp);
}
#else
int shouldexit = 0;
void signalhandler (int s __attribute__((unused))) {
	shouldexit++;
}
const char * str2str (const char * str) {
	return str;
}
int main (void) {
	srand(time(NULL));
#define USAGE	"	*** ircxmpp is developed at http://git.sijanec.eu/sijanec/ircxmpp ***\n" \
		"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" \
		"multiple links can be specified by appending a consecutive number, starting with " \
		"2, to every environment variable. first link is IX_*, second is IX_*2, and so on.\n"
	size_t handles_length = 0;
	ircxmpp ** handles = NULL;
	while (1) {		// note that if input config is invalid we leak memory before exiting
		char b[64];	// i don't free any allocated shit and just return, probably it's ok
		if (handles_length++)
			sprintf(b, "IX_JID%d", handles_length);
		else
			strcpy(b, "IX_JID");
		if (!getenv(b)) {
			if (handles_length == 1) {
				fprintf(stderr, USAGE "FAIL: at least one link is required!\n");
				return 1;
			}
			handles_length--;
			break;
		}
		handles = realloc(handles, sizeof(ircxmpp *)*handles_length);
		handles[handles_length-1] = ircxmpp_init();
#define PREPARE_HANDLE(value, config, function, required) \
	if (handles_length == 1) \
		sprintf(b, "IX_" config); \
	else \
		sprintf(b, "IX_" config "%d", handles_length); \
	if (getenv(b)) \
		ircxmpp_set_##value(handles[handles_length-1], function(getenv(b))); \
	else if (required) \
		fprintf(stderr, USAGE "FAIL: environment variable %s was expected to be set.\n", b)
		PREPARE_HANDLE(jid, "JID", str2str, 1);
		PREPARE_HANDLE(password, "PASS", str2str, 1);
		PREPARE_HANDLE(hostname, "HOST", str2str, 1);
		PREPARE_HANDLE(port, "PORT", atoi, 1);
		PREPARE_HANDLE(channel, "CHANNEL", str2str, 1);
		PREPARE_HANDLE(muc, "MUC", str2str, 1);
		PREPARE_HANDLE(channel_password, "CHPASS", str2str, 0);
	}
	signal(SIGTERM, signalhandler);
	signal(SIGINT, signalhandler);
	// signal(SIGPIPE, SIG_IGN);
	while (!shouldexit) { /* TEH EVENT LOOP */
		for (size_t i = 0; i < handles_length; i++)
			ircxmpp_run_once(handles[i]);
		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");
	for (size_t i = 0; i < handles_length; i++)
		ircxmpp_free(handles[i]);
	free(handles);
	return 0;
}
#endif