summaryrefslogtreecommitdiff
path: root/emacs/xml_interface.c
diff options
context:
space:
mode:
Diffstat (limited to 'emacs/xml_interface.c')
-rw-r--r--emacs/xml_interface.c402
1 files changed, 402 insertions, 0 deletions
diff --git a/emacs/xml_interface.c b/emacs/xml_interface.c
new file mode 100644
index 0000000..f98d5d1
--- /dev/null
+++ b/emacs/xml_interface.c
@@ -0,0 +1,402 @@
+/**
+ @file xml-interface.c
+ @brief An XML interface to communicate with Emacs.
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+
+A basic and ad hoc XML interface. The parent process (e.g., Emacs) is
+supposed to respond to requests starting with the most recent one.
+
+This program's output is separated with NUL ('\0') characters, to
+simplify parsing in Emacs, while the input is separated with newlines,
+to simplify reading with rexmpp_xml_read_fd.
+
+*/
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <syslog.h>
+#include <gnutls/gnutls.h>
+#include <rexmpp.h>
+#include <rexmpp_xml.h>
+#include <rexmpp_openpgp.h>
+#include <rexmpp_http_upload.h>
+
+
+void print_xml (rexmpp_xml_t *node) {
+ char *s = rexmpp_xml_serialize(node, 0);
+ printf("%s%c\n", s, '\0');
+ free(s);
+}
+
+char *request (rexmpp_t *s, rexmpp_xml_t *payload)
+{
+ rexmpp_xml_t *req = rexmpp_xml_new_elem("request", NULL);
+ rexmpp_xml_add_id(req);
+ rexmpp_xml_add_child(req, payload);
+ print_xml(req);
+ char *id = strdup(rexmpp_xml_find_attr_val(req, "id"));
+ rexmpp_xml_free(req);
+ return id;
+}
+
+void req_process (rexmpp_t *s,
+ rexmpp_xml_t *elem);
+
+rexmpp_xml_t *read_response (rexmpp_t *s, const char *id) {
+ rexmpp_xml_t *elem = rexmpp_xml_read_fd(STDIN_FILENO);
+ if (elem != NULL) {
+ if (rexmpp_xml_match(elem, NULL, "response")) {
+ const char *resp_id = rexmpp_xml_find_attr_val(elem, "id");
+ if (resp_id != NULL) {
+ if (strcmp(resp_id, id) == 0) {
+ return elem;
+ } else {
+ /* Just fail for now, to avoid deadlocks. Though this
+ shouldn't happen. */
+ rexmpp_xml_free(elem);
+ rexmpp_log(s, LOG_ERR, "Unexpected response ID received.");
+ return NULL;
+ }
+ }
+ }
+ req_process(s, elem);
+ rexmpp_xml_free(elem);
+ }
+ return read_response(s, id);
+}
+
+rexmpp_xml_t *req_block (rexmpp_t *s, rexmpp_xml_t *req) {
+ char *id = request(s, req);
+ rexmpp_xml_t *resp = read_response(s, id);
+ free(id);
+ return resp;
+}
+
+void respond_xml (rexmpp_t *s,
+ const char *id,
+ rexmpp_xml_t *payload) {
+ rexmpp_xml_t *response = rexmpp_xml_new_elem("response", NULL);
+ rexmpp_xml_add_attr(response, "id", id);
+ if (payload != NULL) {
+ rexmpp_xml_add_child(response, payload);
+ }
+ print_xml(response);
+ rexmpp_xml_free(response);
+}
+
+void respond_text (rexmpp_t *s,
+ const char *id,
+ const char *buf) {
+ rexmpp_xml_t *response = rexmpp_xml_new_elem("response", NULL);
+ rexmpp_xml_add_attr(response, "id", id);
+ if (buf != NULL) {
+ rexmpp_xml_add_text(response, buf);
+ }
+ print_xml(response);
+ rexmpp_xml_free(response);
+}
+
+void on_http_upload (rexmpp_t *s, void *cb_data, const char *url) {
+ char *id = cb_data;
+ respond_text(s, id, url);
+ free(id);
+}
+
+void req_process (rexmpp_t *s,
+ rexmpp_xml_t *elem)
+{
+ const char *id = rexmpp_xml_find_attr_val(elem, "id");
+ if (id == NULL) {
+ return;
+ }
+ rexmpp_err_t err;
+ char buf[64];
+ rexmpp_xml_t *child = rexmpp_xml_first_elem_child(elem);
+ if (rexmpp_xml_match(child, NULL, "stop")) {
+ snprintf(buf, 64, "%d", rexmpp_stop(s));
+ respond_text(s, id, buf);
+ } else if (rexmpp_xml_match(child, NULL, "console")) {
+ char *in = strdup(rexmpp_xml_text_child(child));
+ rexmpp_console_feed(s, in, strlen(in));
+ free(in);
+ respond_text(s, id, NULL);
+ } else if (rexmpp_xml_match(child, NULL, "send")) {
+ if (rexmpp_xml_first_elem_child(child)) {
+ rexmpp_xml_t *stanza =
+ rexmpp_xml_clone(rexmpp_xml_first_elem_child(child));
+ snprintf(buf, 64, "%d", rexmpp_send(s, stanza));
+ respond_text(s, id, buf);
+ }
+ } else if (rexmpp_xml_match(child, NULL, "openpgp-decrypt-message")) {
+ int valid;
+ rexmpp_xml_t *plaintext =
+ rexmpp_openpgp_decrypt_verify_message(s, rexmpp_xml_first_elem_child(child),
+ &valid);
+ /* todo: wrap into another element, with the 'valid' attribute */
+ respond_xml(s, id, plaintext);
+ } else if (rexmpp_xml_match(child, NULL, "openpgp-payload")) {
+ enum rexmpp_ox_mode mode = REXMPP_OX_CRYPT;
+ const char *mode_str = rexmpp_xml_find_attr_val(child, "mode");
+ if (strcmp(mode_str, "sign") == 0) {
+ mode = REXMPP_OX_SIGN;
+ } else if (strcmp(mode_str, "signcrypt") == 0) {
+ mode = REXMPP_OX_SIGNCRYPT;
+ }
+
+ rexmpp_xml_t *payload_xml =
+ rexmpp_xml_first_elem_child(rexmpp_xml_find_child(child, NULL, "payload"));
+
+ char *recipients[16];
+ int recipients_num = 0;
+ rexmpp_xml_t *plchild;
+ for (plchild = rexmpp_xml_first_elem_child(child);
+ plchild != NULL && recipients_num < 15;
+ plchild = plchild->next) {
+ if (rexmpp_xml_match(plchild, NULL, "to")) {
+ recipients[recipients_num] = strdup(rexmpp_xml_text_child(plchild));
+ recipients_num++;
+ }
+ }
+ recipients[recipients_num] = NULL;
+ char *payload_str =
+ rexmpp_openpgp_payload(s, rexmpp_xml_clone(payload_xml),
+ (const char **)recipients, NULL, mode);
+ for (recipients_num = 0; recipients[recipients_num] != NULL; recipients_num++) {
+ free(recipients[recipients_num]);
+ }
+ respond_text(s, id, payload_str);
+ free(payload_str);
+ } else if (rexmpp_xml_match(child, NULL, "get-name")) {
+ const char *jid = rexmpp_xml_text_child(child);
+ if (jid != NULL) {
+ char *name = rexmpp_get_name(s, jid);
+ if (name != NULL) {
+ respond_text(s, id, name);
+ free(name);
+ }
+ }
+ } else if (rexmpp_xml_match(child, NULL, "http-upload")) {
+ char *in = strdup(rexmpp_xml_text_child(child));
+ rexmpp_http_upload_path(s, NULL, in, NULL, on_http_upload, strdup(id));
+ free(in);
+ /* Responding from on_http_upload */
+ } else if (rexmpp_xml_match(child, NULL, "muc-ping-set")) {
+ const char *occupant_jid = rexmpp_xml_find_attr_val(child, "occupant-jid");
+ const char *delay = rexmpp_xml_find_attr_val(child, "delay");
+ const char *password = rexmpp_xml_find_attr_val(child, "password");
+ if (occupant_jid != NULL && delay != NULL) {
+ snprintf(buf, 64, "%d",
+ rexmpp_muc_ping_set(s, occupant_jid, password, atoi(delay)));
+ respond_text(s, id, buf);
+ }
+ } else if (rexmpp_xml_match(child, NULL, "muc-ping-remove")) {
+ const char *occupant_jid = rexmpp_xml_find_attr_val(child, "occupant-jid");
+ if (occupant_jid != NULL) {
+ snprintf(buf, 64, "%d", rexmpp_muc_ping_remove(s, occupant_jid));
+ respond_text(s, id, buf);
+ }
+ }
+ return;
+}
+
+void my_logger (rexmpp_t *s, int priority, const char *fmt, va_list args) {
+ /* Or could just use stderr. */
+ char *buf = malloc(4096);
+ vsnprintf(buf, 4096, fmt, args);
+ 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;
+ }
+ rexmpp_xml_t *node = rexmpp_xml_new_elem("log", NULL);
+ rexmpp_xml_add_attr(node, "priority", priority_str);
+ rexmpp_xml_add_text(node, buf);
+ free(buf);
+ print_xml(node);
+ rexmpp_xml_free(node);
+}
+
+int my_console_print_cb (rexmpp_t *s, const char *fmt, va_list args) {
+ char *buf = malloc(1024 * 20);
+ vsnprintf(buf, 1024 * 20, fmt, args);
+ rexmpp_xml_t *node = rexmpp_xml_new_elem("console", NULL);
+ rexmpp_xml_add_text(node, buf);
+ free(buf);
+ print_xml(node);
+ rexmpp_xml_free(node);
+ return 0;
+}
+
+int my_sasl_property_cb (rexmpp_t *s, rexmpp_sasl_property prop) {
+ if (prop == REXMPP_SASL_PROP_AUTHID) {
+ rexmpp_sasl_property_set (s, REXMPP_SASL_PROP_AUTHID, s->initial_jid.local);
+ return 0;
+ }
+ char *prop_str = NULL;
+ switch (prop) {
+ case REXMPP_SASL_PROP_PASSWORD: prop_str = "password"; break;
+ case REXMPP_SASL_PROP_AUTHID: prop_str = "authid"; break;
+ default: return -1;
+ }
+ rexmpp_xml_t *req = rexmpp_xml_new_elem("sasl", NULL);
+ rexmpp_xml_add_attr(req, "property", prop_str);
+ rexmpp_xml_t *rep = req_block(s, req);
+ if (rep == NULL) {
+ return -1;
+ }
+ const char *val = rexmpp_xml_text_child(rep);
+ if (val == NULL) {
+ return -1;
+ }
+ rexmpp_sasl_property_set (s, prop, val);
+ rexmpp_xml_free(rep);
+ return GSASL_OK;
+}
+
+int my_xml_in_cb (rexmpp_t *s, rexmpp_xml_t *node) {
+ rexmpp_xml_t *req = rexmpp_xml_new_elem("xml-in", NULL);
+ rexmpp_xml_add_child(req, rexmpp_xml_clone(node));
+ rexmpp_xml_t *rep = req_block(s, req);
+ if (rep == NULL) {
+ return 0;
+ }
+ const char *val = rexmpp_xml_text_child(rep);
+ if (val == NULL) {
+ return 0;
+ }
+ int n = atoi(val);
+ rexmpp_xml_free(rep);
+ return n;
+}
+
+int my_xml_out_cb (rexmpp_t *s, rexmpp_xml_t *node) {
+ rexmpp_xml_t *req = rexmpp_xml_new_elem("xml-out", NULL);
+ rexmpp_xml_add_child(req, rexmpp_xml_clone(node));
+ rexmpp_xml_t *rep = req_block(s, req);
+ if (rep == NULL) {
+ return 0;
+ }
+ const char *val = rexmpp_xml_text_child(rep);
+ if (val == NULL) {
+ return 0;
+ }
+ int n = atoi(val);
+ rexmpp_xml_free(rep);
+ return n;
+}
+
+
+int main (int argc, char **argv) {
+
+ /* The minimal initialisation: provide an allocated rexmpp_t
+ structure and an initial jid. */
+ rexmpp_t s;
+ rexmpp_err_t err;
+ err = rexmpp_init(&s, argv[1], my_logger);
+ if (err != REXMPP_SUCCESS) {
+ return -1;
+ }
+ 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.console_print_cb = my_console_print_cb;
+
+ /* Could set a client certificate for SASL EXTERNAL authentication
+ here. */
+ /* gnutls_certificate_set_x509_key_file(s.gnutls_cred, */
+ /* "cert.pem", */
+ /* "key.pem", */
+ /* GNUTLS_X509_FMT_PEM); */
+
+ /* Could also set various other things manually. */
+ /* s.socks_host = "127.0.0.1"; */
+ /* s.socks_port = 4321; */
+ /* s.manual_host = "foo.custom"; */
+ /* gnutls_certificate_set_x509_trust_file(s.gnutls_cred, */
+ /* "foo.custom.crt", */
+ /* GNUTLS_X509_FMT_PEM); */
+ /* rexmpp_openpgp_set_home_dir(&s, "pgp"); */
+ s.roster_cache_file = "roster.xml";
+
+
+ /* Once the main structure is initialised and everything is
+ sufficiently configured, we are ready to run the main loop and
+ call rexmpp from it. */
+
+ fd_set read_fds, write_fds;
+ int nfds;
+ struct timespec tv;
+ struct timespec *mtv;
+ struct timeval tv_ms;
+ struct timeval *mtv_ms;
+ int n = 0;
+
+ do {
+ /* Check if we have some user input. */
+ if (n > 0 && FD_ISSET(STDIN_FILENO, &read_fds)) {
+ rexmpp_xml_t *elem = rexmpp_xml_read_fd(STDIN_FILENO);
+ if (elem != NULL) {
+ req_process(&s, elem);
+ rexmpp_xml_free(elem);
+ }
+ }
+
+ /* Run a single rexmpp iteration. */
+ err = rexmpp_run(&s, &read_fds, &write_fds);
+
+ if (err == REXMPP_SUCCESS) {
+ break;
+ }
+ if (err != REXMPP_E_AGAIN) {
+ printf("error: %s\n", rexmpp_strerror(err));
+ break;
+ }
+ /* Could inspect the state here. */
+ /* printf("res %d / conn %d / tls %d / sasl %d / stream %d / carbons %d\n", */
+ /* s.resolver_state, */
+ /* s.tcp_state, */
+ /* s.tls_state, */
+ /* s.sasl_state, */
+ /* s.stream_state, */
+ /* s.carbons_state); */
+
+ /* Ask rexmpp which file descriptors it is interested in, and what
+ the timeouts should be. */
+ FD_ZERO(&read_fds);
+ FD_ZERO(&write_fds);
+ nfds = rexmpp_fds(&s, &read_fds, &write_fds);
+ mtv = rexmpp_timeout(&s, NULL, &tv);
+ mtv_ms = NULL;
+ if (mtv != NULL) {
+ tv_ms.tv_sec = mtv->tv_sec;
+ tv_ms.tv_usec = mtv->tv_nsec / 1000;
+ mtv_ms = &tv_ms;
+ }
+
+ /* Add other file descriptors we are interested in, particularly
+ stdin for user input. */
+ FD_SET(STDIN_FILENO, &read_fds);
+
+ /* Run select(2) with all those file descriptors and timeouts,
+ waiting for either user input or some rexmpp event to occur. */
+ n = select(nfds, &read_fds, &write_fds, NULL, mtv_ms);
+ if (n == -1) {
+ printf("select error: %s\n", strerror(errno));
+ break;
+ }
+ } while (1);
+
+ /* Deinitialise the rexmpp structure in the end, freeing whatever it
+ allocated. */
+ rexmpp_done(&s);
+ return 0;
+}