From 1f0f919dd2e0266c7a53306408b7c4eccb07888d Mon Sep 17 00:00:00 2001 From: defanor Date: Sun, 22 Nov 2020 23:27:19 +0300 Subject: Add the console module --- README | 2 +- examples/weechat.c | 18 ++--- src/Makefile.am | 5 +- src/rexmpp.c | 6 ++ src/rexmpp.h | 5 ++ src/rexmpp_console.c | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/rexmpp_console.h | 18 +++++ 7 files changed, 266 insertions(+), 12 deletions(-) create mode 100644 src/rexmpp_console.c create mode 100644 src/rexmpp_console.h diff --git a/README b/README index ac62fa1..eb57d66 100644 --- a/README +++ b/README @@ -88,7 +88,7 @@ A rough roadmap: - Various utility functions: [+] Display name establishment. -[ ] A console module. +[.] A console module. - Examples and application: diff --git a/examples/weechat.c b/examples/weechat.c index 904e711..29a592a 100644 --- a/examples/weechat.c +++ b/examples/weechat.c @@ -303,6 +303,12 @@ int my_xml_out_cb (rexmpp_t *s, xmlNodePtr node) { 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) @@ -332,15 +338,8 @@ my_input_cb (const void *ptr, void *data, &query_close_cb, wr, NULL); weechat_buffer_set(buf, "nicklist", "1"); } - } else if (input_data[0] == 'j' && input_data[1] == ' ') { - const char *jid = input_data + 2; - xmlNodePtr presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence")); - xmlNewProp(presence, "from", s->assigned_jid.full); - xmlNewProp(presence, "to", jid); - xmlNodePtr x = xmlNewNode(NULL, "x"); - xmlNewNs(x, "http://jabber.org/protocol/muc", NULL); - xmlAddChild(presence, x); - rexmpp_send(s, presence); + } else { + rexmpp_console_feed(s, input_data, strlen(input_data)); } return WEECHAT_RC_OK; } @@ -538,6 +537,7 @@ command_xmpp_cb (const void *pointer, void *data, 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); diff --git a/src/Makefile.am b/src/Makefile.am index 488b96d..2a0dab0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,9 +14,10 @@ librexmpp_la_SOURCES = rexmpp_roster.h rexmpp_roster.c \ rexmpp.h rexmpp.c \ rexmpp_dns.h rexmpp_dns.c \ rexmpp_jid.h rexmpp_jid.c \ - rexmpp_openpgp.h rexmpp_openpgp.c + rexmpp_openpgp.h rexmpp_openpgp.c \ + rexmpp_console.h rexmpp_console.c include_HEADERS = rexmpp_roster.h rexmpp_tcp.h rexmpp_socks.h rexmpp.h \ - rexmpp_dns.h rexmpp_jid.h rexmpp_openpgp.h + rexmpp_dns.h rexmpp_jid.h rexmpp_openpgp.h rexmpp_console.h librexmpp_la_CFLAGS = $(AM_CFLAGS) $(LIBXML_CFLAGS) $(GNUTLS_CFLAGS) \ $(LIBDANE_CFLAGS) $(GSASL_CFLAGS) $(UNBOUND_CFLAGS) $(GPGME_CFLAGS) librexmpp_la_LIBADD = $(LIBXML_LIBS) $(GNUTLS_LIBS) $(LIBDANE_LIBS) \ diff --git a/src/rexmpp.c b/src/rexmpp.c index c84eb5d..3216817 100644 --- a/src/rexmpp.c +++ b/src/rexmpp.c @@ -32,6 +32,7 @@ #include "rexmpp_dns.h" #include "rexmpp_jid.h" #include "rexmpp_openpgp.h" +#include "rexmpp_console.h" void rexmpp_sax_start_elem_ns (rexmpp_t *s, const char *localname, @@ -394,6 +395,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid) s->xml_in_cb = NULL; s->xml_out_cb = NULL; s->roster_modify_cb = NULL; + s->console_print_cb = NULL; s->ping_delay = 600; s->ping_requested = 0; s->last_network_activity = 0; @@ -849,6 +851,8 @@ rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node) return REXMPP_E_SEND_QUEUE_FULL; } + rexmpp_console_on_send(s, node); + if (rexmpp_xml_is_stanza(node)) { if (s->sm_state == REXMPP_SM_ACTIVE) { if (s->stanzas_out_count - s->stanzas_out_acknowledged >= @@ -1594,6 +1598,8 @@ rexmpp_err_t rexmpp_stream_bind (rexmpp_t *s) { } rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { + rexmpp_console_on_recv(s, elem); + /* IQs. These are the ones that should be processed by the library; if a user-facing application wants to handle them on its own, it should cancel further processing by the library (so we can send diff --git a/src/rexmpp.h b/src/rexmpp.h index 5ad2f61..623390f 100644 --- a/src/rexmpp.h +++ b/src/rexmpp.h @@ -208,6 +208,7 @@ typedef int (*sasl_property_cb_t) (rexmpp_t *s, Gsasl_property prop); typedef int (*xml_in_cb_t) (rexmpp_t *s, xmlNodePtr node); typedef int (*xml_out_cb_t) (rexmpp_t *s, xmlNodePtr node); typedef void (*roster_modify_cb_t) (rexmpp_t *s, xmlNodePtr item); +typedef int (*console_print_cb_t) (rexmpp_t *s, const char *format, va_list args); /** @brief Complete connection state */ struct rexmpp @@ -260,6 +261,7 @@ struct rexmpp xml_in_cb_t xml_in_cb; xml_out_cb_t xml_out_cb; roster_modify_cb_t roster_modify_cb; + console_print_cb_t console_print_cb; /* Stream-related state. */ struct rexmpp_jid assigned_jid; @@ -512,4 +514,7 @@ xmlNodePtr rexmpp_find_event (rexmpp_t *s, const char *from, const char *node, xmlNodePtr *prev_event); + +void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len); + #endif diff --git a/src/rexmpp_console.c b/src/rexmpp_console.c new file mode 100644 index 0000000..47c0bfa --- /dev/null +++ b/src/rexmpp_console.c @@ -0,0 +1,224 @@ +/** + @file rexmpp_console.c + @brief A console module + @author defanor + @date 2020 + @copyright MIT license. + + The "console" is supposed to provide a few common and basic + commands, and to be easily embeddable into programs, similarly to + an XML console. +*/ + +#include + +#include "rexmpp.h" +#include "rexmpp_openpgp.h" +#include "rexmpp_console.h" + + +void rexmpp_console_printf (rexmpp_t *s, const char *format, ...) +{ + va_list args; + if (s->console_print_cb != NULL) { + va_start(args, format); + s->console_print_cb (s, format, args); + va_end(args); + } +} + +char *rexmpp_console_message_string (rexmpp_t *s, xmlNodePtr node) { + char *ret = NULL; + 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) { + rexmpp_console_printf(s, "An invalid OpenPGP message!\n"); + } + + 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) { + ret = xmlNodeGetContent(pl_body); + } + } + xmlFreeNode(elem); + } + } + if (ret == NULL) { + xmlNodePtr body = rexmpp_xml_find_child(node, "jabber:client", "body"); + ret = xmlNodeGetContent(body); + } + return ret; +} + +void rexmpp_console_on_send (rexmpp_t *s, xmlNodePtr node) { + if (rexmpp_xml_match(node, "jabber:client", "message")) { + char *to = xmlGetProp(node, "to"); + if (to != NULL) { + /* "from" should be set for verification. */ + char *from = xmlGetProp(node, "from"); + xmlAttrPtr fromProp = NULL; + if (from == NULL) { + fromProp = xmlNewProp(node, "from", to); + } + char *str = rexmpp_console_message_string(s, node); + if (fromProp != NULL) { + xmlRemoveProp(fromProp); + } + if (str != NULL) { + rexmpp_console_printf(s, "You tell %s: %s\n", to, str); + free(str); + } + free(to); + } + } + if (rexmpp_xml_match(node, "jabber:client", "presence")) { + char *presence_type = xmlGetProp(node, "type"); + rexmpp_console_printf(s, "Becoming %s\n", + (presence_type == NULL) ? + "available" : + presence_type); + if (presence_type != NULL) { + free(presence_type); + } + } +} + +void rexmpp_console_on_recv (rexmpp_t *s, xmlNodePtr node) { + if (rexmpp_xml_match(node, "jabber:client", "message")) { + char *from = xmlGetProp(node, "from"); + if (from != NULL) { + char *str = rexmpp_console_message_string(s, node); + if (str != NULL) { + rexmpp_console_printf(s, "%s tells you: %s\n", from, str); + free(str); + } + free(from); + } + } + if (rexmpp_xml_match(node, "jabber:client", "presence")) { + char *presence_type = xmlGetProp(node, "type"); + char *from = xmlGetProp(node, "from"); + rexmpp_console_printf(s, "%s is %s\n", from, + (presence_type == NULL) ? + "available" : + presence_type); + if (presence_type != NULL) { + free(presence_type); + } + if (from != NULL) { + free(from); + } + } +} + +void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { + /* todo: buffering */ + char *words_save_ptr; + char *word, *jid_str, *msg_text; + struct rexmpp_jid jid; + word = strtok_r(str, " ", &words_save_ptr); + if (word == NULL) { + return; + } + + const char *help = + "Available commands:\n" + "help\n" + "quit\n" + "tell \n" + "signcrypt \n" + "publish-key \n" + "join [as] \n" + ; + + if (! strcmp(word, "help")) { + rexmpp_console_printf(s, help); + } + + if (! strcmp(word, "quit")) { + rexmpp_console_printf(s, "Quitting.\n"); + rexmpp_stop(s); + return; + } + + if (! strcmp(word, "publish-key")) { + char *fingerprint = strtok_r(NULL, " ", &words_save_ptr); + rexmpp_openpgp_publish_key(s, fingerprint); + } + + if (! strcmp(word, "tell")) { + jid_str = strtok_r(NULL, " ", &words_save_ptr); + if (jid_str == NULL || rexmpp_jid_parse(jid_str, &jid)) { + return; + } + msg_text = jid_str + strlen(jid_str) + 1; + xmlNodePtr msg = rexmpp_xml_add_id(s, xmlNewNode(NULL, "message")); + xmlNewProp(msg, "to", jid.full); + xmlNewProp(msg, "type", "chat"); + xmlNewTextChild(msg, NULL, "body", msg_text); + rexmpp_send(s, msg); + } + + if (! strcmp(word, "signcrypt")) { + jid_str = strtok_r(NULL, " ", &words_save_ptr); + if (jid_str == NULL || rexmpp_jid_parse(jid_str, &jid)) { + return; + } + msg_text = jid_str + strlen(jid_str) + 1; + xmlNodePtr body = xmlNewNode(NULL, "body"); + xmlNewNs(body, "jabber:client", NULL); + xmlNodeAddContent(body, msg_text); + const char *rcpt[2]; + rcpt[0] = jid.full; + rcpt[1] = NULL; + char *b64 = rexmpp_openpgp_encrypt_sign(s, body, rcpt); + xmlNodePtr openpgp = xmlNewNode(NULL, "openpgp"); + openpgp->ns = 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", jid.full); + 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); + + rexmpp_send(s, msg); + } + + if (! strcmp(word, "join")) { + jid_str = strtok_r(NULL, " ", &words_save_ptr); + if (jid_str == NULL || rexmpp_jid_parse(jid_str, &jid)) { + return; + } + word = strtok_r(NULL, " ", &words_save_ptr); + if (! strcmp(word, "as")) { + word = strtok_r(NULL, " ", &words_save_ptr); + } + if (word == NULL) { + return; + } + char *full_jid = malloc(strlen(jid.bare) + strlen(word) + 2); + snprintf(full_jid, strlen(jid_str) + strlen(word) + 2, "%s/%s", + jid.bare, word); + xmlNodePtr presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence")); + xmlNewProp(presence, "from", s->assigned_jid.full); + xmlNewProp(presence, "to", full_jid); + xmlNodePtr x = xmlNewNode(NULL, "x"); + xmlNewNs(x, "http://jabber.org/protocol/muc", NULL); + xmlAddChild(presence, x); + rexmpp_send(s, presence); + } +} diff --git a/src/rexmpp_console.h b/src/rexmpp_console.h new file mode 100644 index 0000000..a7841a5 --- /dev/null +++ b/src/rexmpp_console.h @@ -0,0 +1,18 @@ +/** + @file rexmpp_console.h + @brief A console module + @author defanor + @date 2020 + @copyright MIT license. +*/ + +#ifndef REXMPP_CONSOLE_H +#define REXMPP_CONSOLE_H + +#include "rexmpp.h" + +void rexmpp_console_on_send (rexmpp_t *s, xmlNodePtr node); +void rexmpp_console_on_recv (rexmpp_t *s, xmlNodePtr node); +void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len); + +#endif -- cgit v1.2.3