diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile | 26 | ||||
-rw-r--r-- | ircxmpp.h | 195 | ||||
-rw-r--r-- | main.c | 589 |
4 files changed, 814 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68e3894 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +valgrind-out.txt +core +ircxmpp +.gdb_history diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..13dfd0d --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +DESTDIR=/ +CC = cc +cflags = -O0 -Wall -I. -Wextra -pedantic -g $(shell pkg-config --cflags libstrophe) +SRCFILE = main.c +ldflags = $(shell pkg-config --libs libstrophe) -lircclient +BINFILE = ircxmpp +# cflags and ldflags are used so that users specifying CFLAGS and LDFLAGS do not override my flags +# += is not used, because gcc usually accepts last option, for example -O0 -O2 will use -O2 +.NOTPARALLEL: +default: + $(CC) $(cflags) $(CFLAGS) $(SRCFILE) $(ldflags) $(LDFLAGS) -o$(BINFILE) + +install: + mkdir -p $(DESTDIR)/usr/bin/ + cp $(BINFILE) $(DESTDIR)/usr/bin/ + +distclean: + rm $(BINFILE) tmp -rf + +clean: + rm $(BINFILE) tmp -rf + +valgrind: + valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=valgrind-out.txt $(COMMAND) + +.PHONY: valgrind clean distclean install default diff --git a/ircxmpp.h b/ircxmpp.h new file mode 100644 index 0000000..72557e4 --- /dev/null +++ b/ircxmpp.h @@ -0,0 +1,195 @@ +#include <libircclient.h> +enum irc_numeric { /* numerics from rfc 1459 */ + ERR_NOSUCHNICK = 401, + ERR_NOSUCHSERVER, + ERR_NOSUCHCHANNEL, + ERR_CANNOTSENDTOCHAN, + ERR_TOOMANYCHANNELS, + ERR_WASNOSUCHNICK, + ERR_TOOMANYTARGETS, + ERR_NOORIGIN = 409, + ERR_NORECIPIENT = 411, + ERR_NOTEXTTOSEND, + ERR_NOTOPLEVEL, + ERR_WILDTOPLEVEL, + ERR_UNKNOWNCOMMAND = 421, + ERR_NOMOTD, + ERR_NOADMININFO, + ERR_FILEERROR, + ERR_NONICKNAMEGIVEN = 431, + ERR_ERRONEUSNICKNAME, + ERR_NICKNAMEINUSE, + ERR_NICKCOLLISION = 436, + ERR_USERNOTINCHANNEL = 441, + ERR_NOTONCHANNEL, + ERR_USERONCHANNEL, + ERR_NOLOGIN, + ERR_SUMMONDISABLED, + ERR_USERDISABLED, + ERR_NOTREGISTERED = 451, + ERR_NEEDMOREPARAMS = 461, + ERR_ALREADYREGISTERED, + ERR_NOPERMFORHOST, + ERR_PASSWDMISMATCH, + ERR_YOUREBANNEDCREEP, + ERR_KEYSET = 467, + ERR_CHANNELISFULL = 471, + ERR_UNKNOWNMODE, + ERR_INVITEONLYCHAN, + ERR_BANNEDFROMCHAN, + ERR_BADCHANNELKEY, + ERR_NOPRIVILEGES = 481, + ERR_CHANOPRIVSNEEDED, + ERR_CANTKILLSERVER, + ERR_NOOPERHOST = 491, + ERR_UMODEUNKNOWNFLAG = 501, + ERR_USERSDONTMATCH, + RPL_NONE = 300, + RPL_AWAY, + RPL_USERHOST, + RPL_ISON, + RPL_UNAWAY, + RPL_NOWAWAY, + RPL_WHOISUSER = 311, + RPL_WHOISSERVER, + RPL_WHOISOPERATOR, + RPL_WHOISIDLE = 317, + RPL_ENDOFWHOIS, + RPL_WHOISCHANNELS, + RPL_WHOWASUSER = 314, + RPL_ENDOFWHOWAS = 369, + RPL_LISTSTART = 321, + RPL_LIST, + RPL_LISTEND, + RPL_CHANNELMODEIS, + RPL_NOTOPIC = 331, + RPL_TOPIC, + RPL_INVITING = 341, + RPL_SUMMONING = 342, + RPL_VERSION = 351, + RPL_WHOREPLY = 352, + RPL_ENDOFWHO = 315, + RPL_NAMREPLY = 353, + RPL_ENDOFNAMES = 366, + RPL_LINKS = 364, + RPL_ENDOFLINKS, + RPL_BANLIST = 367, + RPL_ENDOFBANLIST, + RPL_INFO = 371, + RPL_ENDOFINFO = 374, + RPL_MOTDSTART, + RPL_MOTD = 372, + RPL_ENDOFMOTD = 376, + RPL_YOUREOPER = 381, + RPL_REHASHING = 382, + RPL_TIME = 391, + RPL_USERSSTART, + RPL_USERS, + RPL_ENDOFUSERS, + RPL_NOUSERS, + RPL_TRACELINK = 200, + RPL_TRACECONNECTING, + RPL_TRACEHANDSHAKE, + RPL_TRACEUNKNOWN, + RPL_TRACEOPERATOR, + RPL_TRACEUSER, + RPL_TRACESERVER, + RPL_TRACENEWTYPE = 208, + RPL_TRACELOG = 261, + RPL_STATSLINKINFO = 211, + RPL_STATSCOMMANDS, + RPL_STATSCLINE, + RPL_STATSNLINE, + RPL_STATSILINE, + RPL_STATSKLINE, + RPL_STATSYLINE = 218, + RPL_ENDOFSTATS, + RPL_STATSLLINE = 241, + RPL_STATSUPTIME, + RPL_STATSOLINE, + RPL_STATSHLINE, + RPL_UMODEIS = 221, + RPL_LUSERCLIENT = 251, + RPL_LUSEROP, + RPL_LUSERUNKNOWN, + RPL_LUSERCHANNELS, + RPL_LUSERME, + RPL_ADMINME, + RPL_ADMINLOC1, + RPL_ADMINLOC2, + RPL_ADMINEMAIL, + RPL_TRACECLASS = 209, + RPL_SERVICEINFO = 231, + RPL_SERVICE = 233, + RPL_SERVLISTEND = 235, + RPL_WHOISCHANOP = 316, + RPL_CLOSING = 362, + RPL_INFOSTART = 373, + ERR_YOUWILLBEBANNED = 466, + ERR_NOSERVICEHOST = 492, + RPL_STATSQLINE = 217, + RPL_ENDOFSERVICES = 232, + RPL_SERVLIST = 234, + RPL_KILLDONE = 361, + RPL_CLOSEEND = 363, + RPL_MYPORTIS = 384, + ERR_BADCHANMASK = 476 +}; +enum side { + IRC, + XMPP +}; +struct bridge { // xmpp connection is the same for every user, only IRC connection differs + struct ircxmpp * ircxmpp; + struct bridge * next; + struct bridge * prev; + char * identifier; /* jid if side is XMPP or nickname if side is IRC */ + irc_session_t * irc; + xmpp_conn_t * conn; /* isti xmpp_ctx_t ima lahko več connov */ + enum side side; /* which side is the real user */ + size_t messages_length; + char ** messages; +}; +struct ircxmpp { + struct bridge * bridges; + irc_session_t * irc; + char * ircnick; + char * irchost; + xmpp_ctx_t * ctx; + xmpp_conn_t * conn; + char * jid; + char * password; + char * hostname; + int port; + char * channel; + char * muc; +}; +void free_bridge (struct bridge **, const char *); +void free_bridges (struct bridge **); +struct bridge ** find_bridge (struct bridge **, const char *, enum side); +void jid2ircnick (char *); +void jid2ircuser (char *); +void bridge_forward (const char *, const char *, struct ircxmpp *, enum side); +int message_handler (xmpp_conn_t * const, xmpp_stanza_t * const, void * const); +int presence_handler (xmpp_conn_t * const, xmpp_stanza_t * const, void * const); +void conn_handler (xmpp_conn_t * const, const xmpp_conn_event_t, const int, + xmpp_stream_error_t * const, void * const); +void conn_handler_bridge (xmpp_conn_t * const, const xmpp_conn_event_t, const int, + xmpp_stream_error_t * const, void * const); +// IRC +void dump_event (irc_session_t *, const char *, const char *, const char **, unsigned); +void event_connect (irc_session_t *, const char *, const char *, const char **, unsigned); +void event_connect_control (irc_session_t *, const char *, const char *, const char **, unsigned); +void event_privmsg (irc_session_t *, const char *, const char *, const char **, unsigned); +void event_partquit_control (irc_session_t *, const char *, const char *, const char **, unsigned); +void event_join_control (irc_session_t *, const char *, const char *, const char **, unsigned); +void event_channel_control (irc_session_t *, const char *, const char *, const char **, unsigned); +void event_nick_control(irc_session_t *, const char *, const char *, const char **, unsigned); +void event_topic_control (irc_session_t *, const char *, const char *, const char **, unsigned); +void event_numeric (irc_session_t *, unsigned int, const char *, const char **, unsigned); +void event_numeric_control (irc_session_t *, unsigned int, const char *, const char **, unsigned); +int irc_run_once (struct bridge *); +void init_irc (struct bridge *); +void init_irc_control (struct ircxmpp *); +// /IRC +int main (int argc, char ** argv); @@ -0,0 +1,589 @@ +#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, NULL /* password */); /* da smo gotovo joinani */ + 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, NULL /* 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, NULL); +} +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 */ + 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"); + bridge_forward(o, 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 */ +} /* 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 (int argc, char ** argv) { + srand(time(NULL)); + struct ircxmpp ircxmpp; + memset(&ircxmpp, '\0', sizeof(ircxmpp)); + xmpp_log_t *log; + if (argc != 1+6) { + fprintf(stderr, "Usage: ircxmpp <jid> <pass> <hostname> <port> <channel> <muc>\n"); + return 1; + } + ircxmpp.jid = argv[1]; + ircxmpp.password = argv[2]; + ircxmpp.hostname = argv[3]; + ircxmpp.port = atoi(argv[4]); + ircxmpp.channel = argv[5]; + ircxmpp.muc = argv[6]; + 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 = 1e8 /* 0.1s per event loop */ + }; + 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; +} |