From 743086cf4e69c77e139c23defaf63bfa403c1678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Luka=20=C5=A0ijanec?= Date: Sat, 30 Apr 2022 02:10:29 +0200 Subject: not working. just a checkpoint before I rewrite to binary tree --- .gitignore | 3 + CHANGELOG | 18 ++ Makefile | 30 +-- README | 41 +++- debian/changelog | 12 +- ircxmpp.c | 692 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ ircxmpp.conf | 10 + ircxmpp.h | 74 +++--- main.c | 605 ------------------------------------------------ 9 files changed, 823 insertions(+), 662 deletions(-) create mode 100644 CHANGELOG mode change 100644 => 120000 debian/changelog create mode 100644 ircxmpp.c delete mode 100644 main.c diff --git a/.gitignore b/.gitignore index 68e3894..5bd2f72 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ valgrind-out.txt core ircxmpp .gdb_history +*.so +*.o +*.out diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..bf42b0b --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,18 @@ +ircxmpp (0.0.3-1) stable; urgency=low + + * Fixed newline message smuggling from XMPP to IRC. + * Added support for multiple links. + + -- Anton Luka Šijanec Fri, 29 Apr 2022 17:00:00 +0200 + +ircxmpp (0.0.2-1) stable; urgency=low + + * Some fixes. + + -- Anton Luka Šijanec Wed, 27 Apr 2022 15:45:00 +0200 + +ircxmpp (0.0.1-1) stable; urgency=low + + * Initial release. + + -- Anton Luka Šijanec Wed, 27 Apr 2022 14:00:00 +0200 diff --git a/Makefile b/Makefile index 19eccb9..5510ba7 100644 --- a/Makefile +++ b/Makefile @@ -1,29 +1,31 @@ DESTDIR=/ CC = cc -cflags = -O0 -Wall -I. -Wformat-security -Wextra -pedantic -g $(shell pkg-config --cflags libstrophe) -SRCFILE = main.c -CFGFILE = ircxmpp.conf -CFGDEST = ircxmpp +cflags = -O0 -Wall -I. -fpic -Wformat-security -Wextra -pedantic -g3 $(shell pkg-config --cflags libstrophe) ldflags = $(shell pkg-config --libs libstrophe) -lircclient -BINFILE = ircxmpp +PROJ = 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) - + $(CC) -c -DIX_LIB $(cflags) $(CFLAGS) $(PROJ).c + $(CC) -shared -olib$(PROJ).so $(PROJ).o + $(CC) -L. $(cflags) $(CFLAGS) $(PROJ).c -l$(PROJ) $(ldflags) $(LDFLAGS) -o$(PROJ) install: - mkdir -p $(DESTDIR)/usr/bin $(DESTDIR)/etc - cp $(BINFILE) $(DESTDIR)/usr/bin/ - cp $(CFGFILE) $(DESTDIR)/etc/$(CFGDEST) + mkdir -p $(DESTDIR)/usr/bin $(DESTDIR)/etc $(DESTDIR)/usr/include $(DESTDIR)/usr/lib + cp $(PROJ) $(DESTDIR)/usr/bin/ + cp $(PROJ).conf $(DESTDIR)/etc/$(PROJ) + cp lib$(PROJ).so $(DESTDIR)/usr/lib/ + cp $(PROJ).h $(DESTDIR)/usr/include/ -distclean: - rm $(BINFILE) tmp -rf +distclean: clean clean: - rm $(BINFILE) tmp -rf + rm $(PROJ) $(PROJ).o lib$(PROJ).so + +uninstall: + rm $(DESTDIR)/usr/bin/$(PROJ) $(DESTDIR)/etc/$(PROJ) $(DESTDIR)/usr/include/$(PROJ).h $(DESTDIR)/usr/lib/lib$(PROJ).so 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 +.PHONY: valgrind clean distclean install uninstall default diff --git a/README b/README index 437edbd..0c0f23f 100644 --- a/README +++ b/README @@ -48,19 +48,48 @@ To implement: - subject changing - automatic +v/+h/+o botnet juggling between bridge IRC connections and control IRC conn - setting user@host of bridge bots on IRC to JIDs of XMPP users by temporary changing rDNS + - reusing bridge and control connections with same nick to different channels on same network Notes: - when connecting to a channel with already joined nicks, they're not connected to XMPP until they send a message or change their nick. only after that are they bridged, and the sent message is not lost. implementing this would require parsing NAMES list, which only contains nicks and not usernames and hostnames, and would also impact performance, especially on MAM-enabled MUCs that send a lot of history on XMPP join for every connection. XMPP MUCs also tend to slow down with a large number of connections, whereas IRC channels don't. + - if possible, send core dumps of crashes to my email address anton@šijanec.eu. + - coredumps contain passwords and some chats, so make sure to change XMPP password before + - to capture core dumps of systemd services (when installed on debian), use systemd-coredump -Debian installation and systemd service: - - this program is packaged in my personal package archive on http://prog.sijanec.eu/ +Debian installation, systemd service and precompiled dynamically linked binaries: + - packages in my ppa on http://prog.sijanec.eu/ (amd64, arm64, armel, i386, src, dbgsym, lib) - after adding the archive and running apt update, install with apt install ircxmpp - edit the configuration file with environment variables in /etc/ircxmpp - run the service with service ircxmpp start, enable it at boot with systemctl enable ircxmpp + - ci/cd: http://jenkins.sijanec.eu/job/ircxmpp for amd64, arm64 and armel Security: - - Do not trust this program. - - If you find a (security) vulnerability or any issue, please email anton at šijanec.eu - - Whenever possible, run the program in a chroot jail as an unpriviledged user. Running with systemd (service ircxmpp start) does that. + - do not trust this program. + - if you find a (security) vulnerability or any issue, please email anton@šijanec.eu. + - whenever possible, run the program in a chroot jail as an unpriviledged user. Running with systemd (service ircxmpp start) does that. - -- Anton Luka Šijanec Wed, 27 Apr 2022 14:30:00 +0200 +Running multiple irc<->xmpp links at the same time: + - first link's environment variables are still in format IX_JID, IX_PASS, IX_HOST and so on + - second link's environment variables are in format IX_JID2, IX_PASS2, IX_HOST2 and so on + - this means you concatenate the config environment variable with the digit + - digits must be consecutive from 2 to infinity, vars without digit (1st link) are required + - IX_LOOPDELAY applies to all links, as there is no delay between individual links (no 2, 3) + +Compilation and manual install from source: + - git clone https://git.sijanec.eu/sijanec/ircxmpp + - cd ircxmpp + - make # tested compilers: gcc, clang and tcc. make -e CC=tcc, make -e CC=clang, ... + - make install # not required, but otherwise LD_LIBRARY_PATH=. ./ircxmpp for libircxmpp.so + +Using as a library: + - you may only use functions between #else and #endif in ircxmpp.h, handler struct is opaque + - after make install, ircxmpp.h and libircxmpp.so are placed in /usr/{include,lib} + - #include and link with -lircxmpp + - functions return nothing (except ircxmpp_init) and log to standard error + - do not call ircxmpp_set_*() functions after first call to ircxmpp_run_once() + - actual main function of the ircxmpp program is in ircxmpp.c between #else and #endif + +Gentoo/openrc?: + - http://github.com/OpenRC/openrc/pull/517 needs to be merged before for increased security + + -- Anton Luka Šijanec Fri, 29 Apr 2022 17:00:00 +0200 diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index ce4a19f..0000000 --- a/debian/changelog +++ /dev/null @@ -1,11 +0,0 @@ -ircxmpp (0.0.2-1) stable; urgency=low - - * Some fixes. - - -- Anton Luka Šijanec Wed, 27 Apr 2021 15:45:00 +0200 - -ircxmpp (0.0.1-1) stable; urgency=low - - * Initial release. - - -- Anton Luka Šijanec Wed, 27 Apr 2021 14:00:00 +0200 diff --git a/debian/changelog b/debian/changelog new file mode 120000 index 0000000..a535994 --- /dev/null +++ b/debian/changelog @@ -0,0 +1 @@ +../CHANGELOG \ No newline at end of file diff --git a/ircxmpp.c b/ircxmpp.c new file mode 100644 index 0000000..5d4b8d9 --- /dev/null +++ b/ircxmpp.c @@ -0,0 +1,692 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ircxmpp.h" +#ifdef IX_LIB +#define BUG(...) bug(__FILE__, __func__, __LINE__, __VA_ARGS__) +__attribute__((noreturn)) static void bug (const char * file, const char * function, int line, const char * format, ...) { + va_list ap; + va_start(ap, format); + fprintf(stderr, "BUG DETECTED in %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 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 { + if ((*bridge)->next) + (*bridge)->next->prev = (*bridge)->prev; + *bridge = (*bridge)->next; + } + free(tofree); +} +static void free_bridges (struct bridge ** bridges) { + while (*bridges) { // enkrat bo *bridges NULL, ker ne bo nobenega več notri + if ((*bridges)->prev) + BUG("(*bridges)->prev is set"); + free_bridge(bridges, "vsi mostovi se podirajo, ker se ircxmpp izklaplja"); + } +} + +static struct bridge ** find_bridge (struct bridge ** bridges, const char * id, enum side side) { +s: + if (!bridges || !*bridges) + return NULL; + if ((*bridges)->side == side && !strcmp((*bridges)->identifier, id)) + return bridges; + bridges = &((*bridges)->next); + goto s; +} + +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_resp = find_bridge(&ircxmpp->bridges, f, !s); + if (strstr(f, "ircxmpp_") || (ircxmpp->irchost && strstr(f, ircxmpp->irchost))) + return; + fprintf(stderr, "sending text from %s to %s: %s\n", f, s == IRC ? "IRC" : "XMPP", + m ? m : "[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(f); + bridge->ircxmpp = ircxmpp; + bridge->side = !s; + 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); + } + } else + bridge = *bridge_resp; + 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, xmpp_stanza_t * const stanza, void * const ud) { + if (!c) /* just to get rid of -Wunused-parameter */ + return 1; + struct ircxmpp * ircxmpp = (struct ircxmpp *) ud; + 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; +} +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))) { + 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; +} + +static 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 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); + } +} +static 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); + } else { + fprintf(stderr, "DEBUG: disconnected\n"); + // xmpp_stop(ircxmpp->ctx); + } + +} + +/* IRC */ +static 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, "%s", p[i]); + } + fprintf(stderr, "]\n"); +} +static 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); +} +static 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); +} +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); /* 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/."); +} +static 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 */ +static 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); +} +static 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); +} +static 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 */ +} +static 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 */ +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(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"); + 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; +} +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()); + 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 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))) { + fprintf(stderr, "could not create IRC session!\n"); + 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; + 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 */ +struct ircxmpp * ircxmpp_init (void) { + xmpp_initialize(); + return calloc(1, sizeof(struct ircxmpp)); +} +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); +} +void ircxmpp_run_once (struct ircxmpp * ircxmpp) { + if (!ircxmpp->ctx || !ircxmpp->conn || (!xmpp_conn_is_connected(ircxmpp->conn) + && !xmpp_conn_is_connecting(ircxmpp->conn))) { + fprintf(stderr, "XMPP control is DISCONNECTED! CONNECTING!\n"); + if (ircxmpp->conn) + xmpp_conn_release(ircxmpp->conn); + if (ircxmpp->ctx) + xmpp_ctx_free(ircxmpp->ctx); + ircxmpp->ctx = xmpp_ctx_new(NULL, xmpp_get_default_logger(XMPP_LEVEL_DEBUG)); + 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))) { + fprintf(stderr, "IRC control is DISCONNECTED with code %d. CONNECTING!\n", ret); + if (ircxmpp->irc) + irc_destroy_session(ircxmpp->irc); + init_irc_control(ircxmpp); + } + struct bridge ** bridge = &ircxmpp->bridges; + while (bridge && *bridge) { + struct bridge ** next = NULL; + if ((*bridge)->next) + next = &((*bridge)->next); + if ((*bridge)->irc && irc_run_once(*bridge)) { + free_bridge(bridge, "irc connection dropped"); + goto cont; + } + if ((*bridge)->conn && !xmpp_conn_is_connected((*bridge)->conn) + && !xmpp_conn_is_connecting(((*bridge)->conn))) { + if ((*bridge)->side == IRC && (*bridge)->messages_length) { + fprintf(stderr, "RECONNECTING BRIDGE BECAUSE IT DIED and has msgs!"); + 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(ircxmpp->ctx, + "groupchat", 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; + } +} +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) { + shouldexit += s+1; /* only for -Wunused-parameter */ +} +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 diff --git a/ircxmpp.conf b/ircxmpp.conf index fd635c7..ae11eef 100644 --- a/ircxmpp.conf +++ b/ircxmpp.conf @@ -19,3 +19,13 @@ # IX_CHPASS=somepassword ## delay after each event loop cycle in microseconds, defaults to 100ms. # IX_LOOPDELAY=100000 +####################### ANOTHER LINK SETUP ######################## +## As many links as you'd like can be setup. Append a number, starting with 2, to every setting. +# IX_JID2=v@lu.e +# IX_PASS2=value +# IX_HOST2=valu.e +# IX_PORT2=6666 +# IX_CHANNEL2=#value +# IX_MUC2=v@l.u.e +# IX_CHPASS2=value +# IX_LOOPDELAY2 does not exist, as the event loop is shared between all links. diff --git a/ircxmpp.h b/ircxmpp.h index da03aa5..89f23a8 100644 --- a/ircxmpp.h +++ b/ircxmpp.h @@ -1,4 +1,6 @@ -#include +#ifdef IX_LIB /* do not use functions until #else in programs that use libircxmpp. */ +#include /* do not use members of struct ircxmpp, use opaque ircxmpp type! */ +#include enum irc_numeric { /* numerics from rfc 1459 */ ERR_NOSUCHNICK = 401, ERR_NOSUCHSERVER, @@ -165,32 +167,52 @@ struct ircxmpp { char * muc; char * channel_password; }; -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, +static void free_bridge (struct bridge **, const char *); +static void free_bridges (struct bridge **); +static struct bridge ** find_bridge (struct bridge **, const char *, enum side); +static void jid2ircnick (char *); +static void jid2ircuser (char *); +static void bridge_forward (const char *, const char *, struct ircxmpp *, enum side); +static int message_handler (xmpp_conn_t * const, xmpp_stanza_t * const, void * const); +static int presence_handler (xmpp_conn_t * const, xmpp_stanza_t * const, void * const); +static 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, +static 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 *); +static void dump_event (irc_session_t *, const char *, const char *, const char **, unsigned); +static void event_connect (irc_session_t *, const char *, const char *, const char **, unsigned); +static void event_connect_control ( + irc_session_t *, const char *, const char *, const char **, unsigned); +static void event_privmsg (irc_session_t *, const char *, const char *, const char **, unsigned); +static void event_partquit_control ( + irc_session_t *, const char *, const char *, const char **, unsigned); +static void event_join_control ( + irc_session_t *, const char *, const char *, const char **, unsigned); +static void event_channel_control ( + irc_session_t *, const char *, const char *, const char **, unsigned); +static void event_nick_control(irc_session_t *, const char *, const char *, const char **, unsigned); +static void event_topic_control ( + irc_session_t *, const char *, const char *, const char **, unsigned); +static void event_numeric ( + irc_session_t *, unsigned int, const char *, const char **, unsigned); +static void event_numeric_control ( + irc_session_t *, unsigned int, const char *, const char **, unsigned); +static int irc_run_once (struct bridge *); +static void init_irc (struct bridge *); +static void init_irc_control (struct ircxmpp *); // /IRC -int main (void); +#endif // IX_LIB +// ZUNANJE +typedef struct ircxmpp ircxmpp; /* opaque handle */ +ircxmpp * ircxmpp_init (void); +void ircxmpp_set_jid (ircxmpp *, const char *); +void ircxmpp_set_password (ircxmpp *, const char *); +void ircxmpp_set_hostname (ircxmpp *, const char *); +void ircxmpp_set_port (ircxmpp *, unsigned short int); +void ircxmpp_set_channel (ircxmpp *, const char *); +void ircxmpp_set_muc (ircxmpp *, const char *); +void ircxmpp_set_channel_password (ircxmpp *, const char *); +void ircxmpp_run_once (struct ircxmpp *); +void ircxmpp_free (struct ircxmpp *); +// /ZUNANJE diff --git a/main.c b/main.c deleted file mode 100644 index 4359e78..0000000 --- a/main.c +++ /dev/null @@ -1,605 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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, - sizeof(*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 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 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, "%s", 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(); - 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); - return 0; -} -- cgit v1.2.3