diff options
Diffstat (limited to 'examples/weechat.c')
-rw-r--r-- | examples/weechat.c | 355 |
1 files changed, 251 insertions, 104 deletions
diff --git a/examples/weechat.c b/examples/weechat.c index 0c26f6e..cd89adb 100644 --- a/examples/weechat.c +++ b/examples/weechat.c @@ -1,11 +1,36 @@ -/* This is quite messy and should be refactored, but good enough for - testing. */ +/* + 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 <jid> <password> + Open a chat buffer (from the server buffer): q <jid> + Join a conference (from the server buffer): j <room>@<server>/<nick> + + 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 <stdlib.h> #include <string.h> #include <syslog.h> +#include <libxml/tree.h> #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"); @@ -22,16 +47,17 @@ struct weechat_rexmpp { struct weechat_rexmpp_muc { struct weechat_rexmpp *wr; - char *jid; + struct rexmpp_jid jid; }; struct t_weechat_plugin *weechat_plugin = NULL; -void my_logger (const struct weechat_rexmpp *wr, +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; @@ -50,143 +76,217 @@ void my_logger (const struct weechat_rexmpp *wr, weechat_printf(wr->server_buffer, "%s\n", buf); } -int my_sasl_property_cb (const struct weechat_rexmpp *wr, Gsasl_property prop) { - rexmpp_t *s = &wr->rexmpp_state; +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) { - char *domainpart = strchr(s->initial_jid, '@'); - if (domainpart != NULL) { - int localpart_len = domainpart - s->initial_jid; - char *localpart = malloc(localpart_len + 1); - localpart[localpart_len] = 0; - strncpy(localpart, s->initial_jid, localpart_len); - gsasl_property_set (s->sasl_session, GSASL_AUTHID, localpart); - free(localpart); - return GSASL_OK; - } + 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 struct weechat_rexmpp *wr, void *data, +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; - char *to = weechat_buffer_get_string(buffer, "name"); + 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", s->assigned_jid, input_data); + weechat_printf_date_tags(buffer, 0, "self_msg", "%s\t%s\n", ">", input_data); return WEECHAT_RC_OK; } -int query_close_cb (struct weechat_rexmpp *wr, void *data, +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 struct weechat_rexmpp_muc *wrm, void *data, +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; - char *to = weechat_buffer_get_string(buffer, "name"); xmlNodePtr msg = rexmpp_xml_add_id(s, xmlNewNode(NULL, "message")); - xmlNewProp(msg, "to", to); + 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 (struct weechat_rexmpp_muc *wrm, void *data, +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); - xmlNewProp(presence, "to", wrm->jid); + 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; } -int my_xml_in_cb (struct weechat_rexmpp *wr, xmlNodePtr node) { +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"); - char *display_name = from; - int i, resource_removed = 0; - for (i = 0; i < strlen(from); i++) { - if (from[i] == '/') { - from[i] = 0; - display_name = from + i + 1; - resource_removed = 1; - break; - } - } if (from != NULL) { - struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", from); + 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, + buf = weechat_buffer_new (from_jid.bare, &query_input_cb, wr, NULL, &query_close_cb, wr, NULL); weechat_buffer_set(buf, "nicklist", "1"); } - if (resource_removed) { - from[i] = '/'; /* restore */ - } xmlNodePtr body = rexmpp_xml_find_child(node, "jabber:client", "body"); - if (body != NULL) { - 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); + + 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); } } - xmlFree(from); + + if (body != NULL) { + display_message(buf, display_name, body); + } } } if (rexmpp_xml_match(node, "jabber:client", "presence")) { char *presence_type = xmlGetProp(node, "type"); - char *jid = xmlGetProp(node, "from"); - int i; - char *resource = ""; - for (i = 0; i < strlen(jid); i++) { - if (jid[i] == '/') { - jid[i] = 0; - resource = jid + i + 1; - break; + 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); + } + } } - } - if (rexmpp_xml_find_child(node, "http://jabber.org/protocol/muc#user", "x")) { + /* Update MUC nicklist */ - struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", jid); + 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, resource); + 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, resource, + 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", ""); + } + } } - free(jid); if (presence_type != NULL) { free(presence_type); } @@ -195,17 +295,25 @@ int my_xml_in_cb (struct weechat_rexmpp *wr, xmlNodePtr node) { return 0; } -int my_xml_out_cb (struct weechat_rexmpp *wr, xmlNodePtr node) { +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 struct weechat_rexmpp *wr, void *data, +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); @@ -222,7 +330,7 @@ my_input_cb (const struct weechat_rexmpp *wr, void *data, weechat_printf(buffer, "Failed to read a document\n"); } } else if (input_data[0] == 'q' && input_data[1] == ' ') { - char *jid = input_data + 2; + const char *jid = input_data + 2; struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", jid); if (buf == NULL) { buf = weechat_buffer_new (jid, @@ -230,42 +338,22 @@ my_input_cb (const struct weechat_rexmpp *wr, void *data, &query_close_cb, wr, NULL); weechat_buffer_set(buf, "nicklist", "1"); } - } else if (input_data[0] == 'j' && input_data[1] == ' ') { - char *jid = input_data + 2; - xmlNodePtr presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence")); - xmlNewProp(presence, "from", s->assigned_jid); - xmlNewProp(presence, "to", jid); - xmlNodePtr x = xmlNewNode(NULL, "x"); - xmlNewNs(x, "http://jabber.org/protocol/muc", NULL); - xmlAddChild(presence, x); - rexmpp_send(s, presence); - int i; - struct weechat_rexmpp_muc *wrm = malloc(sizeof(struct weechat_rexmpp_muc)); - wrm->wr = wr; - wrm->jid = strdup(jid); - for (i = 0; i < strlen(jid); i++) { - if (jid[i] == '/') { - jid[i] = 0; - break; - } - } - struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", jid); - if (buf == NULL) { - buf = weechat_buffer_new (jid, - &muc_input_cb, wrm, NULL, - &muc_close_cb, wrm, 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 (struct weechat_rexmpp *wr, xmlNodePtr item) { +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 */ - weechat_nicklist_remove_nick(wr->server_buffer, jid); + 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, @@ -278,11 +366,12 @@ void my_roster_modify_cb (struct weechat_rexmpp *wr, xmlNodePtr item) { } int -my_close_cb (struct weechat_rexmpp *wr, void *data, struct t_gui_buffer *buffer) +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; @@ -290,7 +379,8 @@ my_close_cb (struct weechat_rexmpp *wr, void *data, struct t_gui_buffer *buffer) void iter (struct weechat_rexmpp *wr, fd_set *rfds, fd_set *wfds); -int fd_read_cb (const struct weechat_rexmpp *wr, void *data, int fd) { +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); @@ -300,7 +390,8 @@ int fd_read_cb (const struct weechat_rexmpp *wr, void *data, int fd) { return 0; } -int fd_write_cb (const struct weechat_rexmpp *wr, void *data, int fd) { +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); @@ -310,7 +401,8 @@ int fd_write_cb (const struct weechat_rexmpp *wr, void *data, int fd) { return 0; } -int timer_cb (const struct weechat_rexmpp *wr, void *data, int remaining_calls) { +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); @@ -319,8 +411,8 @@ int timer_cb (const struct weechat_rexmpp *wr, void *data, int remaining_calls) return 0; } -void hook_free_cb (void *data, struct t_arraylist *arraylist, struct t_hook *hook) { - weechat_unhook(hook); +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) { @@ -340,7 +432,7 @@ void iter (struct weechat_rexmpp *wr, fd_set *rfds, fd_set *wfds) { return; } if (err != REXMPP_E_AGAIN) { - weechat_printf(wr->server_buffer, "rexmpp error"); + weechat_printf(wr->server_buffer, "rexmpp error: %s", rexmpp_strerror(err)); return; } fd_set read_fds, write_fds; @@ -378,6 +470,54 @@ void iter (struct weechat_rexmpp *wr, fd_set *rfds, fd_set *wfds) { } 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) @@ -391,16 +531,23 @@ command_xmpp_cb (const void *pointer, void *data, 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]); - s->log_function = my_logger; + 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>", + "message: a message to send", + NULL, + &command_sc_cb, wr, NULL); } return WEECHAT_RC_OK; } |