/* This is quite messy and should be refactored, but good enough for testing. Building: gcc -fPIC -Wall -Wno-pointer-sign -c `pkg-config --cflags --libs weechat libgsasl libxml-2.0 gnutls rexmpp` examples/weechat.c gcc `pkg-config --cflags --libs weechat libgsasl libxml-2.0 gnutls rexmpp` -shared -fPIC -o weechat.so weechat.o mv weechat.so ~/.weechat/plugins/rexmpp.so Usage: Connect: /xmpp Open a chat buffer (from the server buffer): q Join a conference (from the server buffer): j @/ TODO: - Refine/rethink control/commands. - Add settings (SASL parameters and regular rexmpp configuration). - Add commands for roster management and other functionality. - Refactor the hacky bits of this plugin. */ #include #include #include #include #include "weechat-plugin.h" #include "rexmpp.h" #include "rexmpp_roster.h" #include "rexmpp_jid.h" #include "rexmpp_openpgp.h" WEECHAT_PLUGIN_NAME("rexmpp"); WEECHAT_PLUGIN_DESCRIPTION("XMPP plugin using librexmpp"); WEECHAT_PLUGIN_AUTHOR("defanor "); WEECHAT_PLUGIN_VERSION("0.0.0"); WEECHAT_PLUGIN_LICENSE("MIT"); struct weechat_rexmpp { rexmpp_t rexmpp_state; struct t_gui_buffer *server_buffer; char *password; struct t_arraylist *hooks; }; struct weechat_rexmpp_muc { struct weechat_rexmpp *wr; struct rexmpp_jid jid; }; struct t_weechat_plugin *weechat_plugin = NULL; void my_logger (rexmpp_t *s, int priority, const char *fmt, va_list args) { struct weechat_rexmpp *wr = (struct weechat_rexmpp *)s; char *priority_str = "unknown"; switch (priority) { case LOG_EMERG: priority_str = "emerg"; break; case LOG_ALERT: priority_str = "alert"; break; case LOG_CRIT: priority_str = "crit"; break; case LOG_ERR: priority_str = "err"; break; case LOG_WARNING: priority_str = "warning"; break; case LOG_NOTICE: priority_str = "notice"; break; case LOG_INFO: priority_str = "info"; break; case LOG_DEBUG: priority_str = "debug"; break; } char buf[4096]; sprintf(buf, "[%s] ", priority_str); vsprintf(buf + strlen(buf), fmt, args); weechat_printf(wr->server_buffer, "%s\n", buf); } int my_sasl_property_cb (rexmpp_t *s, Gsasl_property prop) { struct weechat_rexmpp *wr = (struct weechat_rexmpp *)s; if (prop == GSASL_PASSWORD) { gsasl_property_set (s->sasl_session, GSASL_PASSWORD, wr->password); return GSASL_OK; } if (prop == GSASL_AUTHID) { gsasl_property_set (s->sasl_session, GSASL_AUTHID, s->initial_jid.local); return GSASL_OK; } weechat_printf(wr->server_buffer, "unhandled gsasl property: %d\n", prop); return GSASL_NO_CALLBACK; } int query_input_cb (const void *ptr, void *data, struct t_gui_buffer *buffer, const char *input_data) { struct weechat_rexmpp *wr = (void*)ptr; rexmpp_t *s = &wr->rexmpp_state; const char *to = weechat_buffer_get_string(buffer, "name"); xmlNodePtr msg = rexmpp_xml_add_id(s, xmlNewNode(NULL, "message")); xmlNewProp(msg, "to", to); xmlNewProp(msg, "type", "chat"); xmlNewTextChild(msg, NULL, "body", input_data); rexmpp_send(s, msg); weechat_printf_date_tags(buffer, 0, "self_msg", "%s\t%s\n", ">", input_data); return WEECHAT_RC_OK; } int query_close_cb (const void *ptr, void *data, struct t_gui_buffer *buffer) { /* struct weechat_rexmpp *wr = (void*)ptr; */ return WEECHAT_RC_OK; } int muc_input_cb (const void *ptr, void *data, struct t_gui_buffer *buffer, const char *input_data) { struct weechat_rexmpp_muc *wrm = (void*)ptr; rexmpp_t *s = &wrm->wr->rexmpp_state; xmlNodePtr msg = rexmpp_xml_add_id(s, xmlNewNode(NULL, "message")); xmlNewProp(msg, "to", wrm->jid.bare); xmlNewProp(msg, "type", "groupchat"); xmlNewTextChild(msg, NULL, "body", input_data); rexmpp_send(s, msg); return WEECHAT_RC_OK; } int muc_close_cb (const void *ptr, void *data, struct t_gui_buffer *buffer) { struct weechat_rexmpp_muc *wrm = (void*)ptr; rexmpp_t *s = &wrm->wr->rexmpp_state; xmlNodePtr presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence")); xmlNewProp(presence, "from", s->assigned_jid.full); xmlNewProp(presence, "to", wrm->jid.full); xmlNewProp(presence, "type", "unavailable"); rexmpp_send(s, presence); free(wrm); return WEECHAT_RC_OK; } void display_message (struct t_gui_buffer *buf, const char *display_name, xmlNodePtr body) { xmlChar *str = xmlNodeGetContent(body); if (str != NULL) { char tags[4096]; snprintf(tags, 4096, "nick_%s", display_name); weechat_printf_date_tags(buf, 0, tags, "%s\t%s\n", display_name, str); xmlFree(str); } } int my_xml_in_cb (rexmpp_t *s, xmlNodePtr node) { struct weechat_rexmpp *wr = (struct weechat_rexmpp *)s; char *xml_buf = rexmpp_xml_serialize(node); weechat_printf(wr->server_buffer, "recv: %s\n", xml_buf); /* free(xml_buf); */ if (rexmpp_xml_match(node, "jabber:client", "message")) { char *from = xmlGetProp(node, "from"); if (from != NULL) { struct rexmpp_jid from_jid; rexmpp_jid_parse(from, &from_jid); xmlFree(from); char *display_name = from_jid.full; if (from_jid.resource[0]) { display_name = from_jid.resource; } struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", from_jid.bare); if (buf == NULL) { buf = weechat_buffer_new (from_jid.bare, &query_input_cb, wr, NULL, &query_close_cb, wr, NULL); weechat_buffer_set(buf, "nicklist", "1"); } xmlNodePtr body = rexmpp_xml_find_child(node, "jabber:client", "body"); xmlNodePtr openpgp = rexmpp_xml_find_child(node, "urn:xmpp:openpgp:0", "openpgp"); if (openpgp != NULL) { int valid; xmlNodePtr elem = rexmpp_openpgp_decrypt_verify_message(s, node, &valid); if (! valid) { weechat_printf(buf, "An invalid OpenPGP message!"); } if (elem != NULL) { xmlNodePtr payload = rexmpp_xml_find_child(elem, "urn:xmpp:openpgp:0", "payload"); if (payload != NULL) { xmlNodePtr pl_body = rexmpp_xml_find_child(payload, "jabber:client", "body"); if (pl_body != NULL) { display_message(buf, display_name, pl_body); body = NULL; } } xmlFreeNode(elem); } } if (body != NULL) { display_message(buf, display_name, body); } } } if (rexmpp_xml_match(node, "jabber:client", "presence")) { char *presence_type = xmlGetProp(node, "type"); char *from = xmlGetProp(node, "from"); struct rexmpp_jid from_jid; rexmpp_jid_parse(from, &from_jid); xmlFree(from); xmlNodePtr muc = rexmpp_xml_find_child(node, "http://jabber.org/protocol/muc#user", "x"); if (muc != NULL) { /* Handle newly joined MUCs */ if (presence_type == NULL) { xmlNodePtr status = rexmpp_xml_find_child(muc, "http://jabber.org/protocol/muc#user", "status"); if (status != NULL) { char *code = xmlGetProp(status, "code"); if (code != NULL) { if (strcmp(code, "110") == 0) { struct weechat_rexmpp_muc *wrm = malloc(sizeof(struct weechat_rexmpp_muc)); wrm->wr = wr; rexmpp_jid_parse(from_jid.full, &(wrm->jid)); struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", wrm->jid.bare); if (buf == NULL) { buf = weechat_buffer_new (wrm->jid.bare, &muc_input_cb, wrm, NULL, &muc_close_cb, wrm, NULL); weechat_buffer_set(buf, "nicklist", "1"); } } free(code); } } } /* Update MUC nicklist */ struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", from_jid.bare); if (buf != NULL) { if (presence_type != NULL && strcmp(presence_type, "unavailable") == 0) { struct t_gui_nick *nick = weechat_nicklist_search_nick(buf, NULL, from_jid.resource); if (nick != NULL) { weechat_nicklist_remove_nick(buf, nick); } } else { weechat_nicklist_add_nick(buf, NULL, from_jid.resource, "bar_fg", "", "lightgreen", 1); } } } else if (rexmpp_roster_find_item(s, from_jid.bare, NULL) != NULL) { /* A roster item. */ struct t_gui_nick *nick = weechat_nicklist_search_nick(wr->server_buffer, NULL, from_jid.bare); if (presence_type == NULL) { /* An "available" presence: just ensure that it's shown as online. */ weechat_nicklist_nick_set(wr->server_buffer, nick, "prefix", "+"); } else if (strcmp(presence_type, "unavailable") == 0) { /* An "unavailable" presence: set it to "offline" if there's no remaining online resources (i.e., if we can find an online resource for this bare JID other than the one that just went offline). */ xmlNodePtr cur; int found = 0; struct rexmpp_jid cur_from_jid; for (cur = s->roster_presence; cur != NULL; cur = xmlNextElementSibling(cur)) { char *cur_from = xmlGetProp(cur, "from"); rexmpp_jid_parse(cur_from, &cur_from_jid); xmlFree(cur_from); if (strcmp(cur_from_jid.bare, from_jid.bare) == 0 && strcmp(cur_from_jid.resource, from_jid.resource) != 0) { found = 1; } } if (! found) { weechat_nicklist_nick_set(wr->server_buffer, nick, "prefix", ""); } } } if (presence_type != NULL) { free(presence_type); } } free(xml_buf); return 0; } int my_xml_out_cb (rexmpp_t *s, xmlNodePtr node) { struct weechat_rexmpp *wr = (struct weechat_rexmpp *)s; char *xml_buf = rexmpp_xml_serialize(node); weechat_printf(wr->server_buffer, "send: %s\n", xml_buf); free(xml_buf); return 0; } void my_console_print_cb (struct weechat_rexmpp *wr, const char *fmt, va_list args) { char str[4096]; vsnprintf(str, 4096, fmt, args); weechat_printf(wr->server_buffer, "%s", str); } int my_input_cb (const void *ptr, void *data, struct t_gui_buffer *buffer, const char *input_data) { struct weechat_rexmpp *wr = (void*)ptr; rexmpp_t *s = &wr->rexmpp_state; if (input_data[0] == '<') { xmlDocPtr doc = xmlReadMemory(input_data, strlen(input_data), "", "utf-8", 0); if (doc != NULL) { xmlNodePtr node = xmlDocGetRootElement(doc); if (node != NULL) { xmlUnlinkNode(node); rexmpp_send(s, node); } else { weechat_printf(buffer, "No root node\n"); } xmlFreeDoc(doc); } else { weechat_printf(buffer, "Failed to read a document\n"); } } else if (input_data[0] == 'q' && input_data[1] == ' ') { const char *jid = input_data + 2; struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", jid); if (buf == NULL) { buf = weechat_buffer_new (jid, &query_input_cb, wr, NULL, &query_close_cb, wr, NULL); weechat_buffer_set(buf, "nicklist", "1"); } } else { rexmpp_console_feed(s, input_data, strlen(input_data)); } return WEECHAT_RC_OK; } void my_roster_modify_cb (rexmpp_t *s, xmlNodePtr item) { struct weechat_rexmpp *wr = (struct weechat_rexmpp *)s; char *subscription = xmlGetProp(item, "subscription"); char *jid = xmlGetProp(item, "jid"); if (subscription != NULL && strcmp(subscription, "remove") == 0) { /* delete */ struct t_gui_nick *nick = weechat_nicklist_search_nick(wr->server_buffer, NULL, jid); if (nick != NULL) { weechat_nicklist_remove_nick(wr->server_buffer, nick); } } else { /* add or modify */ weechat_nicklist_add_nick(wr->server_buffer, NULL, jid, "bar_fg", "", "lightgreen", 1); } free(jid); if (subscription != NULL) { free(subscription); } } int my_close_cb (const void *ptr, void *data, struct t_gui_buffer *buffer) { /* todo: close MUC buffers first? or at least mark them somehow, so that they won't attempt to send "unavailable" presence on closing. */ struct weechat_rexmpp *wr = (void*)ptr; wr->server_buffer = NULL; rexmpp_stop(&wr->rexmpp_state); return WEECHAT_RC_OK; } void iter (struct weechat_rexmpp *wr, fd_set *rfds, fd_set *wfds); int fd_read_cb (const void *ptr, void *data, int fd) { struct weechat_rexmpp *wr = (void*)ptr; /* weechat_printf(wr->server_buffer, "read hook fired"); */ fd_set read_fds, write_fds; FD_ZERO(&read_fds); FD_ZERO(&write_fds); FD_SET(fd, &read_fds); iter(wr, &read_fds, &write_fds); return 0; } int fd_write_cb (const void *ptr, void *data, int fd) { struct weechat_rexmpp *wr = (void*)ptr; /* weechat_printf(wr->server_buffer, "write hook fired"); */ fd_set read_fds, write_fds; FD_ZERO(&read_fds); FD_ZERO(&write_fds); FD_SET(fd, &write_fds); iter(wr, &read_fds, &write_fds); return 0; } int timer_cb (const void *ptr, void *data, int remaining_calls) { struct weechat_rexmpp *wr = (void*)ptr; /* weechat_printf(wr->server_buffer, "timer hook fired"); */ fd_set read_fds, write_fds; FD_ZERO(&read_fds); FD_ZERO(&write_fds); iter(wr, &read_fds, &write_fds); return 0; } void hook_free_cb (void *data, struct t_arraylist *arraylist, void *hook) { weechat_unhook((struct t_hook *)hook); } void iter (struct weechat_rexmpp *wr, fd_set *rfds, fd_set *wfds) { rexmpp_t *s = &wr->rexmpp_state; /* cleanup old hooks */ /* weechat_printf(wr->server_buffer, "-- hooks removed --"); */ weechat_arraylist_clear(wr->hooks); rexmpp_err_t err; err = rexmpp_run(s, rfds, wfds); if (err == REXMPP_SUCCESS) { free(wr->password); rexmpp_done(&wr->rexmpp_state); weechat_arraylist_free(wr->hooks); free(wr); return; } if (err != REXMPP_E_AGAIN) { weechat_printf(wr->server_buffer, "rexmpp error: %s", rexmpp_strerror(err)); return; } fd_set read_fds, write_fds; int nfds; struct timeval tv; struct timeval *mtv; int i; FD_ZERO(&read_fds); FD_ZERO(&write_fds); nfds = rexmpp_fds(s, &read_fds, &write_fds); mtv = rexmpp_timeout(s, NULL, (struct timeval*)&tv); for (i = 0; i < nfds; i++) { if (FD_ISSET(i, &read_fds)) { /* weechat_printf(wr->server_buffer, "read hook set"); */ weechat_arraylist_add(wr->hooks, weechat_hook_fd(i, 1, 0, 0, fd_read_cb, wr, NULL)); } if (FD_ISSET(i, &write_fds)) { /* weechat_printf(wr->server_buffer, "write hook set"); */ weechat_arraylist_add(wr->hooks, weechat_hook_fd(i, 0, 1, 0, fd_write_cb, wr, NULL)); } } if (mtv != NULL) { /* weechat_printf(wr->server_buffer, "timer hook set: %d %d\n", mtv->tv_sec, mtv->tv_sec); */ int t = mtv->tv_sec * 1000 + mtv->tv_sec / 1000; if (t == 0) { /* A hack, since at 0 weechat won't fire a hook. */ t = 1; } weechat_arraylist_add(wr->hooks, weechat_hook_timer(t, 0, 1, timer_cb, wr, NULL)); } } int command_sc_cb (const void *wr_ptr, void *data, struct t_gui_buffer *buffer, int argc, char **argv, char **argv_eol) { struct weechat_rexmpp *wr = (void*)wr_ptr; rexmpp_t *s = &wr->rexmpp_state; const char *to = weechat_buffer_get_string(buffer, "name"); xmlNodePtr body = xmlNewNode(NULL, "body"); xmlNewNs(body, "jabber:client", NULL); xmlNodeAddContent(body, argv_eol[1]); const char *rcpt[2]; rcpt[0] = to; rcpt[1] = NULL; char *b64 = rexmpp_openpgp_encrypt_sign(s, body, rcpt); if (b64 == NULL) { weechat_printf(buffer, "Failed to encrypt a message."); return WEECHAT_RC_OK; } xmlNodePtr openpgp = xmlNewNode(NULL, "openpgp"); xmlNewNs(openpgp, "urn:xmpp:openpgp:0", NULL); xmlNodeAddContent(openpgp, b64); free(b64); xmlNodePtr msg = rexmpp_xml_add_id(s, xmlNewNode(NULL, "message")); xmlNewProp(msg, "to", to); xmlNewProp(msg, "type", "chat"); xmlAddChild(msg, openpgp); body = xmlNewNode(NULL, "body"); xmlNewNs(body, "jabber:client", NULL); xmlNodeAddContent(body, "This is a secret message."); xmlAddChild(msg, body); /* XEP-0380: Explicit Message Encryption */ xmlNodePtr eme = xmlNewNode(NULL, "encryption"); xmlNewNs(eme, "urn:xmpp:eme:0", NULL); xmlNewProp(eme, "namespace", "urn:xmpp:openpgp:0"); xmlAddChild(msg, eme); rexmpp_send(s, msg); weechat_printf_date_tags(buffer, 0, "self_msg", "%s\t%s\n", ">", argv_eol[1]); return WEECHAT_RC_OK; } int command_xmpp_cb (const void *pointer, void *data, struct t_gui_buffer *buffer, int argc, char **argv, char **argv_eol) { if (argc == 3) { struct weechat_rexmpp *wr = malloc(sizeof(struct weechat_rexmpp)); wr->server_buffer = weechat_buffer_new (argv[1], &my_input_cb, wr, NULL, &my_close_cb, wr, NULL); weechat_buffer_set(wr->server_buffer, "nicklist", "1"); wr->password = strdup(argv[2]); wr->hooks = weechat_arraylist_new(42, 0, 0, NULL, NULL, hook_free_cb, NULL); rexmpp_t *s = &wr->rexmpp_state; rexmpp_init(s, argv[1], my_logger); s->sasl_property_cb = my_sasl_property_cb; s->xml_in_cb = my_xml_in_cb; s->xml_out_cb = my_xml_out_cb; s->roster_modify_cb = my_roster_modify_cb; s->console_print_cb = my_console_print_cb; fd_set read_fds, write_fds; FD_ZERO(&read_fds); FD_ZERO(&write_fds); iter(wr, &read_fds, &write_fds); weechat_hook_command ("sc", "Sign and encrypt a message", "", "message: a message to send", NULL, &command_sc_cb, wr, NULL); } return WEECHAT_RC_OK; } int weechat_plugin_init (struct t_weechat_plugin *plugin, int argc, char *argv[]) { weechat_plugin = plugin; weechat_hook_command ("xmpp", "Initialise an XMPP session", " ", "jid: JID\npassword: password", NULL, &command_xmpp_cb, NULL, NULL); return WEECHAT_RC_OK; } int weechat_plugin_end (struct t_weechat_plugin *plugin) { return WEECHAT_RC_OK; }