summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordefanor <defanor@uberspace.net>2020-11-22 23:27:19 +0300
committerdefanor <defanor@uberspace.net>2020-11-22 23:27:19 +0300
commit1f0f919dd2e0266c7a53306408b7c4eccb07888d (patch)
tree7dd486986c3b3f5215cfe8d35820ee2177a4a203
parent89512dc79db51dac151996dc9460472dec00d242 (diff)
Add the console module
-rw-r--r--README2
-rw-r--r--examples/weechat.c18
-rw-r--r--src/Makefile.am5
-rw-r--r--src/rexmpp.c6
-rw-r--r--src/rexmpp.h5
-rw-r--r--src/rexmpp_console.c224
-rw-r--r--src/rexmpp_console.h18
7 files changed, 266 insertions, 12 deletions
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 <defanor@uberspace.net>
+ @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 <string.h>
+
+#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 <jid> <message>\n"
+ "signcrypt <jid> <message>\n"
+ "publish-key <fingerprint>\n"
+ "join <conference> [as] <nick>\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 <defanor@uberspace.net>
+ @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