#define _GNU_SOURCE #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)) __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); if (strcmp(razlog, "free_bridge_from_tdestroy")) // če nismo iz tdestroy 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? if (strcmp(razlog, "free_bridge_from_tdestroy")) // če nismo iz tdestroy 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 void free_bridge_from_tdestroy (void * bridge) { free_bridge((struct bridge *) bridge, "free_bridge_from_tdestroy"); } static void free_bridges (void ** bridges) { tdestroy(*bridges, free_bridge_from_tdestroy); *bridges = NULL; } 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)) || (ircxmpp->domain && strstr(f, ircxmpp->domain))) 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)); if (!bridge) { return; LOG(ircxmpp, IRCXMPP_ERROR, "calloc bridge failed"); } bridge->identifier = strdup(f); bridge->ircxmpp = ircxmpp; bridge->side = !s; tsearch(bridge, &ircxmpp->bridges, bridge_compare); if (s == IRC) { if (ircxmpp->domain) { char buf[512+512+strlen(ircxmpp->domain)]; // for good measure char * cp = strchr(f, '@'); // 512+1+strlen is ok if (cp) { // cmiiw strncpy(buf, cp+1, 511); buf[511] = '\0'; } else strcpy(buf, "unable.to.extract.domain.from.JID"); *strchrnul(buf, '/') = '\0'; if (buf[strlen(buf)-1] != '.') // jid domain can probably end with . strcat(buf, "."); // two consecutive dots would strcat(buf, ircxmpp->domain); // +invalidate the domain ircxmpp->domain_setter(ircxmpp->domain_setter_userdata, buf); } 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 && strlen(m) < 65535) { char * msg = alloca(strlen(m)+1); { char * w = msg; char * r = msg; do // odstranimo crje if (*r != '\r') w++[0] = *r; while (++r[0]); w++[0] = '\0'; } char * send; while ((send = strtok(msg, "\n"))) { irc_cmd_msg(bridge->irc, ircxmpp->channel, send+1); } } irc_run_once(bridge); } else if (m) { typeof(bridge->messages) new = reallocarray(bridge->messages, bridge->messages_length+1, sizeof(*bridge->messages)); if (!new) { LOG(ircxmpp, IRCXMPP_ERROR, "reallocarray(bridge->messages, bridge->messages_length+1 = %z, sizeof(*bridge->messages) = %z) failed", bridge->messages_length+1, sizeof*(bridge->messages)); return; } bridge->messages = new; 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 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, "bridge 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[i])+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[i])+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 avtor, p[0] kanal, p[1] je nova tema 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); } 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 (ircxmpp->domain) { char domain[512+strlen(ircxmpp->domain)]; strcpy(domain, "čžš .. invalid domain so that we get our IP address as irchost .."); strcat(domain, ircxmpp->domain); ircxmpp->domain_setter(ircxmpp->domain_setter_userdata, domain); } 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; } /* /IRC */ /* irc_is_connected(irc_session_t * session): 1 ali 0 */ static void default_log_handler (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); } static void default_domain_setter (void * u __attribute__((unused)), const char * d __attribute__((unused))) { return; // does nothing } 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)); if (!ircxmpp) return NULL; ircxmpp->log_handler = default_log_handler; 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 = default_log_handler; 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); } void ircxmpp_set_domain_setter (struct ircxmpp * ircxmpp, ircxmpp_domain_setter setter) { if (!setter) setter = default_domain_setter; ircxmpp->domain_setter = setter; } void ircxmpp_set_domain_setter_userdata (struct ircxmpp * ircxmpp, void * userdata) { if (!userdata) userdata = NULL; ircxmpp->domain_setter_userdata = userdata; } void ircxmpp_set_domain (struct ircxmpp * ircxmpp, const char * domain) { free(ircxmpp->domain); ircxmpp->domain = strdup(domain); // this intentionally crashes } static void obdelaj_bridge (const void * nodep, VISIT which __attribute__((unused)), int depth __attribute__((unused))) { // to be called only from twalk struct bridge * bridge = *(struct bridge **) nodep; if (bridge->irc && irc_run_once(bridge)) { MR(bridge->ircxmpp->to_free_bridges); MR(bridge->ircxmpp->to_free_reasons); bridge->ircxmpp->to_free_bridges[bridge->ircxmpp->to_free_bridges_length++] = bridge; bridge->ircxmpp->to_free_reasons[bridge->ircxmpp->to_free_reasons_length++] = "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 { MR(bridge->ircxmpp->to_free_bridges); MR(bridge->ircxmpp->to_free_reasons); bridge->ircxmpp->to_free_bridges[bridge->ircxmpp->to_free_bridges_length++] = bridge; bridge->ircxmpp->to_free_reasons[bridge->ircxmpp->to_free_reasons_length++] = "xmpp connection dropped"; } return; } const char * jid = NULL; if (bridge->conn && xmpp_conn_is_connected(bridge->conn) // TODO: test this && (jid = xmpp_conn_get_bound_jid(bridge->conn))) { for (size_t i = 0; i < bridge->messages_length; i++) { if (!bridge->conn || !xmpp_conn_is_connected(bridge->conn)) { LOG(bridge->ircxmpp, IRCXMPP_WARN, "message for bridge %s dropped", bridge->identifier); free(bridge->messages[i]); continue; } 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[i]); } bridge->messages_length = 0; } } 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->bridges) { // bridges contain ctx inside of their conns 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); } ircxmpp->to_free_bridges_length = ircxmpp->to_free_reasons_length = 0; ircxmpp->to_free_bridges_sizeof = ircxmpp->to_free_reasons_sizeof = ALLOC_CHUNK; size_t req = sizeof(*ircxmpp->to_free_bridges)*ircxmpp->to_free_bridges_sizeof; ircxmpp->to_free_bridges = malloc(req); ircxmpp->to_free_reasons = malloc(req); if (!ircxmpp->to_free_reasons || !ircxmpp->to_free_bridges) { LOG(ircxmpp, IRCXMPP_ERROR, "malloc(%z) to_free failed!", req); goto r; } twalk(ircxmpp->bridges, obdelaj_bridge); for (size_t i = 0; i < ircxmpp->to_free_bridges_length; i++) free_bridge(ircxmpp->to_free_bridges[i], ircxmpp->to_free_reasons[i]); r: free(ircxmpp->to_free_bridges); free(ircxmpp->to_free_reasons); ircxmpp->to_free_bridges_length = ircxmpp->to_free_bridges_sizeof = ircxmpp->to_free_reasons_length = ircxmpp->to_free_reasons_sizeof = 0; } void ircxmpp_free (struct ircxmpp * ircxmpp) { LOG(ircxmpp, IRCXMPP_DEBUG, "engaging the freeing routine"); 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->domain); free(ircxmpp); } #else #include "dns.c" 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; char * domain = NULL; // to know if we want to run dns server or not ircxmpp ** handles = NULL; while (1) { char b[64]; if (handles_length++) sprintf(b, "IX_JID%zu", 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"); free(handles); 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 "%zu", 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); \ free(handles); \ return 2; \ } 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) PREPARE_HANDLE(domain, "DOMAIN", str2str, 0) if (getenv(b)) domain = getenv(b); } struct dns * dns = NULL; if (domain) { dns = dns_init(); char buf[512+strlen(domain)]; strcpy(buf, "ircxmpp.no.domain.set.yet."); strcat(buf, domain); dns_set_domain(dns, buf); if (getenv("IX_DNS_PORT")) dns_set_port(dns, atoi(getenv("IX_DNS_PORT"))); if (getenv("IX_DNS_IP")) dns_set_ip(dns, getenv("IX_DNS_IP")); dns_set_log_handler(dns, dns_default_log_handler); dns_set_log_userdata(dns, NULL); // so we don't read uninitialized values } 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 : 1e7 }; if (dns) dns_run_once(dns); 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); dns_free(dns); return 0; } #endif