summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am2
-rw-r--r--README108
-rw-r--r--configure.ac152
-rw-r--r--doap.xml219
-rw-r--r--emacs/README24
-rw-r--r--emacs/xml_interface.c402
-rw-r--r--emacs/xmpp.el701
-rw-r--r--examples/basic.c232
-rw-r--r--examples/weechat.c355
-rw-r--r--rexmpp.texi5
-rw-r--r--src/Cargo.toml17
-rw-r--r--src/Makefile.am62
-rw-r--r--src/rexmpp.c2896
-rw-r--r--src/rexmpp.h510
-rw-r--r--src/rexmpp.rs280
-rw-r--r--src/rexmpp_base64.c127
-rw-r--r--src/rexmpp_base64.h35
-rw-r--r--src/rexmpp_console.c914
-rw-r--r--src/rexmpp_console.h19
-rw-r--r--src/rexmpp_digest.c125
-rw-r--r--src/rexmpp_digest.h85
-rw-r--r--src/rexmpp_dns.c485
-rw-r--r--src/rexmpp_dns.h130
-rw-r--r--src/rexmpp_dns.rs62
-rw-r--r--src/rexmpp_http_upload.c216
-rw-r--r--src/rexmpp_http_upload.h52
-rw-r--r--src/rexmpp_jid.c185
-rw-r--r--src/rexmpp_jid.h25
-rw-r--r--src/rexmpp_jid.rs17
-rw-r--r--src/rexmpp_jingle.c2472
-rw-r--r--src/rexmpp_jingle.h168
-rw-r--r--src/rexmpp_openpgp.c887
-rw-r--r--src/rexmpp_openpgp.h102
-rw-r--r--src/rexmpp_pubsub.c88
-rw-r--r--src/rexmpp_pubsub.h40
-rw-r--r--src/rexmpp_random.c34
-rw-r--r--src/rexmpp_random.h29
-rw-r--r--src/rexmpp_random.rs5
-rw-r--r--src/rexmpp_roster.c99
-rw-r--r--src/rexmpp_roster.h15
-rw-r--r--src/rexmpp_rust.rs8
-rw-r--r--src/rexmpp_sasl.c262
-rw-r--r--src/rexmpp_sasl.h110
-rw-r--r--src/rexmpp_socks.rs180
-rw-r--r--src/rexmpp_tcp.c214
-rw-r--r--src/rexmpp_tcp.h57
-rw-r--r--src/rexmpp_tcp.rs469
-rw-r--r--src/rexmpp_tls.c987
-rw-r--r--src/rexmpp_tls.h161
-rw-r--r--src/rexmpp_utf8.h93
-rw-r--r--src/rexmpp_xml.c805
-rw-r--r--src/rexmpp_xml.h269
-rw-r--r--src/rexmpp_xml.rs1040
-rw-r--r--src/rexmpp_xml_parser.c323
-rw-r--r--src/rexmpp_xml_parser.h106
-rw-r--r--src/rexmpp_xml_parser.rs145
-rw-r--r--tests/Makefile.am24
-rw-r--r--tests/README8
-rw-r--r--tests/base64.c18
-rw-r--r--tests/send_to_self.c192
-rw-r--r--tests/xml_parse_and_print.c29
-rw-r--r--tests/xml_print_and_parse.c64
62 files changed, 16321 insertions, 1624 deletions
diff --git a/Makefile.am b/Makefile.am
index fcb3911..40ca628 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,5 +1,5 @@
ACLOCAL_AMFLAGS = -I m4
-SUBDIRS = src
+SUBDIRS = src tests
EXTRA_DIST = examples/basic.c examples/weechat.c
info_TEXINFOS = rexmpp.texi
diff --git a/README b/README
index 3a43f2a..858b59c 100644
--- a/README
+++ b/README
@@ -1,7 +1,5 @@
rexmpp - a reusable XMPP IM client library
-This is currently at a draft/prototype stage.
-
The goal is to produce a library reusable from different languages
(via C FFI), without hijacking an event loop, requiring any specific
one, or otherwise restricting a user, and fairly feature-rich. The
@@ -14,7 +12,31 @@ rely on any particular UI, should be flexible and not stay in the way
of implementing additional XEPs on top of it, and should try to make
it easy to implement a decent client application using it.
-Current dependencies: c-ares, libxml2, gnutls, gsasl.
+Mandatory dependencies:
+
+- libxml2, libexpat, or rxml (Rust)
+- gcrypt, nettle, or openssl
+
+Optional dependencies:
+
+- gsasl
+- libunbound or c-ares
+- gnutls with gnutls-dane or openssl
+- icu-i18n
+- gpgme
+- curl
+
+For Jingle calls:
+
+- libnice (with glib)
+- gnutls or openssl
+- libsrtp2
+- portaudio
+- opus (optional)
+
+For use of the alternative Rust implementations, rustc and cargo
+should be available, and the following Rust libraries will be pulled:
+libc, errno, rxml.
A rough roadmap:
@@ -22,67 +44,91 @@ A rough roadmap:
- Basic protocol:
-[+] XMPP core (RFC 6120). A prototype is mostly ready, though it can
- use more of error handling and refinement.
+[+] XMPP core (RFC 6120). Mostly ready, though would be nice to
+ review and refactor.
- Reliable and predictable message delivery:
-[+] XEP-0198: Stream Management. Implemented (both acknowledgements
- and resumption, making use of XEP-0203: Delayed Delivery).
-[+] XEP-0280: Message Carbons.
+[+] XEP-0198 v1.6: Stream Management. Implemented (both acknowledgements
+ and resumption, making use of XEP-0203 v2.0: Delayed Delivery).
+[+] XEP-0280 v0.13: Message Carbons.
- Better connectivity:
[+] "Happy Eyeballs" (RFC 8305).
-[+] XEP-0368: SRV records for XMPP over TLS.
-[+] SOCKS5 (RFC 1928) support. Implemented, though can be improved.
-[+] XEP-0199: XMPP Ping.
+[+] XEP-0368 v1.1: SRV records for XMPP over TLS (when built with
+ libunbound or c-ares).
+[+] SOCKS5 (RFC 1928) support. Implemented, though no authentication.
+[+] XEP-0199 v2.0: XMPP Ping.
+[.] Certificate verification using DANE (experimental, only when built
+ with GnuTLS).
+[+] DNSSEC checks (when built with libunbound).
- Library refinement:
[.] Doxygen documentation.
[.] Texinfo manual.
-[ ] Proper JID handling (RFC 7622).
-[ ] Abstraction of the used XML, SASL, TLS, and DNS libraries, and
- optional usage of alternative ones. Though maybe shouldn't
- abstract out XML functions and structures: could reuse existing
- libxml2 bindings that way.
-[ ] Automated testing.
+[.] Proper JID handling (RFC 7622).
+[+] Abstraction of the used XML, SASL, TLS, and DNS libraries, and
+ optional usage of alternative ones.
+[.] Automated testing.
+[.] Alternative module implementations in Rust (looks like there is a
+ memory leak somewhere around those though).
- IM features:
[+] XMPP IM (RFC 6121): roster management (loading and pushes, with
versioning and caching)
-[+] XEP-0030: Service Discovery (replying to queries)
-[+] XEP-0115: Entity Capabilities (including into initial presence)
-[ ] XEP-0166: Jingle
-[ ] XEP-0234: Jingle File Transfer
-[ ] XEP-0261: Jingle In-Band Bytestreams Transport Method
-[ ] XEP-0363: HTTP File Upload
-[ ] XEP-0260: Jingle SOCKS5 Bytestreams Transport Method
-[ ] XEP-0391: Jingle Encrypted Transports
+[+] XEP-0030 v2.5: Service Discovery (replying to queries, recursively
+ searching for features)
+[+] XEP-0115 v1.5: Entity Capabilities (including into initial presence)
+[+] XEP-0092 v1.1: Software Version
+[+] XEP-0172 v1.1: User Nickname
+[+] XEP-0373 v0.6: OpenPGP for XMPP
+[+] XEP-0402 v1.1: PEP Native Bookmarks
+[+] XEP-0410 v1.1: MUC Self-Ping
+[+] XEP-0363 v1.0: HTTP File Upload (when built with curl)
+[+] XEP-0166 v1.1: Jingle
+[+] XEP-0234 v0.19: Jingle File Transfer (sending and accepting, but
+ no requests and no ranged transfers)
+[+] XEP-0261 v1.0: Jingle In-Band Bytestreams Transport Method
+[+] XEP-0167 v1.2: Jingle RTP Sessions (uses PortAudio and Opus)
+[+] XEP-0176 v1.1: Jingle ICE-UDP Transport Method (uses libnice,
+ discovers TURN/STUN servers with XEP-0215: External Service
+ Discovery)
+[+] XEP-0320 v1.0: Use of DTLS-SRTP in Jingle Sessions
+[ ] XEP-0260: Jingle SOCKS5 Bytestreams Transport Method?
+[ ] XEP-0391: Jingle Encrypted Transports?
[ ] XEP-0184: Message Delivery Receipts?
-[ ] OpenPGP/OTR/OMEMO/MLS encryption?
+[ ] OTR/OMEMO/MLS encryption?
- Additional state tracking:
-[ ] XMPP IM (RFC 6121): track presences of contacts.
+[+] XMPP IM (RFC 6121): track presences of contacts.
+[+] XEP-0163 v1.2: Personal Eventing Protocol: track contacts'
+ published items.
+[+] IQ response caching.
[ ] XEP-0030: Service Discovery: track features provided by known
- entities.
+ entities?
[ ] XEP-0115: Entity Capabilities: maintain a capability database,
- track capabilities of known entities.
-[ ] XEP-0045: Multi-User Chat: tracking of related states/presences.
+ track capabilities of known entities?
+[ ] XEP-0045: Multi-User Chat: tracking of related states/presences?
+
+- Various utility functions:
-- Various utility functions?
+[+] Display name establishment.
+[+] A console module.
+[+] XEP-0060 v1.19: Publish-Subscribe: helper functions.
- Examples and application:
[+] Basic usage example.
[.] WeeChat plugin.
+[+] Emacs mode (and an XML-based interface). See emacs/README.
diff --git a/configure.ac b/configure.ac
index 942c7a3..bb25c33 100644
--- a/configure.ac
+++ b/configure.ac
@@ -6,31 +6,153 @@ AC_INIT([rexmpp], [0.0.0], [defanor@uberspace.net])
AM_INIT_AUTOMAKE([-Werror -Wall])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_SRCDIR([src/rexmpp.c])
-AC_CONFIG_HEADERS([config.h])
-AC_CONFIG_FILES([Makefile src/Makefile rexmpp.pc Doxyfile])
+AC_CONFIG_HEADERS([src/config.h])
+AC_CONFIG_FILES([Makefile src/Makefile tests/Makefile rexmpp.pc Doxyfile])
# Checks for programs.
AC_PROG_CC
AM_PROG_AR
+
+# Checks for libraries and related parameters.
+
+AC_ARG_WITH([rust],
+ AS_HELP_STRING([--with-rust],
+ [use available Rust implementations of modules]))
+AC_ARG_WITH([expat],
+ AS_HELP_STRING([--with-expat], [use libexpat instead of libxml2]))
+
+AS_IF([test "x$with_rust" == "xyes"],
+ [AC_PATH_PROG([RUSTC], [rustc], [notfound])
+ AS_IF([test "x$RUSTC" == "xnotfound"], [AC_MSG_ERROR([rustc is required])])
+ AC_PATH_PROG([CARGO], [cargo], [notfound])
+ AS_IF([test "x$CARGO" == "xnotfound"], [AC_MSG_ERROR([cargo is required])])
+ AC_DEFINE([USE_RUST], [1], [Use Rust sources over C ones])],
+ [AS_IF([test "x$with_expat" == "xyes"],
+ [PKG_CHECK_MODULES([EXPAT], [expat],
+ [AC_DEFINE([USE_EXPAT], [1], [Use libexpat])])],
+ [PKG_CHECK_MODULES([LIBXML2], [libxml-2.0],
+ [AC_DEFINE([USE_LIBXML2], [1], [Use libxml2])])])])
+AM_CONDITIONAL([USE_RUST], [test "x$with_rust" == "xyes"])
+
LT_INIT
-# Checks for libraries.
-PKG_CHECK_MODULES([LIBXML], [libxml-2.0])
-AC_SUBST(LIBXML_CFLAGS)
-AC_SUBST(LIBXML_LIBS)
-PKG_CHECK_MODULES([GNUTLS], [gnutls])
-AC_SUBST(GNUTLS_CFLAGS)
-AC_SUBST(GNUTLS_LIBS)
+# Cryptographic libraries, for hashing
+
+AC_ARG_WITH([gcrypt],
+ AS_HELP_STRING([--without-gcrypt], [do not use gcrypt]))
+AC_ARG_WITH([nettle],
+ AS_HELP_STRING([--without-nettle], [do not use nettle]))
+
+AS_IF([test "x$with_gcrypt" != "xno"],
+ [AM_PATH_LIBGCRYPT([],
+ [AC_DEFINE([HAVE_GCRYPT], [1], [Libgcrypt is available])])])
+
+AS_IF([test "x$with_gcrypt" == "xno" -a "x$with_nettle" != "xno"],
+ [PKG_CHECK_MODULES([NETTLE], [nettle],
+ [AC_DEFINE([HAVE_NETTLE], [1], [Libnettle is available])])])
+
+AS_IF([test "x$with_gcrypt" == "xno" -a "x$with_nettle" == "xno"],
+ [PKG_CHECK_MODULES([OPENSSL], [openssl],
+ [AC_DEFINE([HAVE_OPENSSL], [1], [OpenSSL is available])])])
+
+
+# libnice (+ glib) and libsrtp for media calls, optional
+
+AC_ARG_ENABLE([calls], AS_HELP_STRING([--disable-calls],
+ [build without Jingle media call support]))
+AC_ARG_WITH([opus],
+ AS_HELP_STRING([--without-opus], [Do not use libopus]))
+AS_IF([test "x$enable_calls" != "xno"],
+ [PKG_CHECK_MODULES([NICE], [nice],
+ [AC_DEFINE([HAVE_NICE], [1], [libnice is available])])
+ PKG_CHECK_MODULES([GLIB], [glib-2.0],
+ [AC_DEFINE([HAVE_GLIB], [1], [glib is available])])
+ PKG_CHECK_MODULES([SRTP], [libsrtp2],
+ [AC_DEFINE([HAVE_SRTP], [1], [libsrtp2 is available])])
+ PKG_CHECK_MODULES([PORTAUDIO], [portaudio-2.0],
+ [AC_DEFINE([HAVE_PORTAUDIO], [1], [portaudio-2.0 is available])])
+ AS_IF([test "x$with_opus" != "xno"],
+ PKG_CHECK_MODULES([OPUS], [opus],
+ [AC_DEFINE([HAVE_OPUS], [1], [libopus is available])]))
+ AC_DEFINE([ENABLE_CALLS], [1], [Jingle ICE-UDP DTLS-SRTP calls are enabled])])
+
+# GSASL, optional
+
+AC_ARG_WITH([gsasl],
+ AS_HELP_STRING([--without-gsasl], [Don't use libgsasl for SASL]))
+AS_IF([test "x$with_gsasl" != "xno"],
+ [PKG_CHECK_MODULES([GSASL], [libgsasl], [AC_DEFINE([HAVE_GSASL], [1], [GSASL is available])])])
+
+# DNS: libunbound or c-ares, optional
+
+AC_ARG_VAR([DNSSEC_TRUST_ANCHOR_FILE],
+ [A DNSSEC trust anchor, containing DNSKEY in zone file format])
+AS_IF([test "x$DNSSEC_TRUST_ANCHOR_FILE" == "x"],
+ [DNSSEC_TRUST_ANCHOR_FILE="/usr/share/dns/root.key"])
+AC_DEFINE_UNQUOTED([DNSSEC_TRUST_ANCHOR_FILE],
+ "$DNSSEC_TRUST_ANCHOR_FILE",
+ [A DNSSEC trust anchor, containing DNSKEY in zone file format])
+
+AC_ARG_WITH([unbound],
+ AS_HELP_STRING([--without-unbound], [don't use libunbound]))
+AC_ARG_WITH([cares],
+ AS_HELP_STRING([--with-cares], [use c-ares]))
+
+AS_IF([test "x$with_unbound" != "xno"],
+ [PKG_CHECK_MODULES([UNBOUND], [libunbound],
+ [AC_DEFINE([USE_UNBOUND], [1], [Use libunbound])])])
+AS_IF([test "x$with_cares" == "xyes"],
+ [PKG_CHECK_MODULES([CARES], [libcares],
+ [AC_DEFINE([USE_CARES], [1], [Use c-ares])])])
+
+# GPGME, optional
+
+AC_ARG_WITH([gpgme],
+ AS_HELP_STRING([--without-gpgme], [Don't use GPGME for OpenPGP]))
+AS_IF([test "x$with_gpgme" != "xno"],
+ [AM_PATH_GPGME([], [AC_DEFINE([HAVE_GPGME], [1], [GPGME is available])])])
+
+# TLS: GnuTLS, OpenSSL, or none
+
+AC_ARG_ENABLE([tls], AS_HELP_STRING([--disable-tls], [build without TLS support]))
+AC_ARG_WITH([openssl],
+ AS_HELP_STRING([--with-openssl], [use OpenSSL]))
+AC_ARG_WITH([gnutls],
+ AS_HELP_STRING([--with-gnutls], [use GnuTLS]))
+
+AS_IF([test "x$with_gnutls" == "xyes"],
+ [PKG_CHECK_MODULES([GNUTLS], [gnutls],
+ [PKG_CHECK_MODULES([LIBDANE], [gnutls-dane],
+ [AC_DEFINE([USE_GNUTLS], [1], [Use GnuTLS])])])],
+
+ [test "x$with_openssl" == "xyes"],
+ [PKG_CHECK_MODULES([OPENSSL], [openssl],
+ [AC_DEFINE([USE_OPENSSL], [1], [Use OpenSSL])])],
+
+ [test "x$enable_tls" != "xno"],
+ [PKG_CHECK_MODULES([GNUTLS], [gnutls],
+ [PKG_CHECK_MODULES([LIBDANE], [gnutls-dane],
+ [AC_DEFINE([USE_GNUTLS], [1], [Use GnuTLS])],
+ [PKG_CHECK_MODULES([OPENSSL], [openssl],
+ [AC_DEFINE([USE_OPENSSL], [1], [Use OpenSSL])])])])])
+
+# ICU, optional
+
+AC_ARG_WITH([icu],
+ AS_HELP_STRING([--without-icu], [don't use ICU for JID checks]))
+AS_IF([test "x$with_icu" != "xno"],
+ [PKG_CHECK_MODULES([ICU_I18N], [icu-i18n],
+ AC_DEFINE([HAVE_ICU], [1], [icu-i18n is available]))])
-PKG_CHECK_MODULES([GSASL], [libgsasl])
-AC_SUBST(GSASL_CFLAGS)
-AC_SUBST(GSASL_LIBS)
+# curl, optional
-PKG_CHECK_MODULES([CARES], [libcares])
-AC_SUBST(CARES_CFLAGS)
-AC_SUBST(CARES_LIBS)
+AC_ARG_WITH([curl],
+ AS_HELP_STRING([--without-curl], [don't use curl for HTTP file upload]))
+AS_IF([test "x$with_curl" != "xno"],
+ [PKG_CHECK_MODULES([CURL], [libcurl],
+ AC_DEFINE([HAVE_CURL], [1], [curl is available]))])
# Checks for header files.
diff --git a/doap.xml b/doap.xml
new file mode 100644
index 0000000..c05815d
--- /dev/null
+++ b/doap.xml
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="utf-8"?>
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:xmpp='https://linkmauve.fr/ns/xmpp-doap#'
+ xmlns="http://usefulinc.com/ns/doap#">
+ <Project xml:lang='en'>
+ <name>rexmpp</name>
+ <shortdesc>A reusable XMPP IM client library</shortdesc>
+ <programming-language>C</programming-language>
+ <language>en</language>
+ <license>MIT</license>
+ <repository>
+ <GitRepository>
+ <browse rdf:resource="https://git.uberspace.net/rexmpp/" />
+ <location rdf:resource="https://git.uberspace.net/rexmpp/" />
+ </GitRepository>
+ </repository>
+ <os>Linux</os>
+ <category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-library" />
+ <maintainer rdf:resource="https://defanor.uberspace.net/about.xhtml#me" />
+
+ <!-- XMPP core -->
+ <implements rdf:resource="https://xmpp.org/rfcs/rfc6120.html" />
+ <!-- XMPP IM -->
+ <implements rdf:resource="https://xmpp.org/rfcs/rfc6121.html" />
+ <!--XMPP address format -->
+ <implements rdf:resource="https://datatracker.ietf.org/doc/html/rfc7622" />
+ <!-- SOCKS5 -->
+ <implements rdf:resource="https://datatracker.ietf.org/doc/html/rfc1928" />
+ <!-- Happy Eyeballs -->
+ <implements rdf:resource="https://datatracker.ietf.org/doc/html/rfc8305" />
+
+ <!-- todo: maybe add DNSSEC, DANE RFCs -->
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0198.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.6</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0280.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>0.13</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0368.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.1</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0199.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>2.0</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0030.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>2.5</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0115.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.5</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0092.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.1</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0172.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.1</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0373.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>0.6</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0402.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.1</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0410.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.1</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0363.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.0</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0166.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.1</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0234.html"/>
+ <xmpp:status>partial</xmpp:status>
+ <xmpp:note>sending and accepting, but no requests and no
+ ranged transfers</xmpp:note>
+ <xmpp:version>0.19</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0261.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.0</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0167.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.2</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0176.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.1</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0320.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.0</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0163.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.2</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ <xmpp:note>tracks contacts' published items</xmpp:note>
+ </xmpp:SupportedXep>
+ </implements>
+
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0060.html"/>
+ <xmpp:status>partial</xmpp:status>
+ <xmpp:version>1.2</xmpp:version>
+ <xmpp:since>0.0</xmpp:since>
+ <xmpp:note>helper functions</xmpp:note>
+ </xmpp:SupportedXep>
+ </implements>
+
+ </Project>
+</rdf:RDF>
diff --git a/emacs/README b/emacs/README
new file mode 100644
index 0000000..efa192c
--- /dev/null
+++ b/emacs/README
@@ -0,0 +1,24 @@
+xmpp.el - an Emacs interface to rexmpp
+
+Since Emacs can't poll sockets and use rexmpp directly, this works by
+introducing a program which basically serializes library calls and
+callbacks into XML, and which xmpp.el runs as a subprocess.
+
+Once rexmpp_xml_interface is built, and possibly the path to it is
+adjusted in the xmpp-command variable, one can load xmpp.el, set a
+password in ~/.authinfo (or elsewhere auth-source will be able to read
+it from, with "port xmpp"), perhaps enable tracking-mode, and run M-x
+xmpp RET <JID> RET.
+
+The buffers it creates are an XML console, a text console (using
+rexmpp's console module, type "help" to see the available commands), a
+process buffer, a log buffer. The xmpp-query function (or just
+incoming messages) will create query buffers for one-to-one chats,
+xmpp-muc-join creates MUC ones.
+
+The used XML interface will probably be adjusted, and there's still a
+lot to add or improve in xmpp.el, but it is fairly usable (i.e.,
+replaces bitlbee + rcirc for me) since September 2021.
+
+Possibly in the future it will work with other libraries as well,
+and/or will be moved out of the rexmpp's repository.
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;
+}
diff --git a/emacs/xmpp.el b/emacs/xmpp.el
new file mode 100644
index 0000000..ebe6911
--- /dev/null
+++ b/emacs/xmpp.el
@@ -0,0 +1,701 @@
+;;; xmpp.el --- an XMPP client -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021 defanor
+
+;; Author: defanor <defanor@uberspace.net>
+;; Maintainer: defanor <defanor@uberspace.net>
+;; Created: 2021-02-24
+;; Keywords: xmpp, rexmpp
+;; Homepage: https://git.uberspace.net/rexmpp/
+;; Version: 0.0.0
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; See rexmpp's xml_interface.c. Possibly it'll also work with other
+;; libraries later.
+
+;; This is even less polished than the library.
+
+;;; Code:
+
+(require 'xml)
+(require 'seq)
+(require 'tracking)
+(require 'auth-source)
+
+(defgroup xmpp nil
+ "An Emacs interface to rexmpp."
+ :prefix "xmpp-"
+ :group 'applications)
+
+(defface xmpp-timestamp
+ '((((type graphic) (class color) (background dark)) :foreground "SteelBlue")
+ (((type graphic) (class color) (background light)) :foreground "SteelBlue"))
+ "Timestamp face."
+ :group 'xmpp)
+
+(defface xmpp-my-nick
+ '((((type graphic) (class color) (background dark)) :foreground "LightSkyBlue")
+ (((type graphic) (class color) (background light)) :foreground "Blue")
+ (t :weight bold))
+ "Own nick face."
+ :group 'xmpp)
+
+(defface xmpp-other-nick
+ '((((type graphic) (class color) (background dark)) :foreground "PaleGreen")
+ (((type graphic) (class color) (background light)) :foreground "DarkGreen")
+ (t :weight bold))
+ "Others' nick face."
+ :group 'xmpp)
+
+(defface xmpp-presence
+ '((((type graphic) (class color) (background dark)) :foreground "wheat1")
+ (((type graphic) (class color) (background light)) :foreground "wheat4"))
+ "Presence notification face."
+ :group 'xmpp)
+
+(defface xmpp-action
+ '((((type graphic) (class color) (background dark)) :foreground "thistle1")
+ (((type graphic) (class color) (background light)) :foreground "thistle4"))
+ "Action (/me) face."
+ :group 'xmpp)
+
+
+(defvar xmpp-command "rexmpp_xml_interface"
+ "A command to run an XMPP client subprocess.")
+
+(defvar xmpp-timestamp-format "%H:%M"
+ "Time string format to use in query buffers.")
+
+(defvar xmpp-proc nil
+ "XMPP process buffer. This should be defined for all the
+ XMPP-related buffers.")
+(make-variable-buffer-local 'xmpp-proc)
+
+(defvar xmpp-jid nil
+ "User JID related to a current XMPP-related buffer.")
+(make-variable-buffer-local 'xmpp-jid)
+
+(defvar xmpp-query-buffers nil
+ "An association list of query buffers corresponding to JIDs.")
+(make-variable-buffer-local 'xmpp-query-buffers)
+
+(defvar xmpp-muc-buffers nil
+ "An association list of MUC buffers corresponding to conference
+ JIDs.")
+(make-variable-buffer-local 'xmpp-muc-buffers)
+
+(defvar xmpp-log-buffer nil
+ "An XMPP log buffer.")
+(make-variable-buffer-local 'xmpp-log-buffer)
+
+(defvar xmpp-console-buffer nil
+ "An XMPP text console buffer.")
+(make-variable-buffer-local 'xmpp-console-buffer)
+
+(defvar xmpp-xml-buffer nil
+ "An XMPP XML console buffer.")
+(make-variable-buffer-local 'xmpp-xml-buffer)
+
+(defvar xmpp-active-requests nil
+ "Active requests for a subprocess.")
+(make-variable-buffer-local 'xmpp-active-requests)
+
+(defvar xmpp-truncate-buffer-at 100000
+ "The buffer size at which to truncate an XMPP-related buffer by
+ approximately halving it.")
+
+(defun xmpp-timestamp-string (&optional time)
+ (let ((str (format-time-string xmpp-timestamp-format time)))
+ (add-face-text-property 0 (length str) 'xmpp-timestamp nil str)
+ str))
+
+(defun xmpp-activity-notify ()
+ (tracking-add-buffer (current-buffer)))
+
+(defun xmpp-jid-to-bare (jid)
+ (let* ((jid-list (reverse (string-to-list jid)))
+ (resource-pos (seq-position jid-list ?/)))
+ (if resource-pos
+ (concat (reverse (seq-drop jid-list (1+ resource-pos))))
+ jid)))
+
+(defun xmpp-jid-localpart (jid)
+ (let* ((jid-list (string-to-list jid))
+ (at-pos (seq-position jid-list ?@)))
+ (when at-pos
+ (concat (seq-take jid-list at-pos)))))
+
+(defun xmpp-jid-resource (jid)
+ (let* ((jid-list (reverse (string-to-list jid)))
+ (resource-pos (seq-position jid-list ?/)))
+ (if resource-pos
+ (concat (reverse (seq-take jid-list resource-pos)))
+ jid)))
+
+
+(defun xmpp-gen-id ()
+ (number-to-string (random)))
+
+(defun xmpp-xml-set-xmlns (node &optional parent-ns)
+ "Propagates xmlns to child elements. This is a temporary hack
+to keep using xml.el without proper namespace parsing, so that
+its printing--which doesn't handle namespaces--can be used too."
+ (if (listp node)
+ (let* ((xmlns (xml-get-attribute-or-nil node 'xmlns))
+ (ns (or xmlns parent-ns))
+ (attrs (xml-node-attributes node)))
+ (cons (xml-node-name node)
+ (cons (if xmlns
+ attrs
+ (if ns
+ (cons (cons 'xmlns ns) attrs)
+ attrs))
+ (mapcar (lambda (x) (xmpp-xml-set-xmlns x ns)) (xml-node-children node)))))
+ node))
+
+(defun xmpp-xml-parse-region (&optional beg end buffer)
+ (mapcar 'xmpp-xml-set-xmlns
+ (xml-parse-region beg end buffer)))
+
+(defun xmpp-xml-match (xml name ns)
+ (and (consp xml)
+ (eq (xml-node-name xml) name)
+ (equal (xml-get-attribute-or-nil xml 'xmlns) ns)))
+
+(defun xmpp-xml-child (xml name &optional ns)
+ (seq-find (lambda (x) (xmpp-xml-match x name ns)) xml))
+
+(defun xmpp-proc-write (xml &optional proc)
+ (let ((cur-proc (or proc xmpp-proc)))
+ (with-temp-buffer
+ (xml-print xml)
+ (insert "\n")
+ (process-send-region cur-proc (point-min) (point-max)))))
+
+(defun xmpp-with-message-body (proc message-xml func)
+ (let* ((message-contents (xml-node-children message-xml))
+ (message-body (xmpp-xml-child message-contents 'body "jabber:client"))
+ (message-openpgp (xmpp-xml-child message-contents 'openpgp "urn:xmpp:openpgp:0")))
+ (if message-openpgp
+ ;; TODO: check validation results.
+ (xmpp-request `(openpgp-decrypt-message nil ,message-xml)
+ (lambda (response)
+ (let* ((payload (xmpp-xml-child response 'payload "urn:xmpp:openpgp:0")))
+ (funcall func (car (xml-node-children payload)))))
+ proc)
+ (funcall func message-body))))
+
+(defun xmpp-message-string (str)
+ (if (string-prefix-p "/me " str)
+ (let ((action (substring str 3)))
+ (add-face-text-property
+ 0
+ (length action)
+ 'xmpp-action
+ nil
+ action)
+ action)
+ (concat ": " str)))
+
+(defun xmpp-process-input (proc xml)
+ (with-current-buffer (process-buffer proc)
+ (with-current-buffer xmpp-xml-buffer
+ (xmpp-insert (format "<!-- server, %s -->\n" (current-time-string)))
+ (xmpp-insert-xml (list xml))
+ (xmpp-insert "\n"))
+ (when (xmpp-xml-match xml 'presence "jabber:client")
+ (let* ((presence-from (xml-get-attribute-or-nil xml 'from))
+ (presence-type (or (xml-get-attribute-or-nil xml 'type) "available"))
+ (presence-show (car (xml-node-children (xmpp-xml-child xml 'show "jabber:client"))))
+ (presence-status (car (xml-node-children (xmpp-xml-child xml 'status "jabber:client"))))
+ (presence-string
+ (concat
+ presence-from " is "
+ presence-type
+ (when presence-show
+ (concat " (" presence-show ")"))
+ (when presence-status
+ (concat ": " presence-status))))
+ (bare-jid (xmpp-jid-to-bare presence-from))
+ (resourcepart (xmpp-jid-resource presence-from)))
+ (add-face-text-property
+ 0
+ (length presence-string)
+ 'xmpp-presence
+ nil
+ presence-string)
+ (when (assoc bare-jid xmpp-query-buffers)
+ (with-current-buffer (cdr (assoc bare-jid xmpp-query-buffers))
+ (xmpp-insert (concat
+ (xmpp-timestamp-string) ", "
+ presence-string "\n"))))
+ (when (assoc bare-jid xmpp-muc-buffers)
+ (with-current-buffer (cdr (assoc bare-jid xmpp-muc-buffers))
+ (xmpp-insert
+ (concat (xmpp-timestamp-string) ", "
+ presence-string "\n")))))))
+ (when (xmpp-xml-match xml 'message "jabber:client")
+ (let* ((carbons-sent (xmpp-xml-child xml 'sent "urn:xmpp:carbons:2"))
+ (carbons-received (xmpp-xml-child xml 'received "urn:xmpp:carbons:2"))
+ (carbons-forwarded (xmpp-xml-child (or carbons-sent carbons-received)
+ 'forwarded "urn:xmpp:forward:0"))
+ (carbons-message (xmpp-xml-child carbons-forwarded 'message "jabber:client"))
+ (message-xml (or carbons-message xml))
+ (message-from (xml-get-attribute-or-nil message-xml 'from))
+ (message-delay (xmpp-xml-child message-xml 'delay "urn:xmpp:delay"))
+ (message-time (if message-delay
+ (encode-time
+ (iso8601-parse
+ (xml-get-attribute-or-nil message-delay 'stamp)))
+ (current-time)))
+ (chat-with (cond (carbons-sent (xml-get-attribute-or-nil message-xml 'to))
+ (t message-from))))
+ (xmpp-with-message-body
+ proc message-xml
+ (lambda (message-body)
+ (when message-body
+ (let ((message-str
+ (xmpp-message-string (car (xml-node-children message-body)))))
+ (xmpp-with-name
+ message-from
+ (lambda (message-from-name)
+ (pcase (xml-get-attribute-or-nil xml 'type)
+ ("chat"
+ (with-current-buffer (xmpp-query chat-with proc)
+ (add-face-text-property
+ 0
+ (length message-from-name)
+ (if (equal (with-current-buffer (process-buffer xmpp-proc) xmpp-jid)
+ (xmpp-jid-to-bare chat-with))
+ 'xmpp-my-nick
+ 'xmpp-other-nick)
+ nil
+ message-from-name)
+ (xmpp-insert
+ (concat (xmpp-timestamp-string message-time) ", "
+ message-from-name
+ message-str "\n"))
+ (xmpp-activity-notify)))
+ ("groupchat"
+ (with-current-buffer (xmpp-muc-buffer chat-with proc)
+ (let ((from-nick (xmpp-jid-resource message-from)))
+ (add-face-text-property
+ 0
+ (length from-nick)
+ (if (equal xmpp-muc-my-occupant-jid message-from)
+ 'xmpp-my-nick
+ 'xmpp-other-nick)
+ nil
+ from-nick)
+ (xmpp-insert
+ (concat (xmpp-timestamp-string message-time) ", "
+ from-nick
+ message-str "\n"))
+ (xmpp-activity-notify))))))))))))))
+
+(defun xmpp-set-from (proc xml)
+ (let* ((name (xml-node-name xml))
+ (attr (xml-node-attributes xml))
+ (children (xml-node-children xml))
+ (new-attr (if (assoc 'from attr)
+ attr
+ (cons (cons 'from
+ (with-current-buffer
+ (process-buffer proc)
+ xmpp-jid))
+ attr))))
+ (cons name (cons new-attr children))))
+
+(defun xmpp-process-output (proc xml)
+ (with-current-buffer (process-buffer proc)
+ (with-current-buffer xmpp-xml-buffer
+ (xmpp-insert (format "<!-- client, %s -->\n" (current-time-string)))
+ (xmpp-insert-xml (list xml))
+ (xmpp-insert "\n")))
+ (when (xmpp-xml-match xml 'message "jabber:client")
+ (xmpp-with-message-body
+ ;; The "from" attribute is needed for validation.
+ proc (xmpp-set-from proc xml)
+ (lambda (message-body)
+ (xmpp-with-name
+ xmpp-jid
+ (lambda (my-name)
+ (add-face-text-property 0 (length my-name) 'xmpp-my-nick nil my-name)
+ (let ((message-to (xml-get-attribute-or-nil xml 'to)))
+ (pcase (xml-get-attribute-or-nil xml 'type)
+ ("chat"
+ (when message-body
+ (let ((buf (xmpp-query message-to proc)))
+ (when buf
+ (with-current-buffer buf
+ (xmpp-insert
+ (concat
+ (xmpp-timestamp-string) ", "
+ my-name
+ (xmpp-message-string
+ (car (xml-node-children message-body)))
+ "\n")))))))
+ ("groupchat" nil))))))))
+ (when (and (xmpp-xml-match xml 'presence "jabber:client")
+ (or (not (xml-get-attribute-or-nil xml 'type))
+ (equal (xml-get-attribute-or-nil xml 'type) "available"))
+ (xmpp-xml-child xml 'x "http://jabber.org/protocol/muc"))
+ ;; Joining a MUC
+ (let* ((occupant-jid (xml-get-attribute xml 'to))
+ (muc-jid (xmpp-jid-to-bare occupant-jid))
+ (buf (xmpp-muc-buffer muc-jid proc)))
+ (with-current-buffer buf
+ (setq-local xmpp-muc-my-occupant-jid occupant-jid)))))
+
+(defun xmpp-process (proc xml)
+ (let* ((buf (process-buffer proc))
+ (log-buf (with-current-buffer buf xmpp-log-buffer))
+ (console-buf (with-current-buffer buf xmpp-console-buffer))
+ (my-jid (with-current-buffer buf xmpp-jid))
+ (xml-elem (car xml)))
+ (pcase (xml-node-name xml-elem)
+ ('request
+ (let ((rid (xml-get-attribute xml-elem 'id)))
+ (pcase (car (xml-node-children xml-elem))
+ (`(sasl ((property . ,prop)))
+ (let ((resp
+ (if (equal prop "password")
+ (let ((secret
+ (plist-get
+ (car
+ (auth-source-search
+ :max 1
+ :user my-jid
+ :port "xmpp"
+ :require '(:user :secret))) :secret)))
+ (if (functionp secret)
+ (funcall secret)
+ secret))
+ (read-passwd
+ (concat "SASL " prop ": ")))))
+ (xmpp-proc-write `((response ((id . ,rid)) ,resp))
+ proc)))
+ (`(xml-in nil ,xml-in)
+ (progn (xmpp-process-input proc xml-in)
+ (xmpp-proc-write `((response ((id . ,rid)) "0")) proc)))
+ (`(xml-out nil ,xml-out)
+ (progn (xmpp-process-output proc xml-out)
+ (xmpp-proc-write `((response ((id . ,rid)) "0")) proc))))))
+ ('log
+ (with-current-buffer log-buf
+ (goto-char (point-max))
+ (insert (format "%s [%s] %s\n" (current-time-string)
+ (xml-get-attribute xml-elem 'priority)
+ (car (xml-node-children xml-elem))))))
+ ('console
+ (with-current-buffer console-buf
+ (xmpp-insert (car (xml-node-children xml-elem)))))
+ ('response
+ (with-current-buffer buf
+ (let* ((rid (xml-get-attribute xml-elem 'id))
+ (cb (alist-get rid xmpp-active-requests nil nil 'string-equal)))
+ (setq xmpp-active-requests
+ (assoc-delete-all rid xmpp-active-requests))
+ (when cb
+ (funcall cb (car (xml-node-children xml-elem))))))))))
+
+(defun xmpp-request (req cb &optional proc)
+ (let ((cur-proc (or proc xmpp-proc))
+ (req-id (xmpp-gen-id)))
+ (with-current-buffer (process-buffer cur-proc)
+ (xmpp-proc-write `((request ((id . ,req-id)) ,req)) cur-proc)
+ (push (cons req-id cb) xmpp-active-requests))))
+
+(defun xmpp-with-name (jid cb &optional proc)
+ (let ((cur-proc (or proc xmpp-proc))
+ (bare-jid (xmpp-jid-to-bare jid)))
+ (with-current-buffer (process-buffer cur-proc)
+ ;; Use resource for MUC private messages, determine a nick
+ ;; otherwise.
+ (if (assoc bare-jid xmpp-muc-buffers)
+ (funcall cb (xmpp-jid-resource jid))
+ (xmpp-request `(get-name nil ,jid) cb proc)))))
+
+(defun xmpp-http-upload (path &optional proc)
+ (interactive "fFile path: ")
+ (xmpp-request
+ `(http-upload nil ,path)
+ (lambda (url)
+ (kill-new url)
+ (message "Uploaded the file to %s" url))
+ proc))
+
+(defun xmpp-stop (&optional proc)
+ (interactive)
+ (xmpp-request '(stop) nil proc))
+
+(defun xmpp-kill-buffers (&optional proc)
+ (interactive)
+ (when (and xmpp-log-buffer
+ xmpp-console-buffer
+ xmpp-xml-buffer)
+ (mapcar (lambda (b) (kill-buffer (cdr b))) xmpp-query-buffers)
+ (mapcar (lambda (b) (kill-buffer (cdr b))) xmpp-muc-buffers)
+ (kill-buffer xmpp-log-buffer)
+ (kill-buffer xmpp-console-buffer)
+ (kill-buffer xmpp-xml-buffer)
+ (kill-buffer)))
+
+(defun xmpp-send (xml &optional proc)
+ (xmpp-request `(send nil ,xml) nil proc))
+
+(defun xmpp-filter (proc str)
+ (when (buffer-live-p (process-buffer proc))
+ (with-current-buffer (process-buffer proc)
+ (save-excursion
+ (goto-char (point-max))
+ (insert str)
+ (goto-char (point-min))
+ (let ((zero (search-forward "\0" nil t)))
+ (while zero
+ (let ((xml (xmpp-xml-parse-region (point-min) (1- zero))))
+ (xmpp-process proc xml)
+ (delete-region (point-min) zero)
+ (setq zero (search-forward "\0" nil t)))))))))
+
+;;;###autoload
+(defun xmpp (jid)
+ "Initiates a new XMPP session."
+ (interactive "sJID: ")
+ (let* ((bare-jid (xmpp-jid-to-bare jid))
+ (proc-buf (generate-new-buffer
+ (concat "*xmpp:" bare-jid " process*"))))
+ (with-current-buffer proc-buf
+ (setq-local xmpp-jid bare-jid)
+ (setq-local xmpp-active-requests nil)
+ (setq-local xmpp-query-buffers '())
+ (setq-local xmpp-muc-buffers '())
+ (setq-local xmpp-log-buffer
+ (generate-new-buffer
+ (concat "*xmpp:" bare-jid " log*")))
+ (setq-local xmpp-console-buffer
+ (generate-new-buffer
+ (concat "*xmpp:" bare-jid " text console*")))
+ (setq-local xmpp-xml-buffer
+ (generate-new-buffer
+ (concat "*xmpp:" bare-jid " XML console*")))
+ (with-current-buffer xmpp-console-buffer
+ (xmpp-console-mode))
+ (with-current-buffer xmpp-xml-buffer
+ (xmpp-xml-mode))
+ (setq-local xmpp-proc
+ (make-process :name "xmpp"
+ :command (list xmpp-command jid)
+ :buffer proc-buf
+ :filter 'xmpp-filter))
+ (let ((new-proc xmpp-proc))
+ (with-current-buffer xmpp-console-buffer
+ (setq-local xmpp-proc new-proc))
+ (with-current-buffer xmpp-xml-buffer
+ (setq-local xmpp-proc new-proc))
+ (with-current-buffer xmpp-log-buffer
+ (setq-local xmpp-proc new-proc))))))
+
+(defun xmpp-restart (&optional proc)
+ "Restarts an XMPP process."
+ (interactive)
+ (let* ((cur-proc (or proc xmpp-proc))
+ (proc-buf (process-buffer cur-proc)))
+ (when (and cur-proc (process-live-p cur-proc))
+ (xmpp-stop cur-proc))
+ (with-current-buffer proc-buf
+ (setq-local xmpp-active-requests nil)
+ (setq-local xmpp-proc
+ (make-process :name "xmpp"
+ :command (list xmpp-command xmpp-jid)
+ :buffer proc-buf
+ :filter 'xmpp-filter))
+ (let ((new-proc xmpp-proc))
+ (mapcar (lambda (b)
+ (with-current-buffer b (setq-local xmpp-proc new-proc)))
+ (append (list xmpp-console-buffer xmpp-xml-buffer xmpp-log-buffer)
+ (mapcar 'cdr xmpp-query-buffers)
+ (mapcar 'cdr xmpp-muc-buffers)))))))
+
+(defun xmpp-insert (args)
+ (save-excursion
+ (when (and xmpp-truncate-buffer-at
+ (> xmpp-prompt-start-marker xmpp-truncate-buffer-at))
+ (goto-char (/ xmpp-truncate-buffer-at 2))
+ (search-forward "\n")
+ (delete-region (point-min) (point)))
+ (goto-char xmpp-prompt-start-marker)
+ (funcall 'insert args)
+ (set-marker xmpp-prompt-start-marker (point))
+ (set-marker xmpp-prompt-end-marker (+ 2 (point)))))
+
+(defun xmpp-insert-xml (xml)
+ (save-excursion
+ (goto-char xmpp-prompt-start-marker)
+ (xml-print xml)
+ (setq-local xmpp-prompt-start-marker (point-marker))
+ (goto-char (+ 2 xmpp-prompt-start-marker))
+ (setq-local xmpp-prompt-end-marker (point-marker))))
+
+(defun xmpp-send-input ()
+ (interactive)
+ (let ((input (buffer-substring xmpp-prompt-end-marker (point-max))))
+ (unless (string-empty-p input)
+ (pcase major-mode
+ ('xmpp-query-mode (xmpp-send `(message ((xmlns . "jabber:client")
+ (id . ,(xmpp-gen-id))
+ (to . ,xmpp-jid)
+ (type . "chat"))
+ (body nil ,input))))
+ ('xmpp-muc-mode (xmpp-send `(message ((xmlns . "jabber:client")
+ (id . ,(xmpp-gen-id))
+ (to . ,xmpp-jid)
+ (type . "groupchat"))
+ (body nil ,input))))
+ ('xmpp-console-mode (xmpp-request `(console nil ,input) nil xmpp-proc))
+ ('xmpp-xml-mode
+ (mapcar 'xmpp-send (xmpp-xml-parse-region xmpp-prompt-end-marker (point-max))))))
+ (delete-region xmpp-prompt-end-marker (point-max))))
+
+
+(defvar xmpp-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "RET") 'xmpp-send-input)
+ map)
+ "Keymap for `xmpp-mode'.")
+
+(define-derived-mode xmpp-mode nil "XMPP"
+ "XMPP major mode."
+ (insert "> ")
+ (add-text-properties (point-min) (point-max)
+ '(field t read-only t rear-nonsticky t))
+ (setq-local xmpp-prompt-start-marker (point-min-marker))
+ (setq-local xmpp-prompt-end-marker (point-max-marker)))
+
+(define-derived-mode xmpp-query-mode xmpp-mode "XMPP-query"
+ "XMPP Query major mode.")
+
+(define-derived-mode xmpp-muc-mode xmpp-mode "XMPP-MUC"
+ "XMPP Query major mode.")
+
+(define-derived-mode xmpp-console-mode xmpp-mode "XMPP-text-console"
+ "XMPP Text Console major mode.")
+
+(define-derived-mode xmpp-xml-mode xmpp-mode "XMPP-XML-console"
+ "XMPP XML Console major mode.")
+
+
+(defun xmpp-query-buffer-on-close ()
+ (let ((query-jid xmpp-jid))
+ (when (buffer-live-p (process-buffer xmpp-proc))
+ (with-current-buffer (process-buffer xmpp-proc)
+ (setq xmpp-query-buffers
+ (seq-remove (lambda (x) (equal (car x) query-jid))
+ xmpp-query-buffers)))))
+ t)
+
+(defun xmpp-muc-buffer-on-close ()
+ (let ((muc-jid xmpp-jid))
+ (when (buffer-live-p (process-buffer xmpp-proc))
+ (with-current-buffer (process-buffer xmpp-proc)
+ (setq xmpp-muc-buffers
+ (seq-remove (lambda (x) (equal (car x) muc-jid))
+ xmpp-muc-buffers)))))
+ t)
+
+(defun xmpp-query (jid &optional proc)
+ (interactive "sQuery JID: ")
+ (let ((process (or proc xmpp-proc)))
+ (with-current-buffer (process-buffer process)
+ ;; Use full JID for MUC private messages, but a bare JID for
+ ;; regular chats.
+ (let* ((bare-jid (xmpp-jid-to-bare jid))
+ (target-jid (if (assoc bare-jid xmpp-muc-buffers)
+ jid
+ bare-jid))
+ (buf (if (assoc target-jid xmpp-query-buffers)
+ (cdr (assoc target-jid xmpp-query-buffers))
+ (let ((query-buf (generate-new-buffer
+ (concat "*xmpp:" target-jid "*"))))
+ (with-current-buffer query-buf
+ (xmpp-query-mode)
+ (setq-local xmpp-jid target-jid)
+ (setq-local xmpp-proc process)
+ (setq-local kill-buffer-query-functions
+ (cons #'xmpp-query-buffer-on-close
+ kill-buffer-query-functions)))
+ (push (cons target-jid query-buf) xmpp-query-buffers)
+ query-buf))))
+ (when (interactive-p)
+ (display-buffer buf))
+ buf))))
+
+(defun xmpp-muc-join (jid &optional nick proc)
+ (interactive "sConference JID: ")
+ (with-current-buffer (process-buffer (or proc xmpp-proc))
+ (let* ((bare-jid (xmpp-jid-to-bare jid))
+ (my-nick (or nick (xmpp-jid-localpart xmpp-jid)))
+ (full-jid (concat bare-jid "/" my-nick)))
+ (xmpp-send `(presence ((xmlns . "jabber:client")
+ (id . ,(xmpp-gen-id))
+ (to . ,full-jid))
+ (x ((xmlns . "http://jabber.org/protocol/muc")))))
+ (xmpp-request
+ `(muc-ping-set ((occupant-jid . ,full-jid)
+ (delay . "600"))
+ nil)
+ nil
+ proc))))
+
+(defun xmpp-muc-leave (jid &optional proc)
+ (interactive "sConference JID: ")
+ (with-current-buffer (process-buffer (or proc xmpp-proc))
+ (with-current-buffer (cdr (assoc jid xmpp-muc-buffers))
+ (xmpp-send `(presence ((xmlns . "jabber:client")
+ (id . ,(xmpp-gen-id))
+ (to . ,xmpp-muc-my-occupant-jid)
+ (type . "unavailable"))))
+ (xmpp-request
+ `(muc-ping-remove ((occupant-jid . ,xmpp-muc-my-occupant-jid))
+ nil)
+ nil))))
+
+(defun xmpp-muc-buffer (jid &optional proc)
+ (let* ((process (or proc xmpp-proc))
+ (bare-jid (xmpp-jid-to-bare jid)))
+ (with-current-buffer (process-buffer process)
+ (let ((buf (if (assoc bare-jid xmpp-muc-buffers)
+ (cdr (assoc bare-jid xmpp-muc-buffers))
+ (let ((muc-buf (generate-new-buffer (concat "*xmpp:" bare-jid "*"))))
+ (with-current-buffer muc-buf
+ (xmpp-muc-mode)
+ (setq-local xmpp-jid bare-jid)
+ (setq-local xmpp-proc process)
+ (setq-local kill-buffer-query-functions
+ (cons #'xmpp-muc-buffer-on-close
+ kill-buffer-query-functions)))
+ (push (cons bare-jid muc-buf) xmpp-muc-buffers)
+ muc-buf))))
+ (when (interactive-p)
+ (display-buffer buf))
+ buf))))
+
+(provide 'xmpp)
+
+;;; xmpp.el ends here
diff --git a/examples/basic.c b/examples/basic.c
index cbc9f4f..5df4f65 100644
--- a/examples/basic.c
+++ b/examples/basic.c
@@ -1,13 +1,30 @@
+/**
+ @file basic.c
+ @brief A reference rexmpp-based client.
+ @author defanor <defanor@uberspace.net>
+ @date 2020--2021
+ @copyright MIT license.
+*/
+
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <syslog.h>
-#include <gnutls/gnutls.h>
#include <gsasl.h>
-
+#include <time.h>
+#include <stdlib.h>
#include <rexmpp.h>
+#include <rexmpp_xml.h>
+#include <rexmpp_sasl.h>
+
+int log_level = 8;
+/* A logger callback. This one just prints all the logs into
+ stderr. */
void my_logger (rexmpp_t *s, int priority, const char *fmt, va_list args) {
+ if (priority >= log_level) {
+ return;
+ }
char *priority_str = "unknown";
switch (priority) {
case LOG_EMERG: priority_str = "emerg"; break;
@@ -24,82 +41,139 @@ void my_logger (rexmpp_t *s, int priority, const char *fmt, va_list args) {
fprintf(stderr, "\n");
}
-int my_sasl_property_cb (rexmpp_t *s, Gsasl_property prop) {
- if (prop == GSASL_PASSWORD) {
- char buf[4096];
+/* A SASL property callback, used to retrieve credentials. This one
+ just asks user for a password and provides AUTHID based on the
+ initial JID. */
+int my_sasl_property_cb (rexmpp_t *s, rexmpp_sasl_property prop) {
+ if (prop == REXMPP_SASL_PROP_PASSWORD) {
+ char *buf = NULL;
+ size_t buf_len = 4096;
printf("password: ");
- gets(buf);
- gsasl_property_set (s->sasl_session, GSASL_PASSWORD, buf);
- 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;
+ getline(&buf, &buf_len, stdin);
+ if (buf != NULL) {
+ if (buf[strlen(buf) - 1] == '\n') {
+ buf[strlen(buf) - 1] = '\0';
+ }
+ rexmpp_sasl_property_set (s, REXMPP_SASL_PROP_PASSWORD, buf);
+ free(buf);
}
+ return 0;
+ }
+ if (prop == REXMPP_SASL_PROP_AUTHID) {
+ rexmpp_sasl_property_set (s, REXMPP_SASL_PROP_AUTHID, s->initial_jid.local);
+ return 0;
}
- printf("unhandled gsasl property: %d\n", prop);
- return GSASL_NO_CALLBACK;
+ printf("unhandled SASL property: %d\n", prop);
+ return -1;
}
-int my_xml_in_cb (rexmpp_t *s, xmlNodePtr node) {
- char *xml_buf = rexmpp_xml_serialize(node);
+/* An XML in callback, printing what was received. */
+int my_xml_in_cb (rexmpp_t *s, rexmpp_xml_t *node) {
+ char *xml_buf = rexmpp_xml_serialize(node, 0);
printf("recv: %s\n", xml_buf);
free(xml_buf);
return 0;
}
-int my_xml_out_cb (rexmpp_t *s, xmlNodePtr node) {
- char *xml_buf = rexmpp_xml_serialize(node);
+/* An XML out callback, printing what is about to be sent. */
+int my_xml_out_cb (rexmpp_t *s, rexmpp_xml_t *node) {
+ char *xml_buf = rexmpp_xml_serialize(node, 0);
printf("send: %s\n", xml_buf);
free(xml_buf);
return 0;
}
-main (int argc, char **argv) {
+int my_console_print_cb (rexmpp_t *s, const char *fmt, va_list args) {
+ vprintf(fmt, args);
+ return 0;
+}
+
+/* void my_socket_options(rexmpp_t *s, int sock) { */
+/* int pmtudisc = IP_PMTUDISC_WANT; */
+/* setsockopt(sock, IPPROTO_IP, IP_MTU_DISCOVER, &pmtudisc, sizeof(pmtudisc)); */
+/* } */
+
+void print_help (char *prog_name) {
+ printf("Usage: %s [options] <jid>\n" \
+ "Options:\n" \
+ "-c\tenable textual console\n" \
+ "-x\tenable XML console\n" \
+ "-l <n>\tset log level (0 to disable, 8 to print everything)\n"
+ "-h\tprint this help message\n"
+ , prog_name);
+}
+
+int main (int argc, char **argv) {
+ int c, xml_console = 0, txt_console = 0, log = 0;
+ if (argc < 2) {
+ print_help(argv[0]);
+ return -1;
+ }
+ while ((c = getopt (argc, argv, "xchl:")) != -1) {
+ if (c == 'x') {
+ xml_console = 1;
+ } else if (c == 'c') {
+ txt_console = 1;
+ } else if (c == 'l') {
+ log_level = atoi(optarg);
+ } else if (c == 'h') {
+ print_help(argv[0]);
+ return 0;
+ }
+ }
+
+ /* The minimal initialisation: provide an allocated rexmpp_t
+ structure and an initial jid. */
rexmpp_t s;
rexmpp_err_t err;
- if (argc != 2) {
- printf("Usage: %s <jid>", argv[0]);
+ err = rexmpp_init(&s, argv[argc - 1], my_logger);
+ if (err != REXMPP_SUCCESS) {
+ puts("Failed to initialise rexmpp.");
return -1;
}
- err = rexmpp_init(&s, argv[1]);
- s.log_function = my_logger;
+ /* Set the primary callback functions: for console, SASL, XML in and
+ out. */
+ if (txt_console) {
+ s.console_print_cb = my_console_print_cb;
+ }
s.sasl_property_cb = my_sasl_property_cb;
- s.xml_in_cb = my_xml_in_cb;
- s.xml_out_cb = my_xml_out_cb;
- if (err != REXMPP_SUCCESS) {
- puts("error");
- return -1;
+ if (xml_console) {
+ s.xml_in_cb = my_xml_in_cb;
+ s.xml_out_cb = my_xml_out_cb;
}
- /* gnutls_certificate_set_x509_key_file(s.gnutls_cred, */
- /* "cert.pem", */
- /* "key.pem", */
- /* GNUTLS_X509_FMT_PEM); */
- fd_set read_fds, write_fds;
- int nfds;
- struct timeval tv;
- struct timeval *mtv;
- int n = 0;
+ /* Could set a client certificate for SASL EXTERNAL authentication
+ and Jingle's DTLS here. */
+ s.x509_key_file = "client.key";
+ s.x509_cert_file = "client.crt";
+
+ /* 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); */
-
+ /* s.manual_host = "localhost"; */
+ s.local_address = "192.168.1.8";
+ /* rexmpp_tls_set_x509_trust_file(&s, "localhost.crt"); */
+ /* rexmpp_openpgp_set_home_dir(&s, "pgp"); */
s.roster_cache_file = "roster.xml";
+ /* s.tls_policy = REXMPP_TLS_AVOID; */
+ /* s.socket_cb = my_socket_options; */
+
+ /* 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)) {
char input[4097];
ssize_t input_len;
@@ -109,45 +183,32 @@ main (int argc, char **argv) {
} else {
input[input_len - 1] = '\0';
if (strlen(input) != 0) {
- if (input[0] == '<') {
- xmlDocPtr doc = xmlReadMemory(input, input_len, "", "utf-8", 0);
- if (doc != NULL) {
- xmlNodePtr node = xmlDocGetRootElement(doc);
- if (node != NULL) {
- xmlUnlinkNode(node);
- rexmpp_send(&s, node);
- } else {
- puts("No root node");
- }
- xmlFreeDoc(doc);
+ if (input[0] == '<' && xml_console) {
+ /* Raw XML input. */
+ rexmpp_xml_t *node = rexmpp_xml_parse(input, input_len);
+ if (node != NULL) {
+ rexmpp_send(&s, node);
} else {
- puts("Failed to read a document");
+ puts("Failed to parse XML");
}
- } else if (strcmp(input, ".") == 0) {
- rexmpp_stop(&s);
- } else if (strcmp(input, "connerr") == 0) {
- close(s.server_socket);
- s.server_socket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
- gnutls_transport_set_int(s.gnutls_session, s.server_socket);
- } else {
- xmlNodePtr msg = rexmpp_xml_add_id(&s, xmlNewNode(NULL, "message"));
- xmlNewProp(msg, "to", "test2@foo.custom");
- xmlNewProp(msg, "type", "chat");
- xmlNewTextChild(msg, NULL, "body", input);
- rexmpp_send(&s, msg);
+ } else if (txt_console) {
+ rexmpp_console_feed(&s, input, input_len);
}
}
}
}
+
+ /* Run a single rexmpp iteration. */
err = rexmpp_run(&s, &read_fds, &write_fds);
if (err == REXMPP_SUCCESS) {
puts("done");
break;
}
if (err != REXMPP_E_AGAIN) {
- puts("error");
+ 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, */
@@ -155,18 +216,35 @@ main (int argc, char **argv) {
/* 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, (struct timeval*)&tv);
+ 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);
- n = select(nfds, &read_fds, &write_fds, NULL, mtv);
+
+ /* 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;
}
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;
}
diff --git a/rexmpp.texi b/rexmpp.texi
index 5a23721..e92b969 100644
--- a/rexmpp.texi
+++ b/rexmpp.texi
@@ -21,6 +21,7 @@
@menu
* Copying Conditions:: License information.
* Introduction:: The basics.
+* Examples:: usage examples.
@end menu
@node Copying Conditions
@@ -51,5 +52,9 @@ in relevant standards: the intent is to keep the library flexible and
simple, only handling (or assisting with) more complex and routine parts
of the protocol.
+@node Examples
+@chapter Examples
+@section A basic example
+@verbatiminclude examples/basic.c
@bye
diff --git a/src/Cargo.toml b/src/Cargo.toml
new file mode 100644
index 0000000..65a0ff4
--- /dev/null
+++ b/src/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "rexmpp_rust"
+version = "0.1.0"
+authors = ["defanor <defanor@uberspace.net>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[lib]
+name = "rexmpp_rust"
+crate-type = ["staticlib"]
+path = "rexmpp_rust.rs"
+
+[dependencies]
+libc = "0.2"
+errno = "0.3"
+rxml = "0.9"
diff --git a/src/Makefile.am b/src/Makefile.am
index 741add0..2dfc581 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,17 +1,55 @@
-AM_CFLAGS = -Werror -Wall -Wextra -Wno-pointer-sign -Wno-unused-parameter
-
-# -Wno-pointer-sign is used to suppress libxml2-related warnings.
-# Since we only care about UTF-8, and in almost all cases just its
-# ASCII subset (comparing or setting fixed namespaces, element names,
-# etc), it shouldn't matter. Later it would be nice to abstract XML
-# manipulations anyway, to allow libexpat as an alternative.
+AM_CFLAGS = -Werror -Wall -Wextra -pedantic -std=gnu99
lib_LTLIBRARIES = librexmpp.la
librexmpp_la_SOURCES = rexmpp_roster.h rexmpp_roster.c \
- rexmpp_tcp.h rexmpp_tcp.c \
+ rexmpp.h rexmpp.c \
+ rexmpp_dns.h rexmpp_dns.c \
+ rexmpp_tls.h rexmpp_tls.c \
+ rexmpp_jid.h rexmpp_jid.c \
+ rexmpp_openpgp.h rexmpp_openpgp.c \
+ rexmpp_console.h rexmpp_console.c \
+ rexmpp_pubsub.h rexmpp_pubsub.c \
+ rexmpp_http_upload.h rexmpp_http_upload.c \
+ rexmpp_jingle.h rexmpp_jingle.c \
+ rexmpp_base64.h rexmpp_base64.c \
+ rexmpp_sasl.h rexmpp_sasl.c \
+ rexmpp_xml.h rexmpp_xml.c \
+ rexmpp_utf8.h \
+ rexmpp_random.h rexmpp_random.c \
+ rexmpp_digest.h rexmpp_digest.c
+
+include_HEADERS = config.h rexmpp_roster.h rexmpp_tcp.h rexmpp_socks.h rexmpp.h \
+ rexmpp_dns.h rexmpp_tls.h rexmpp_jid.h rexmpp_openpgp.h rexmpp_console.h \
+ rexmpp_pubsub.h rexmpp_http_upload.h rexmpp_jingle.h rexmpp_base64.h \
+ rexmpp_sasl.h rexmpp_xml.h rexmpp_utf8.h rexmpp_xml_parser.h \
+ rexmpp_random.h rexmpp_digest.h
+librexmpp_la_CFLAGS = $(AM_CFLAGS) $(LIBXML2_CFLAGS) $(EXPAT_CFLAGS) \
+ $(GNUTLS_CFLAGS) $(LIBDANE_CFLAGS) $(OPENSSL_CFLAGS) \
+ $(GSASL_CFLAGS) $(UNBOUND_CFLAGS) $(CARES_CFLAGS) $(GPGME_CFLAGS) \
+ $(ICU_I18N_CFLAGS) $(LIBGCRYPT_CFLAGS) $(CURL_CFLAGS) \
+ $(NICE_CFLAGS) $(GLIB_CFLAGS) $(SRTP_CFLAGS) \
+ $(PORTAUDIO_CFLAGS) $(OPUS_CFLAGS) $(NETTLE_CFLAGS)
+librexmpp_la_LIBADD = $(LIBXML2_LIBS) $(EXPAT_LIBS) \
+ $(GNUTLS_LIBS) $(LIBDANE_LIBS) $(OPENSSL_LIBS) \
+ $(GSASL_LIBS) $(UNBOUND_LIBS) $(CARES_LIBS) $(GPGME_LIBS) $(ICU_I18N_LIBS) \
+ $(LIBGCRYPT_LIBS) $(CURL_LIBS) $(NICE_LIBS) $(GLIB_LIBS) $(SRTP_LIBS) \
+ $(PORTAUDIO_LIBS) $(OPUS_LIBS) $(NETTLE_LIBS)
+librexmpp_la_LDFLAGS = []
+
+if USE_RUST
+target_debug_librexmpp_rust_a_SOURCES = \
+ rexmpp_rust.rs rexmpp.rs rexmpp_jid.rs rexmpp_dns.rs rexmpp_tcp.rs \
+ rexmpp_socks.rs rexmpp_xml.rs rexmpp_xml_parser.rs
+noinst_LIBRARIES = target/debug/librexmpp_rust.a
+librexmpp_la_LIBADD += target/debug/librexmpp_rust.a
+librexmpp_la_LDFLAGS += -L. -lpthread -ldl
+
+target/debug/librexmpp_rust.a: $(target_debug_librexmpp_rust_a_SOURCES)
+ $(CARGO) build
+
+else
+librexmpp_la_SOURCES += rexmpp_tcp.h rexmpp_tcp.c \
rexmpp_socks.h rexmpp_socks.c \
- rexmpp.h rexmpp.c
-include_HEADERS = rexmpp_roster.h rexmpp_tcp.h rexmpp_socks.h rexmpp.h
-librexmpp_la_CFLAGS = $(AM_CFLAGS) $(LIBXML_CFLAGS) $(GNUTLS_CFLAGS) $(GSASL_CFLAGS) $(CARES_CFLAGS)
-librexmpp_la_LIBADD = $(LIBXML_LIBS) $(GNUTLS_LIBS) $(GSASL_LIBS) $(CARES_LIBS)
+ rexmpp_xml_parser.h rexmpp_xml_parser.c
+endif
diff --git a/src/rexmpp.c b/src/rexmpp.c
index c4c1e1c..24b9965 100644
--- a/src/rexmpp.c
+++ b/src/rexmpp.c
@@ -8,39 +8,100 @@
#include <string.h>
#include <sys/time.h>
+#include <time.h>
#include <errno.h>
#include <syslog.h>
#include <arpa/nameser.h>
-
-#include <ares.h>
-#include <libxml/tree.h>
-#include <libxml/xmlsave.h>
-#include <gnutls/gnutls.h>
-#include <gnutls/crypto.h>
-#include <gnutls/x509.h>
-#include <gsasl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <assert.h>
+#include <stdbool.h>
+
+#include "config.h"
+
+#ifdef HAVE_GCRYPT
+#include <gcrypt.h>
+#endif
+#ifdef USE_UNBOUND
+#include <unbound.h>
+#endif
+#ifdef HAVE_GPGME
+#include <gpgme.h>
+#endif
+#ifdef HAVE_CURL
+#include <curl/curl.h>
+#endif
#include "rexmpp.h"
+#include "rexmpp_xml.h"
#include "rexmpp_tcp.h"
#include "rexmpp_socks.h"
#include "rexmpp_roster.h"
+#include "rexmpp_dns.h"
+#include "rexmpp_jid.h"
+#include "rexmpp_openpgp.h"
+#include "rexmpp_console.h"
+#include "rexmpp_http_upload.h"
+#include "rexmpp_jingle.h"
+#include "rexmpp_base64.h"
+#include "rexmpp_sasl.h"
+#include "rexmpp_random.h"
+#include "rexmpp_digest.h"
+
+struct rexmpp_iq_cacher {
+ rexmpp_iq_callback_t cb;
+ void *cb_data;
+};
+
+struct rexmpp_feature_search {
+ const char *feature_var;
+ int max_requests;
+ int pending;
+ rexmpp_iq_callback_t cb;
+ void *cb_data;
+ int fresh;
+ int found;
+};
+
+const char *rexmpp_strerror (rexmpp_err_t error) {
+ switch (error) {
+ case REXMPP_SUCCESS: return "No error";
+ case REXMPP_E_AGAIN: return "An operation is in progress";
+ case REXMPP_E_SEND_QUEUE_FULL: return
+ "A message can't be queued for sending, because the queue is full";
+ case REXMPP_E_STANZA_QUEUE_FULL: return
+ "The library can't take responsibility for message delivery because "
+ "XEP-0198 stanza queue is full";
+ case REXMPP_E_CANCELLED: return "Cancelled by a user";
+ case REXMPP_E_SEND_BUFFER_EMPTY: return
+ "Attempted to send while send buffer is empty";
+ case REXMPP_E_SEND_BUFFER_NOT_EMPTY: return
+ "Attempted to start sending while send buffer is not empty";
+ case REXMPP_E_SASL: return "SASL-related error";
+ case REXMPP_E_PGP: return "OpenPGP-related error";
+ case REXMPP_E_TLS: return "TLS-related error";
+ case REXMPP_E_TCP: return "TCP-related error";
+ case REXMPP_E_DNS: return "DNS-related error";
+ case REXMPP_E_XML: return "XML-related error";
+ case REXMPP_E_JID: return "JID-related error";
+ case REXMPP_E_MALLOC: return "Memory allocation failure";
+ case REXMPP_E_ROSTER: return "Roster-related error";
+ case REXMPP_E_ROSTER_ITEM_NOT_FOUND: return "Roster item is not found";
+ case REXMPP_E_PARAM: return "An erroneous parameter is supplied";
+ case REXMPP_E_STREAM: return "A stream error";
+ case REXMPP_E_OTHER: return "An unspecified error";
+ default: return "Unknown error";
+ }
+}
void rexmpp_sax_start_elem_ns (rexmpp_t *s,
- const char *localname,
- const char *prefix,
- const char *URI,
- int nb_namespaces,
- const char **namespaces,
- int nb_attributes,
- int nb_defaulted,
- const char **attributes);
-
-void rexmpp_sax_end_elem_ns(rexmpp_t *s,
- const char *localname,
- const char *prefix,
- const char *URI);
-
-void rexmpp_sax_characters (rexmpp_t *s, const char * ch, int len);
+ const char *name,
+ const char *namespace,
+ rexmpp_xml_attr_t *attributes);
+
+void rexmpp_sax_end_elem_ns(rexmpp_t *s);
+
+void rexmpp_sax_characters (rexmpp_t *s, const char * ch, size_t len);
void rexmpp_log (rexmpp_t *s, int priority, const char *format, ...)
{
@@ -52,174 +113,470 @@ void rexmpp_log (rexmpp_t *s, int priority, const char *format, ...)
}
}
-char *rexmpp_capabilities_string (rexmpp_t *s, xmlNodePtr info) {
- /* Assuming the info is sorted already. Would be better to sort it
- here (todo). */
- xmlNodePtr cur;
- int buf_len = 1024, str_len = 0;
- char *str = malloc(buf_len);
- for (cur = info; cur; cur = cur->next) {
- if (strcmp(cur->name, "identity") == 0) {
- int cur_len = 5; /* ///< for an empty identity */
-
- /* Collect the properties we'll need. */
- char *category = xmlGetProp(cur, "category");
- char *type = xmlGetProp(cur, "type");
- char *lang = xmlGetProp(cur, "xml:lang");
- char *name = xmlGetProp(cur, "name");
-
- /* Calculate the length needed. */
- if (category != NULL) {
- cur_len += strlen(category);
- }
- if (type != NULL) {
- cur_len += strlen(type);
- }
- if (lang != NULL) {
- cur_len += strlen(lang);
- }
- if (name != NULL) {
- cur_len += strlen(name);
- }
+rexmpp_err_t rexmpp_muc_ping_set (rexmpp_t *s,
+ const char *occupant_jid,
+ const char *password,
+ unsigned int delay)
+{
+ /* At first try to edit an existing record. */
+ rexmpp_muc_ping_t *mp = s->muc_ping;
+ while (mp != NULL) {
+ if (strcmp(mp->jid, occupant_jid) == 0) {
+ mp->delay = delay;
+ return REXMPP_SUCCESS;
+ }
+ mp = mp->next;
+ }
- /* Reallocate the buffer if necessary. */
- if (cur_len > buf_len - str_len) {
- while (cur_len > buf_len - str_len) {
- buf_len *= 2;
- }
- str = realloc(str, buf_len);
- }
+ /* No existing self-ping record for this occupant JID; create a new
+ one. */
+ mp = malloc(sizeof(rexmpp_muc_ping_t));
+ if (mp == NULL) {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to allocate memory for a MUC self-ping record: %s",
+ strerror(errno));
+ return REXMPP_E_MALLOC;
+ }
+ mp->jid = strdup(occupant_jid);
+ if (mp->jid == NULL) {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to duplicate JID string for a MUC self-ping record: %s",
+ strerror(errno));
+ free(mp);
+ return REXMPP_E_OTHER;
+ }
+ if (password != NULL) {
+ mp->password = strdup(password);
+ } else {
+ mp->password = NULL;
+ }
- /* Fill the data. */
- if (category != NULL) {
- strcpy(str + str_len, category);
- str_len += strlen(category);
- }
- str[str_len] = '/';
- str_len++;
- if (type != NULL) {
- strcpy(str + str_len, type);
- str_len += strlen(type);
- }
- str[str_len] = '/';
- str_len++;
- if (lang != NULL) {
- strcpy(str + str_len, lang);
- str_len += strlen(lang);
- }
- str[str_len] = '/';
- str_len++;
- if (name != NULL) {
- strcpy(str + str_len, name);
- str_len += strlen(name);
- }
- str[str_len] = '<';
- str_len++;
+ mp->delay = delay;
+ mp->requested = 0;
+ mp->last_activity.tv_sec = 0;
+ mp->last_activity.tv_nsec = 0;
+ mp->next = s->muc_ping;
+ s->muc_ping = mp;
+ return REXMPP_SUCCESS;
+}
- /* Free the values. */
- if (category != NULL) {
- free(category);
+rexmpp_err_t rexmpp_muc_ping_remove (rexmpp_t *s, const char *occupant_jid) {
+ rexmpp_muc_ping_t **mp = &(s->muc_ping);
+ while (*mp != NULL) {
+ if (strcmp(occupant_jid, (*mp)->jid) == 0) {
+ rexmpp_muc_ping_t *found = *mp;
+ *mp = found->next;
+ free(found->jid);
+ if (found->password != NULL) {
+ free(found->password);
}
- if (type != NULL) {
- free(type);
- }
- if (lang != NULL) {
- free(lang);
- }
- if (name != NULL) {
- free(name);
+ free(found);
+ return REXMPP_SUCCESS;
+ }
+ mp = &((*mp)->next);
+ }
+ rexmpp_log(s, LOG_WARNING,
+ "Removal of MUC self-ping record for JID %s is requested, "
+ "but no such record is found",
+ occupant_jid);
+ return REXMPP_E_OTHER;
+}
+
+rexmpp_err_t rexmpp_muc_join (rexmpp_t *s,
+ const char *occupant_jid,
+ const char *password,
+ unsigned int ping_delay)
+{
+ rexmpp_xml_t *presence =
+ rexmpp_xml_new_elem("presence", "jabber:client");
+ rexmpp_xml_add_id(presence);
+ rexmpp_xml_add_attr(presence, "from", s->assigned_jid.full);
+ rexmpp_xml_add_attr(presence, "to", occupant_jid);
+ rexmpp_xml_t *x =
+ rexmpp_xml_new_elem("x", "http://jabber.org/protocol/muc");
+ rexmpp_xml_add_child(presence, x);
+ rexmpp_err_t ret = rexmpp_send(s, presence);
+ if (ping_delay > 0) {
+ rexmpp_muc_ping_set(s, occupant_jid, password, ping_delay);
+ }
+ return ret;
+}
+
+rexmpp_err_t rexmpp_muc_leave (rexmpp_t *s, const char *occupant_jid) {
+ rexmpp_muc_ping_remove(s, occupant_jid);
+ rexmpp_xml_t *presence =
+ rexmpp_xml_new_elem("presence", "jabber:client");
+ rexmpp_xml_add_id(presence);
+ rexmpp_xml_add_attr(presence, "from", s->assigned_jid.full);
+ rexmpp_xml_add_attr(presence, "to", occupant_jid);
+ rexmpp_xml_add_attr(presence, "type", "unavailable");
+ return rexmpp_send(s, presence);
+}
+
+void rexmpp_muc_pong (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ rexmpp_muc_ping_t *mp = ptr;
+ clock_gettime(CLOCK_MONOTONIC, &(mp->last_activity));
+ mp->requested = 0;
+ if (! success) {
+ const char *jid = rexmpp_xml_find_attr_val(req, "to");
+ rexmpp_xml_t *error = rexmpp_xml_first_elem_child(response);
+ if (error == NULL) {
+ rexmpp_log(s, LOG_ERR,
+ "MUC self-ping failure for %s, and no error element received",
+ jid);
+ rexmpp_muc_ping_remove(s, jid);
+ } else {
+ rexmpp_log(s, LOG_WARNING, "MUC self-ping failure for %s: %s",
+ jid, error->alt.elem.qname.name);
+ if (rexmpp_xml_match(error, NULL, "service-unavailable") ||
+ rexmpp_xml_match(error, NULL, "feature-not-implemented") ||
+ rexmpp_xml_match(error, NULL, "remote-server-not-found") ||
+ rexmpp_xml_match(error, NULL, "remote-server-timeout")) {
+ rexmpp_log(s, LOG_WARNING, "Giving up on pinging it");
+ rexmpp_muc_ping_remove(s, jid);
+ } else if (rexmpp_xml_match(error, NULL, "item-not-found")) {
+ /* Ignore and keep pinging? */
+ } else {
+ /* Some other error, re-join. */
+ rexmpp_muc_join(s, jid, NULL, 0);
}
- } else if (strcmp(cur->name, "feature") == 0) {
- char *var = xmlGetProp(cur, "var");
- int cur_len = 2 + strlen(var);
- if (cur_len > buf_len - str_len) {
- while (cur_len > buf_len - str_len) {
- buf_len *= 2;
+ }
+ }
+}
+
+char *rexmpp_capabilities_hash (rexmpp_t *s,
+ rexmpp_xml_t *info)
+{
+ /* Assuming the info is sorted already. Might be useful to sort it
+ here. */
+ rexmpp_digest_t digest_ctx;
+ if (rexmpp_digest_init(&digest_ctx, REXMPP_DIGEST_SHA1)) {
+ rexmpp_log(s, LOG_ERR, "Failed to initialize a digest object");
+ return NULL;
+ }
+
+ rexmpp_xml_t *cur;
+ for (cur = info; cur; cur = cur->next) {
+ if (strcmp(cur->alt.elem.qname.name, "identity") == 0) {
+ const char *identity_strings[4] =
+ { rexmpp_xml_find_attr_val(cur, "category"),
+ rexmpp_xml_find_attr_val(cur, "type"),
+ rexmpp_xml_find_attr_val(cur, "xml:lang"),
+ rexmpp_xml_find_attr_val(cur, "name")
+ };
+ int i;
+ for (i = 0; i < 4; i++) {
+ if (identity_strings[i] != NULL) {
+ rexmpp_digest_update(&digest_ctx,
+ identity_strings[i],
+ strlen(identity_strings[i]));
}
- str = realloc(str, buf_len);
+ rexmpp_digest_update(&digest_ctx, (i < 3) ? "/" : "<", 1);
+ }
+ } else if (strcmp(cur->alt.elem.qname.name, "feature") == 0) {
+ const char *var = rexmpp_xml_find_attr_val(cur, "var");
+ if (var != NULL) {
+ rexmpp_digest_update(&digest_ctx, var, strlen(var));
+ rexmpp_digest_update(&digest_ctx, "<", 1);
+ } else {
+ rexmpp_log(s, LOG_ERR, "Found an empty feature var");
}
- strcpy(str + str_len, var);
- str_len += strlen(var);
- str[str_len] = '<';
- str_len++;
- free(var);
} else {
rexmpp_log(s, LOG_ERR,
- "Unsupported node type in disco info: %s", cur->name);
+ "Unsupported node type in disco info: %s",
+ cur->alt.elem.qname.name);
}
}
- str[str_len] = '\0';
- return str;
+
+ size_t digest_len = rexmpp_digest_len(REXMPP_DIGEST_SHA1);
+ char *digest_buf = malloc(digest_len);
+ rexmpp_digest_finish(&digest_ctx, digest_buf, digest_len);
+ char *digest_base64 = NULL;
+ size_t digest_base64_len = 0;
+ rexmpp_base64_to(digest_buf, digest_len, &digest_base64, &digest_base64_len);
+ free(digest_buf);
+ return digest_base64;
}
-char *rexmpp_capabilities_hash (rexmpp_t *s,
- xmlNodePtr info)
+rexmpp_xml_t *rexmpp_find_event (rexmpp_t *s,
+ const char *from,
+ const char *node,
+ rexmpp_xml_t **prev_event)
{
- int err;
- char *hash;
- char *str = rexmpp_capabilities_string(s, info);
- err = gsasl_sha1(str, strlen(str), &hash);
- free(str);
- if (err) {
- rexmpp_log(s, LOG_ERR, "Hashing failure: %s",
- gsasl_strerror(err));
+ rexmpp_xml_t *prev, *cur;
+ for (prev = NULL, cur = s->roster_events;
+ cur != NULL;
+ prev = cur, cur = cur->next) {
+ const char *cur_from = rexmpp_xml_find_attr_val(cur, "from");
+ if (cur_from == NULL) {
+ continue;
+ }
+ rexmpp_xml_t *cur_event =
+ rexmpp_xml_find_child(cur,
+ "http://jabber.org/protocol/pubsub#event",
+ "event");
+ rexmpp_xml_t *cur_items =
+ rexmpp_xml_find_child(cur_event,
+ "http://jabber.org/protocol/pubsub#event",
+ "items");
+ if (cur_items == NULL) {
+ continue;
+ }
+ const char *cur_node = rexmpp_xml_find_attr_val(cur_items, "node");
+ if (cur_node == NULL) {
+ continue;
+ }
+ int match = (strcmp(cur_from, from) == 0 && strcmp(cur_node, node) == 0);
+ if (match) {
+ if (prev_event != NULL) {
+ *prev_event = prev;
+ }
+ return cur;
+ }
+ }
+ return NULL;
+}
+
+/* https://docs.modernxmpp.org/client/design/#names */
+char *rexmpp_get_name (rexmpp_t *s, const char *jid_str) {
+ struct rexmpp_jid jid;
+ if (rexmpp_jid_parse(jid_str, &jid) != 0) {
return NULL;
}
- char *out = NULL;
- size_t out_len = 0;
- gsasl_base64_to(hash, 20, &out, &out_len);
- free(hash);
- return out;
+ if (s->manage_roster) {
+ rexmpp_xml_t *roster_item = rexmpp_roster_find_item(s, jid.bare, NULL);
+ if (roster_item != NULL) {
+ const char *name = rexmpp_xml_find_attr_val(roster_item, "name");
+ if (name != NULL) {
+ return strdup(name);
+ }
+ }
+ if (s->track_roster_events) {
+ rexmpp_xml_t *elem =
+ rexmpp_find_event(s, jid.bare, "http://jabber.org/protocol/nick", NULL);
+ if (elem != NULL) {
+ rexmpp_xml_t *event =
+ rexmpp_xml_find_child(elem,
+ "http://jabber.org/protocol/pubsub#event",
+ "event");
+ rexmpp_xml_t *items =
+ rexmpp_xml_find_child(event,
+ "http://jabber.org/protocol/pubsub#event",
+ "items");
+ rexmpp_xml_t *item =
+ rexmpp_xml_find_child(items,
+ "http://jabber.org/protocol/pubsub#event",
+ "item");
+ if (item != NULL) {
+ rexmpp_xml_t *nick =
+ rexmpp_xml_find_child(item,
+ "http://jabber.org/protocol/nick",
+ "nick");
+ if (nick != NULL &&
+ nick->type == REXMPP_XML_ELEMENT &&
+ nick->alt.elem.children != NULL &&
+ nick->alt.elem.children->type == REXMPP_XML_TEXT) {
+ return strdup(rexmpp_xml_text_child(nick));
+ }
+ }
+ }
+ }
+ }
+ if (jid.local[0] != '\0') {
+ return strdup(jid.local);
+ }
+ return strdup(jid.bare);
}
-xmlNodePtr rexmpp_xml_feature (const char *var) {
- xmlNodePtr feature = xmlNewNode(NULL, "feature");
- xmlNewProp(feature, "var", var);
+rexmpp_xml_t *rexmpp_xml_feature (const char *var) {
+ rexmpp_xml_t *feature = rexmpp_xml_new_elem("feature", NULL);
+ rexmpp_xml_add_attr(feature, "var", var);
return feature;
}
-xmlNodePtr rexmpp_xml_error (const char *type, const char *condition) {
- xmlNodePtr error = xmlNewNode(NULL, "error");
- xmlNewProp(error, "type", type);
- xmlNodePtr cond = xmlNewNode(NULL, condition);
- xmlNewNs(cond, "urn:ietf:params:xml:ns:xmpp-stanzas", NULL);
- xmlAddChild(error, cond);
- return error;
+void rexmpp_disco_find_feature_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ struct rexmpp_feature_search *search = ptr;
+ if (! success) {
+ const char *to = rexmpp_xml_find_attr_val(request, "to");
+ rexmpp_xml_t *query = request->alt.elem.children;
+ rexmpp_log(s, LOG_ERR, "Failed to query %s for %s.", to, query->alt.elem.qname.namespace);
+ } else if (! search->found) {
+ rexmpp_xml_t *query = rexmpp_xml_first_elem_child(response);
+ if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#info",
+ "query")) {
+ rexmpp_xml_t *child = rexmpp_xml_first_elem_child(query);
+ while (child != NULL && (! search->found)) {
+ if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#info",
+ "feature")) {
+ const char *var = rexmpp_xml_find_attr_val(child, "var");
+ if (var != NULL) {
+ if (strcmp(var, search->feature_var) == 0) {
+ search->cb(s, search->cb_data, request, response, success);
+ search->found = 1;
+ }
+ }
+ }
+ child = rexmpp_xml_next_elem_sibling(child);
+ }
+ if ((! search->found) && (search->max_requests > 0)) {
+ /* Still not found, request items */
+ const char *jid = rexmpp_xml_find_attr_val(request, "to");
+ if (jid != NULL) {
+ search->pending++;
+ search->max_requests--;
+ rexmpp_xml_t *query =
+ rexmpp_xml_new_elem("query",
+ "http://jabber.org/protocol/disco#items");
+ rexmpp_cached_iq_new(s, "get", jid, query,
+ rexmpp_disco_find_feature_cb,
+ search, search->fresh);
+ }
+ }
+ } else if (rexmpp_xml_match(query,
+ "http://jabber.org/protocol/disco#items",
+ "query")) {
+ rexmpp_xml_t *child = rexmpp_xml_first_elem_child(query);
+ while (child != NULL && (search->max_requests > 0)) {
+ if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#items",
+ "item")) {
+ const char *jid = rexmpp_xml_find_attr_val(child, "jid");
+ if (jid != NULL) {
+ search->pending++;
+ search->max_requests--;
+ rexmpp_xml_t *query =
+ rexmpp_xml_new_elem("query",
+ "http://jabber.org/protocol/disco#info");
+ rexmpp_cached_iq_new(s, "get", jid, query,
+ rexmpp_disco_find_feature_cb,
+ search, search->fresh);
+ }
+ }
+ child = rexmpp_xml_next_elem_sibling(child);
+ }
+ }
+ }
+ search->pending--;
+ if (search->pending == 0) {
+ if (! search->found) {
+ search->cb(s, search->cb_data, NULL, NULL, 0);
+ }
+ free(search);
+ }
+}
+
+rexmpp_err_t
+rexmpp_disco_find_feature (rexmpp_t *s,
+ const char *jid,
+ const char *feature_var,
+ rexmpp_iq_callback_t cb,
+ void *cb_data,
+ int fresh,
+ int max_requests)
+{
+ struct rexmpp_feature_search *search =
+ malloc(sizeof(struct rexmpp_feature_search));
+ if (search == NULL) {
+ return REXMPP_E_MALLOC;
+ }
+ search->max_requests = max_requests - 1;
+ search->found = 0;
+ search->pending = 1;
+ search->cb = cb;
+ search->cb_data = cb_data;
+ search->fresh = fresh;
+ search->feature_var = feature_var;
+ rexmpp_xml_t *query =
+ rexmpp_xml_new_elem("query", "http://jabber.org/protocol/disco#info");
+ if (jid == NULL) {
+ jid = s->initial_jid.domain;
+ }
+ return rexmpp_cached_iq_new(s, "get", jid, query,
+ rexmpp_disco_find_feature_cb, search, fresh);
}
-xmlNodePtr rexmpp_xml_default_disco_info () {
+rexmpp_xml_t *rexmpp_disco_info (rexmpp_t *s) {
+ if (s->disco_info != NULL) {
+ return s->disco_info;
+ }
+ rexmpp_xml_t *prev = NULL, *cur;
/* There must be at least one identity, so filling in somewhat
sensible defaults. A basic client may leave them be, while an
advanced one would adjust and/or extend them. */
- xmlNodePtr identity = xmlNewNode(NULL, "identity");
- xmlNewProp(identity, "category", "client");
- xmlNewProp(identity, "type", "console");
- xmlNewProp(identity, "name", "rexmpp");
- xmlNodePtr disco_feature =
- rexmpp_xml_feature("http://jabber.org/protocol/disco#info");
- identity->next = disco_feature;
- xmlNodePtr ping_feature = rexmpp_xml_feature("urn:xmpp:ping");
- disco_feature->next = ping_feature;
- return identity;
+ s->disco_info = rexmpp_xml_new_elem("identity", NULL);
+ rexmpp_xml_add_attr(s->disco_info, "category", "client");
+ rexmpp_xml_add_attr(s->disco_info, "type", s->client_type);
+ rexmpp_xml_add_attr(s->disco_info, "name", s->client_name);
+ prev = s->disco_info;
+ cur = rexmpp_xml_feature("http://jabber.org/protocol/disco#info");
+ prev->next = cur;
+ prev = cur;
+ if (s->nick_notifications) {
+ cur = rexmpp_xml_feature("http://jabber.org/protocol/nick+notify");
+ prev->next = cur;
+ prev = cur;
+ }
+ if (s->autojoin_bookmarked_mucs) {
+ cur = rexmpp_xml_feature("urn:xmpp:bookmarks:1+notify");
+ prev->next = cur;
+ prev = cur;
+ }
+ if (s->retrieve_openpgp_keys) {
+ cur = rexmpp_xml_feature("urn:xmpp:openpgp:0:public-keys+notify");
+ prev->next = cur;
+ prev = cur;
+ }
+ if (s->enable_jingle) {
+ cur = rexmpp_xml_feature("urn:xmpp:jingle:1");
+ prev->next = cur;
+ prev = cur;
+ cur = rexmpp_xml_feature("urn:xmpp:jingle:apps:file-transfer:5");
+ prev->next = cur;
+ prev = cur;
+ cur = rexmpp_xml_feature("urn:xmpp:jingle:transports:ibb:1");
+ prev->next = cur;
+ prev = cur;
+#ifdef ENABLE_CALLS
+ cur = rexmpp_xml_feature("urn:xmpp:jingle:apps:dtls:0");
+ prev->next = cur;
+ prev = cur;
+ cur = rexmpp_xml_feature("urn:xmpp:jingle:transports:ice-udp:1");
+ prev->next = cur;
+ prev = cur;
+ cur = rexmpp_xml_feature("urn:xmpp:jingle:apps:rtp:1");
+ prev->next = cur;
+ prev = cur;
+ cur = rexmpp_xml_feature("urn:xmpp:jingle:apps:rtp:audio");
+ prev->next = cur;
+ prev = cur;
+#endif
+ }
+ cur = rexmpp_xml_feature("urn:xmpp:ping");
+ prev->next = cur;
+ prev = cur;
+ return s->disco_info;
}
-int rexmpp_sasl_cb (Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) {
- rexmpp_t *s = gsasl_callback_hook_get(ctx);
- if (s == NULL || s->sasl_property_cb == NULL) {
- return GSASL_NO_CALLBACK;
- }
- return s->sasl_property_cb(s, prop);
-}
+struct rexmpp_xml_parser_handlers sax = {
+ (rexmpp_xml_parser_element_start)rexmpp_sax_start_elem_ns,
+ (rexmpp_xml_parser_element_end)rexmpp_sax_end_elem_ns,
+ (rexmpp_xml_parser_characters)rexmpp_sax_characters
+};
-rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid)
+rexmpp_err_t rexmpp_init (rexmpp_t *s,
+ const char *jid,
+ log_function_t log_func)
{
int err;
- xmlSAXHandler sax = {
- .initialized = XML_SAX2_MAGIC,
- .characters = (charactersSAXFunc)rexmpp_sax_characters,
- .startElementNs = (startElementNsSAX2Func)rexmpp_sax_start_elem_ns,
- .endElementNs = (endElementNsSAX2Func)rexmpp_sax_end_elem_ns,
- };
s->tcp_state = REXMPP_TCP_NONE;
s->resolver_state = REXMPP_RESOLVER_NONE;
@@ -230,113 +587,192 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid)
s->carbons_state = REXMPP_CARBONS_INACTIVE;
s->manual_host = NULL;
s->manual_port = 5222;
- s->manual_direct_tls = 0;
+ s->manual_direct_tls = false;
s->disco_node = "rexmpp";
s->socks_host = NULL;
s->server_host = NULL;
- s->enable_carbons = 1;
- s->enable_service_discovery = 1;
- s->manage_roster = 1;
+ s->enable_carbons = true;
+ s->manage_roster = true;
s->roster_cache_file = NULL;
+ s->track_roster_presence = true;
+ s->track_roster_events = true;
+ s->nick_notifications = true;
+#ifdef HAVE_GPGME
+ s->retrieve_openpgp_keys = true;
+#else
+ s->retrieve_openpgp_keys = false;
+#endif
+ s->autojoin_bookmarked_mucs = true;
+ s->tls_policy = REXMPP_TLS_REQUIRE;
+ s->enable_jingle = true;
+ s->client_name = PACKAGE_NAME;
+ s->client_type = "console";
+ s->client_version = PACKAGE_VERSION;
+ s->local_address = NULL;
+ s->jingle_prefer_rtcp_mux = true;
+ s->muc_ping_default_delay = 600;
s->send_buffer = NULL;
s->send_queue = NULL;
s->server_srv = NULL;
- s->server_srv_cur = NULL;
+ s->server_srv_cur = -1;
s->server_srv_tls = NULL;
- s->server_srv_tls_cur = NULL;
+ s->server_srv_tls_cur = -1;
s->server_socket = -1;
+ s->server_socket_dns_secure = false;
s->current_element_root = NULL;
s->current_element = NULL;
+ s->input_queue = NULL;
+ s->input_queue_last = NULL;
s->stream_features = NULL;
s->roster_items = NULL;
s->roster_ver = NULL;
+ s->roster_presence = NULL;
+ s->roster_events = NULL;
s->stanza_queue = NULL;
s->stream_id = NULL;
s->active_iq = NULL;
- s->tls_session_data = NULL;
- s->tls_session_data_size = 0;
- s->id_counter = 0;
+ s->iq_cache = NULL;
s->reconnect_number = 0;
s->next_reconnect_time.tv_sec = 0;
- s->next_reconnect_time.tv_usec = 0;
- s->initial_jid = NULL;
- s->assigned_jid = NULL;
+ s->next_reconnect_time.tv_nsec = 0;
+ s->initial_jid.full[0] = '\0';
+ s->assigned_jid.full[0] = '\0';
s->stanza_queue_size = 1024;
s->send_queue_size = 1024;
s->iq_queue_size = 1024;
- s->log_function = NULL;
+ s->iq_cache_size = 1024;
+ s->max_jingle_sessions = 1024;
+ s->x509_cert_file = NULL;
+ s->x509_key_file = NULL;
+ s->x509_trust_file = NULL;
+ s->log_function = log_func;
s->sasl_property_cb = NULL;
s->xml_in_cb = NULL;
s->xml_out_cb = NULL;
s->roster_modify_cb = NULL;
+ s->console_print_cb = NULL;
+ s->socket_cb = NULL;
s->ping_delay = 600;
- s->ping_requested = 0;
- s->last_network_activity = 0;
+ s->ping_requested = false;
+ s->last_network_activity.tv_sec = 0;
+ s->last_network_activity.tv_nsec = 0;
+ s->muc_ping = NULL;
+ s->disco_info = NULL;
+
+ s->jingle_rtp_description =
+ rexmpp_xml_new_elem("description", "urn:xmpp:jingle:apps:rtp:1");
+ rexmpp_xml_add_attr(s->jingle_rtp_description, "media", "audio");
+ rexmpp_xml_t *pl_type;
+
+#ifdef HAVE_OPUS
+ pl_type = rexmpp_xml_new_elem("payload-type", "urn:xmpp:jingle:apps:rtp:1");
+ rexmpp_xml_add_attr(pl_type, "id", "97");
+ rexmpp_xml_add_attr(pl_type, "name", "opus");
+ rexmpp_xml_add_attr(pl_type, "clockrate", "48000");
+ rexmpp_xml_add_attr(pl_type, "channels", "2");
+ rexmpp_xml_add_child(s->jingle_rtp_description, pl_type);
+#endif
+
+ pl_type = rexmpp_xml_new_elem("payload-type", "urn:xmpp:jingle:apps:rtp:1");
+ rexmpp_xml_add_attr(pl_type, "id", "0");
+ rexmpp_xml_add_attr(pl_type, "name", "PCMU");
+ rexmpp_xml_add_attr(pl_type, "clockrate", "8000");
+ rexmpp_xml_add_attr(pl_type, "channels", "1");
+ rexmpp_xml_add_child(s->jingle_rtp_description, pl_type);
+
+ pl_type = rexmpp_xml_new_elem("payload-type", "urn:xmpp:jingle:apps:rtp:1");
+ rexmpp_xml_add_attr(pl_type, "id", "8");
+ rexmpp_xml_add_attr(pl_type, "name", "PCMA");
+ rexmpp_xml_add_attr(pl_type, "clockrate", "8000");
+ rexmpp_xml_add_attr(pl_type, "channels", "1");
+ rexmpp_xml_add_child(s->jingle_rtp_description, pl_type);
if (jid == NULL) {
rexmpp_log(s, LOG_CRIT, "No initial JID is provided.");
return REXMPP_E_JID;
}
- s->initial_jid = strdup(jid);
+ if (rexmpp_jid_parse(jid, &(s->initial_jid))) {
+ rexmpp_log(s, LOG_CRIT, "Failed to parse the initial JID.");
+ return REXMPP_E_JID;
+ }
+ if (! rexmpp_jid_check(&s->initial_jid)) {
+ rexmpp_log(s, LOG_CRIT, "An invalid initial JID is provided.");
+ return REXMPP_E_JID;
+ }
- s->xml_parser = xmlCreatePushParserCtxt(&sax, s, "", 0, NULL);
+#ifdef HAVE_GCRYPT
+ if (! gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) {
+ rexmpp_log(s, LOG_DEBUG, "Initializing libgcrypt");
+ if (gcry_check_version(NULL) == NULL) {
+ rexmpp_log(s, LOG_CRIT, "Failed to initialize libgcrypt");
+ return REXMPP_E_OTHER;
+ }
+ gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
+ }
+#endif
+
+ s->xml_parser = rexmpp_xml_parser_new(&sax, s);
if (s->xml_parser == NULL) {
rexmpp_log(s, LOG_CRIT, "Failed to create an XML parser context.");
return REXMPP_E_XML;
}
- err = ares_library_init(ARES_LIB_INIT_ALL);
- if (err != 0) {
- rexmpp_log(s, LOG_CRIT, "ares library initialisation error: %s",
- ares_strerror(err));
- xmlFreeParserCtxt(s->xml_parser);
+ if (rexmpp_dns_ctx_init(s)) {
+ rexmpp_xml_parser_free(s->xml_parser);
return REXMPP_E_DNS;
}
- err = ares_init(&(s->resolver_channel));
- if (err) {
- rexmpp_log(s, LOG_CRIT, "ares channel initialisation error: %s",
- ares_strerror(err));
- ares_library_cleanup();
- xmlFreeParserCtxt(s->xml_parser);
- return REXMPP_E_DNS;
- }
-
- err = gnutls_certificate_allocate_credentials(&(s->gnutls_cred));
- if (err) {
- rexmpp_log(s, LOG_CRIT, "gnutls credentials allocation error: %s",
- gnutls_strerror(err));
- ares_destroy(s->resolver_channel);
- ares_library_cleanup();
- xmlFreeParserCtxt(s->xml_parser);
- return REXMPP_E_TLS;
- }
- err = gnutls_certificate_set_x509_system_trust(s->gnutls_cred);
- if (err < 0) {
- rexmpp_log(s, LOG_CRIT, "Certificates loading error: %s",
- gnutls_strerror(err));
- ares_destroy(s->resolver_channel);
- ares_library_cleanup();
- xmlFreeParserCtxt(s->xml_parser);
+ if (rexmpp_tls_init(s)) {
+ rexmpp_dns_ctx_deinit(s);
+ rexmpp_xml_parser_free(s->xml_parser);
return REXMPP_E_TLS;
}
- err = gsasl_init(&(s->sasl_ctx));
+ err = rexmpp_sasl_ctx_init(s);
if (err) {
- rexmpp_log(s, LOG_CRIT, "gsasl initialisation error: %s",
- gsasl_strerror(err));
- gnutls_certificate_free_credentials(s->gnutls_cred);
- ares_destroy(s->resolver_channel);
- ares_library_cleanup();
- xmlFreeParserCtxt(s->xml_parser);
+ rexmpp_tls_deinit(s);
+ rexmpp_dns_ctx_deinit(s);
+ rexmpp_xml_parser_free(s->xml_parser);
return REXMPP_E_SASL;
}
- gsasl_callback_hook_set(s->sasl_ctx, s);
- gsasl_callback_set(s->sasl_ctx, rexmpp_sasl_cb);
- s->disco_info = rexmpp_xml_default_disco_info();
+ if (rexmpp_jingle_init(s)) {
+ rexmpp_sasl_ctx_deinit(s);
+ rexmpp_tls_deinit(s);
+ rexmpp_dns_ctx_deinit(s);
+ rexmpp_xml_parser_free(s->xml_parser);
+ }
+
+#ifdef HAVE_GPGME
+ gpgme_check_version(NULL);
+ err = gpgme_new(&(s->pgp_ctx));
+ if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_CRIT, "gpgme initialisation error: %s",
+ gpgme_strerror(err));
+ rexmpp_sasl_ctx_deinit(s);
+ rexmpp_tls_deinit(s);
+ rexmpp_dns_ctx_deinit(s);
+ rexmpp_jingle_stop(s);
+ rexmpp_xml_parser_free(s->xml_parser);
+ return REXMPP_E_PGP;
+ }
+#else
+ s->pgp_ctx = NULL;
+#endif
+#ifdef HAVE_CURL
+ if (curl_global_init(CURL_GLOBAL_ALL) != 0) {
+ rexmpp_log(s, LOG_CRIT, "Failed to initialize curl");
+ }
+ s->curl_multi = curl_multi_init();
+ if (s->curl_multi == NULL) {
+ rexmpp_log(s, LOG_CRIT, "Failed to initialize curl_multi");
+ /* todo: free other structures and fail */
+ }
+#else
+ s->curl_multi = NULL;
+#endif
return REXMPP_SUCCESS;
}
@@ -345,19 +781,16 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid)
structures), but keeps others (e.g., stanza queue and stream ID,
since we may resume the stream afterwards). */
void rexmpp_cleanup (rexmpp_t *s) {
- if (s->tls_state != REXMPP_TLS_INACTIVE &&
- s->tls_state != REXMPP_TLS_AWAITING_DIRECT) {
- gnutls_deinit(s->gnutls_session);
- }
+ rexmpp_tls_cleanup(s);
s->tls_state = REXMPP_TLS_INACTIVE;
if (s->sasl_state != REXMPP_SASL_INACTIVE) {
- gsasl_finish(s->sasl_session);
- s->sasl_session = NULL;
+ rexmpp_sasl_ctx_cleanup(s);
s->sasl_state = REXMPP_SASL_INACTIVE;
}
if (s->tcp_state == REXMPP_TCP_CONNECTING) {
int sock = rexmpp_tcp_conn_finish(&s->server_connection);
if (sock != -1) {
+ rexmpp_log(s, LOG_DEBUG, "TCP disconnected");
close(sock);
}
s->tcp_state = REXMPP_TCP_NONE;
@@ -372,90 +805,136 @@ void rexmpp_cleanup (rexmpp_t *s) {
s->send_buffer = NULL;
}
if (s->stream_features != NULL) {
- xmlFreeNode(s->stream_features);
+ rexmpp_xml_free(s->stream_features);
s->stream_features = NULL;
}
- while (s->send_queue != NULL) {
- xmlNodePtr next = xmlNextElementSibling(s->send_queue);
- xmlFreeNode(s->send_queue);
- s->send_queue = next;
+ if (s->send_queue != NULL) {
+ rexmpp_xml_free_list(s->send_queue);
+ s->send_queue = NULL;
}
if (s->current_element_root != NULL) {
- xmlFreeNode(s->current_element_root);
+ rexmpp_xml_free_list(s->current_element_root);
s->current_element_root = NULL;
s->current_element = NULL;
}
+ if (s->input_queue != NULL) {
+ rexmpp_xml_free_list(s->input_queue);
+ s->input_queue = NULL;
+ s->input_queue_last = NULL;
+ }
if (s->server_srv != NULL) {
- ares_free_data(s->server_srv);
+ rexmpp_dns_result_free(s->server_srv);
s->server_srv = NULL;
- s->server_srv_cur = NULL;
+ s->server_srv_cur = -1;
}
if (s->server_srv_tls != NULL) {
- ares_free_data(s->server_srv_tls);
+ rexmpp_dns_result_free(s->server_srv_tls);
s->server_srv_tls = NULL;
- s->server_srv_tls_cur = NULL;
+ s->server_srv_tls_cur = -1;
}
s->sm_state = REXMPP_SM_INACTIVE;
s->ping_requested = 0;
}
+void rexmpp_iq_finish (rexmpp_t *s,
+ rexmpp_iq_t *iq,
+ int success,
+ rexmpp_xml_t *response)
+{
+ if (iq->cb != NULL) {
+ iq->cb(s, iq->cb_data, iq->request, response, success);
+ }
+ rexmpp_xml_free(iq->request);
+ free(iq);
+}
+
/* Frees the things that persist through reconnects. */
void rexmpp_done (rexmpp_t *s) {
+ rexmpp_jingle_stop(s);
rexmpp_cleanup(s);
- gsasl_done(s->sasl_ctx);
- gnutls_certificate_free_credentials(s->gnutls_cred);
- ares_destroy(s->resolver_channel);
- ares_library_cleanup();
- xmlFreeParserCtxt(s->xml_parser);
- if (s->initial_jid != NULL) {
- free(s->initial_jid);
- s->initial_jid = NULL;
+#ifdef HAVE_CURL
+ curl_multi_cleanup(s->curl_multi);
+ curl_global_cleanup();
+#endif
+#ifdef HAVE_GPGME
+ gpgme_release(s->pgp_ctx);
+#endif
+ rexmpp_sasl_ctx_deinit(s);
+ rexmpp_tls_deinit(s);
+ rexmpp_dns_ctx_deinit(s);
+ rexmpp_xml_parser_free(s->xml_parser);
+ if (s->jingle_rtp_description != NULL) {
+ rexmpp_xml_free(s->jingle_rtp_description);
+ s->jingle_rtp_description = NULL;
}
if (s->stream_id != NULL) {
free(s->stream_id);
s->stream_id = NULL;
}
if (s->roster_items != NULL) {
- xmlFreeNodeList(s->roster_items);
+ rexmpp_xml_free_list(s->roster_items);
s->roster_items = NULL;
}
+ if (s->roster_presence != NULL) {
+ rexmpp_xml_free_list(s->roster_presence);
+ s->roster_presence = NULL;
+ }
+ if (s->roster_events != NULL) {
+ rexmpp_xml_free_list(s->roster_events);
+ s->roster_events = NULL;
+ }
if (s->roster_ver != NULL) {
free(s->roster_ver);
s->roster_ver = NULL;
}
+ while (s->muc_ping != NULL) {
+ rexmpp_muc_ping_t *mp_next = s->muc_ping->next;
+ free(s->muc_ping->jid);
+ free(s->muc_ping);
+ s->muc_ping = mp_next;
+ }
if (s->disco_info != NULL) {
- xmlFreeNodeList(s->disco_info);
+ rexmpp_xml_free_list(s->disco_info);
s->disco_info = NULL;
}
- while (s->stanza_queue != NULL) {
- xmlNodePtr next = xmlNextElementSibling(s->stanza_queue);
- xmlFreeNode(s->send_queue);
- s->send_queue = next;
+ if (s->stanza_queue != NULL) {
+ rexmpp_xml_free_list(s->stanza_queue);
+ s->stanza_queue = NULL;
}
while (s->active_iq != NULL) {
rexmpp_iq_t *next = s->active_iq->next;
- xmlFreeNode(s->active_iq->request);
- free(s->active_iq);
+ rexmpp_iq_t *iq = s->active_iq;
s->active_iq = next;
+ rexmpp_iq_finish(s, iq, 0, NULL);
}
- if (s->tls_session_data != NULL) {
- free(s->tls_session_data);
+ if (s->iq_cache != NULL) {
+ rexmpp_xml_free_list(s->iq_cache);
+ s->iq_cache = NULL;
}
}
void rexmpp_schedule_reconnect (rexmpp_t *s) {
+ if (s->stream_state == REXMPP_STREAM_CLOSE_REQUESTED ||
+ s->stream_state == REXMPP_STREAM_CLOSING) {
+ /* Don't schedule a reconnect if a reconnect-causing condition
+ happened during closing. */
+ return;
+ }
if (s->reconnect_number == 0) {
- gnutls_rnd(GNUTLS_RND_NONCE, &s->reconnect_seconds, sizeof(time_t));
+ rexmpp_random_buf((char*)&s->reconnect_seconds, sizeof(time_t));
if (s->reconnect_seconds < 0) {
s->reconnect_seconds = - s->reconnect_seconds;
}
s->reconnect_seconds %= 60;
}
- time_t seconds = s->reconnect_seconds << s->reconnect_number;
+ time_t seconds = 3600;
+ if (s->reconnect_number <= 12) {
+ seconds = s->reconnect_seconds << s->reconnect_number;
+ }
if (seconds > 3600) {
seconds = 3600;
}
- gettimeofday(&(s->next_reconnect_time), NULL);
+ clock_gettime(CLOCK_MONOTONIC, &(s->next_reconnect_time));
s->next_reconnect_time.tv_sec += seconds;
rexmpp_log(s, LOG_DEBUG, "Scheduled reconnect number %d, in %d seconds",
s->reconnect_number,
@@ -473,100 +952,24 @@ const char *jid_bare_to_host (const char *jid_bare) {
return NULL;
}
-xmlNodePtr rexmpp_xml_add_id (rexmpp_t *s, xmlNodePtr node) {
- char buf[11];
- snprintf(buf, 11, "%u", s->id_counter);
- s->id_counter++;
- xmlNewProp(node, "id", buf);
- return node;
-}
-
-unsigned int rexmpp_xml_siblings_count (xmlNodePtr node) {
- unsigned int i;
- for (i = 0; node != NULL; i++) {
- node = xmlNextElementSibling(node);
- }
- return i;
-}
-
-int rexmpp_xml_match (xmlNodePtr node,
- const char *namespace,
- const char *name)
-{
- if (node == NULL) {
- return 0;
- }
- if (name != NULL) {
- if (strcmp(name, node->name) != 0) {
- return 0;
- }
- }
- if (namespace != NULL) {
- if (node->ns == NULL) {
- if (strcmp(namespace, "jabber:client") != 0) {
- return 0;
- }
- } else {
- if (strcmp(namespace, node->ns->href) != 0) {
- return 0;
- }
- }
- }
- return 1;
-}
-
-xmlNodePtr rexmpp_xml_find_child (xmlNodePtr node,
- const char *namespace,
- const char *name)
-{
- if (node == NULL) {
- return NULL;
- }
- xmlNodePtr child;
- for (child = xmlFirstElementChild(node);
- child != NULL;
- child = xmlNextElementSibling(child))
- {
- if (rexmpp_xml_match(child, namespace, name)) {
- return child;
- }
- }
- return NULL;
-}
-
-xmlNodePtr rexmpp_xml_set_delay (rexmpp_t *s, xmlNodePtr node) {
+rexmpp_xml_t *rexmpp_xml_set_delay (rexmpp_t *s, rexmpp_xml_t *node) {
if (rexmpp_xml_find_child (node, NULL, "delay")) {
return node;
}
char buf[42];
time_t t = time(NULL);
- struct tm *local_time = localtime(&t);
- strftime(buf, 42, "%FT%T%z", local_time);
- xmlNodePtr delay = xmlNewChild(node, NULL, "delay", NULL);
- xmlNewProp(delay, "stamp", buf);
- if (s != NULL && s->assigned_jid != NULL) {
- xmlNewProp(delay, "from", s->assigned_jid);
+ struct tm utc_time;
+ gmtime_r(&t, &utc_time);
+ strftime(buf, 42, "%FT%TZ", &utc_time);
+ rexmpp_xml_t *delay = rexmpp_xml_new_elem("delay", NULL);
+ rexmpp_xml_add_child(node, delay);
+ rexmpp_xml_add_attr(delay, "stamp", buf);
+ if (s != NULL && s->assigned_jid.full[0]) {
+ rexmpp_xml_add_attr(delay, "from", s->assigned_jid.full);
}
return node;
}
-char *rexmpp_xml_serialize(xmlNodePtr node) {
- xmlBufferPtr buf = xmlBufferCreate();
- xmlSaveCtxtPtr ctx = xmlSaveToBuffer(buf, "utf-8", 0);
- xmlSaveTree(ctx, node);
- xmlSaveFlush(ctx);
- unsigned char *out = xmlBufferDetach(buf);
- xmlBufferFree(buf);
- return out;
-}
-
-int rexmpp_xml_is_stanza (xmlNodePtr node) {
- return rexmpp_xml_match(node, "jabber:client", "message") ||
- rexmpp_xml_match(node, "jabber:client", "iq") ||
- rexmpp_xml_match(node, "jabber:client", "presence");
-}
-
-
rexmpp_err_t rexmpp_send_start (rexmpp_t *s, const void *data, size_t data_len)
{
int sasl_err;
@@ -575,11 +978,9 @@ rexmpp_err_t rexmpp_send_start (rexmpp_t *s, const void *data, size_t data_len)
return REXMPP_E_SEND_BUFFER_NOT_EMPTY;
}
if (s->sasl_state == REXMPP_SASL_ACTIVE) {
- sasl_err = gsasl_encode (s->sasl_session, data, data_len,
- &(s->send_buffer), &(s->send_buffer_len));
- if (sasl_err != GSASL_OK) {
- rexmpp_log(s, LOG_ERR, "SASL encoding error: %s",
- gsasl_strerror(sasl_err));
+ sasl_err = rexmpp_sasl_encode (s, data, data_len,
+ &(s->send_buffer), &(s->send_buffer_len));
+ if (sasl_err) {
s->sasl_state = REXMPP_SASL_ERROR;
return REXMPP_E_SASL;
}
@@ -592,7 +993,7 @@ rexmpp_err_t rexmpp_send_start (rexmpp_t *s, const void *data, size_t data_len)
s->send_buffer_len = data_len;
}
s->send_buffer_sent = 0;
- return REXMPP_E_AGAIN;
+ return REXMPP_SUCCESS;
}
rexmpp_err_t rexmpp_send_continue (rexmpp_t *s)
@@ -601,109 +1002,118 @@ rexmpp_err_t rexmpp_send_continue (rexmpp_t *s)
rexmpp_log(s, LOG_ERR, "nothing to send");
return REXMPP_E_SEND_BUFFER_EMPTY;
}
- int ret;
+ ssize_t ret;
+ rexmpp_tls_err_t err;
while (1) {
if (s->tls_state == REXMPP_TLS_ACTIVE) {
- ret = gnutls_record_send (s->gnutls_session,
- s->send_buffer,
- s->send_buffer_len);
- } else {
- ret = send (s->server_socket,
- s->send_buffer + s->send_buffer_sent,
- s->send_buffer_len - s->send_buffer_sent,
- 0);
- }
- if (ret > 0) {
- s->last_network_activity = time(NULL);
- s->send_buffer_sent += ret;
- if (s->send_buffer_sent == s->send_buffer_len) {
- free(s->send_buffer);
- s->send_buffer = NULL;
- if (s->send_queue != NULL) {
- xmlNodePtr node = s->send_queue;
- unsigned char *buf = rexmpp_xml_serialize(node);
- ret = rexmpp_send_start(s, buf, strlen(buf));
- free(buf);
- if (ret != REXMPP_E_AGAIN) {
- return ret;
- }
- s->send_queue = xmlNextElementSibling(s->send_queue);
- xmlFreeNode(node);
- } else {
- return REXMPP_SUCCESS;
- }
- }
- } else {
- if (s->tls_state == REXMPP_TLS_ACTIVE) {
- if (ret != GNUTLS_E_AGAIN) {
+ err = rexmpp_tls_send (s,
+ s->tls,
+ s->send_buffer,
+ s->send_buffer_len,
+ &ret);
+ if (ret <= 0) {
+ if (err != REXMPP_TLS_E_AGAIN) {
s->tls_state = REXMPP_TLS_ERROR;
/* Assume a TCP error for now as well. */
- rexmpp_log(s, LOG_ERR, "TLS send error: %s", gnutls_strerror(ret));
rexmpp_cleanup(s);
s->tcp_state = REXMPP_TCP_ERROR;
rexmpp_schedule_reconnect(s);
- return REXMPP_E_TLS;
}
- } else {
+ /* Returning E_AGAIN for now, since the error is potentially
+ recoverable after the scheduled reconnect. */
+ return REXMPP_E_AGAIN;
+ }
+ } else {
+ ret = send (s->server_socket,
+ s->send_buffer + s->send_buffer_sent,
+ s->send_buffer_len - s->send_buffer_sent,
+ 0);
+ if (ret <= 0) {
if (errno != EAGAIN) {
rexmpp_log(s, LOG_ERR, "TCP send error: %s", strerror(errno));
rexmpp_cleanup(s);
s->tcp_state = REXMPP_TCP_ERROR;
rexmpp_schedule_reconnect(s);
- return REXMPP_E_TCP;
}
+ /* E_AGAIN, similarly to the TLS case. */
+ return REXMPP_E_AGAIN;
+ }
+ }
+
+ assert(ret > 0);
+
+ clock_gettime(CLOCK_MONOTONIC, &(s->last_network_activity));
+ s->send_buffer_sent += ret;
+ if (s->send_buffer_sent == s->send_buffer_len) {
+ free(s->send_buffer);
+ s->send_buffer = NULL;
+ if (s->send_queue != NULL) {
+ rexmpp_xml_t *node = s->send_queue;
+ char *buf = rexmpp_xml_serialize(node, 0);
+ ret = rexmpp_send_start(s, buf, strlen(buf));
+ free(buf);
+ if (ret != REXMPP_SUCCESS) {
+ return ret;
+ }
+ s->send_queue = s->send_queue->next;
+ rexmpp_xml_free(node);
+ } else {
+ return REXMPP_SUCCESS;
}
- return REXMPP_E_AGAIN;
}
+
}
}
rexmpp_err_t rexmpp_send_raw (rexmpp_t *s, const void *data, size_t data_len)
{
int ret = rexmpp_send_start(s, data, data_len);
- if (ret != REXMPP_E_AGAIN) {
- return ret;
+ if (ret == REXMPP_SUCCESS) {
+ ret = rexmpp_send_continue(s);
}
- return rexmpp_send_continue(s);
+ return ret;
}
rexmpp_err_t rexmpp_sm_send_req (rexmpp_t *s);
-rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node)
+rexmpp_err_t rexmpp_send (rexmpp_t *s, rexmpp_xml_t *node)
{
int need_ack = 0;
int ret;
if (s->xml_out_cb != NULL && s->xml_out_cb(s, node) == 1) {
- xmlFreeNode(node);
+ rexmpp_xml_free(node);
rexmpp_log(s, LOG_WARNING, "Message sending was cancelled by xml_out_cb.");
return REXMPP_E_CANCELLED;
}
if (rexmpp_xml_siblings_count(s->send_queue) >= s->send_queue_size) {
- xmlFreeNode(node);
+ rexmpp_xml_free(node);
rexmpp_log(s, LOG_ERR, "The send queue is full, not sending.");
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 >=
- s->stanza_queue_size) {
- xmlFreeNode(node);
+ if (s->stanzas_out_count >=
+ s->stanza_queue_size + s->stanzas_out_acknowledged) {
+ rexmpp_xml_free(node);
rexmpp_log(s, LOG_ERR, "The stanza queue is full, not sending.");
return REXMPP_E_STANZA_QUEUE_FULL;
}
need_ack = 1;
- xmlNodePtr queued_stanza = rexmpp_xml_set_delay(s, xmlCopyNode(node, 1));
+ rexmpp_xml_t *queued_stanza =
+ rexmpp_xml_set_delay(s, rexmpp_xml_clone(node));
if (s->stanza_queue == NULL) {
s->stanza_queue = queued_stanza;
} else {
- xmlNodePtr last = s->stanza_queue;
- while (xmlNextElementSibling(last) != NULL) {
- last = xmlNextElementSibling(last);
+ rexmpp_xml_t *last = s->stanza_queue;
+ while (last->next != NULL) {
+ last = last->next;
}
- xmlAddNextSibling(last, queued_stanza);
+ last->next = queued_stanza;
}
}
if (s->sm_state != REXMPP_SM_INACTIVE) {
@@ -712,10 +1122,10 @@ rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node)
}
if (s->send_buffer == NULL) {
- unsigned char *buf = rexmpp_xml_serialize(node);
+ char *buf = rexmpp_xml_serialize(node, 0);
ret = rexmpp_send_raw(s, buf, strlen(buf));
free(buf);
- xmlFreeNode(node);
+ rexmpp_xml_free(node);
if (ret != REXMPP_SUCCESS && ret != REXMPP_E_AGAIN) {
return ret;
}
@@ -723,11 +1133,11 @@ rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node)
if (s->send_queue == NULL) {
s->send_queue = node;
} else {
- xmlNodePtr last = s->send_queue;
- while (xmlNextElementSibling(last) != NULL) {
- last = xmlNextElementSibling(last);
+ rexmpp_xml_t *last = s->send_queue;
+ while (last->next != NULL) {
+ last = last->next;
}
- xmlAddNextSibling(last, node);
+ last->next = node;
}
ret = REXMPP_E_AGAIN;
}
@@ -738,37 +1148,35 @@ rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node)
}
void rexmpp_iq_reply (rexmpp_t *s,
- xmlNodePtr req,
+ rexmpp_xml_t *req,
const char *type,
- xmlNodePtr payload)
+ rexmpp_xml_t *payload)
{
- xmlNodePtr iq_stanza = xmlNewNode(NULL, "iq");
- xmlNewNs(iq_stanza, "jabber:client", NULL);
- xmlNewProp(iq_stanza, "type", type);
- char *id = xmlGetProp(req, "id");
+ rexmpp_xml_t *iq_stanza = rexmpp_xml_new_elem("iq", "jabber:client");
+ rexmpp_xml_add_attr(iq_stanza, "type", type);
+ const char *id = rexmpp_xml_find_attr_val(req, "id");
if (id != NULL) {
- xmlNewProp(iq_stanza, "id", id);
- free(id);
+ rexmpp_xml_add_attr(iq_stanza, "id", id);
}
- char *to = xmlGetProp(req, "from");
+ const char *to = rexmpp_xml_find_attr_val(req, "from");
if (to != NULL) {
- xmlNewProp(iq_stanza, "to", to);
- free(to);
+ rexmpp_xml_add_attr(iq_stanza, "to", to);
}
- if (s->assigned_jid != NULL) {
- xmlNewProp(iq_stanza, "from", s->assigned_jid);
+ if (s->assigned_jid.full[0]) {
+ rexmpp_xml_add_attr(iq_stanza, "from", s->assigned_jid.full);
}
if (payload != NULL) {
- xmlAddChild(iq_stanza, payload);
+ rexmpp_xml_add_child(iq_stanza, payload);
}
rexmpp_send(s, iq_stanza);
}
-void rexmpp_iq_new (rexmpp_t *s,
- const char *type,
- const char *to,
- xmlNodePtr payload,
- rexmpp_iq_callback_t cb)
+rexmpp_err_t rexmpp_iq_new (rexmpp_t *s,
+ const char *type,
+ const char *to,
+ rexmpp_xml_t *payload,
+ rexmpp_iq_callback_t cb,
+ void *cb_data)
{
unsigned int i;
rexmpp_iq_t *prev = NULL, *last = s->active_iq;
@@ -777,107 +1185,211 @@ void rexmpp_iq_new (rexmpp_t *s,
last = last->next;
}
if (i >= s->iq_queue_size && s->iq_queue_size > 0) {
+ assert(prev != NULL);
+ assert(last != NULL);
rexmpp_log(s, LOG_WARNING,
"The IQ queue limit is reached, giving up on the oldest IQ.");
prev->next = NULL;
- if (last->cb != NULL) {
- last->cb(s, last->request, NULL, 0);
- }
- xmlFreeNode(last->request);
- free(last);
+ rexmpp_iq_finish(s, last, 0, NULL);
}
- xmlNodePtr iq_stanza = rexmpp_xml_add_id(s, xmlNewNode(NULL, "iq"));
- xmlNewNs(iq_stanza, "jabber:client", NULL);
- xmlNewProp(iq_stanza, "type", type);
+ rexmpp_xml_t *iq_stanza =
+ rexmpp_xml_new_elem("iq", "jabber:client");
+ rexmpp_xml_add_id(iq_stanza);
+ rexmpp_xml_add_attr(iq_stanza, "type", type);
if (to != NULL) {
- xmlNewProp(iq_stanza, "to", to);
+ rexmpp_xml_add_attr(iq_stanza, "to", to);
}
- if (s->assigned_jid != NULL) {
- xmlNewProp(iq_stanza, "from", s->assigned_jid);
+ if (s->assigned_jid.full[0]) {
+ rexmpp_xml_add_attr(iq_stanza, "from", s->assigned_jid.full);
}
- xmlAddChild(iq_stanza, payload);
+ rexmpp_xml_add_child(iq_stanza, payload);
rexmpp_iq_t *iq = malloc(sizeof(rexmpp_iq_t));
- iq->request = xmlCopyNode(iq_stanza, 1);
+ if (iq == NULL) {
+ return REXMPP_E_MALLOC;
+ }
+ iq->request = rexmpp_xml_clone(iq_stanza);
iq->cb = cb;
+ iq->cb_data = cb_data;
iq->next = s->active_iq;
s->active_iq = iq;
- rexmpp_send(s, iq_stanza);
+ return rexmpp_send(s, iq_stanza);
}
+void rexmpp_iq_cache_cb (rexmpp_t *s,
+ void *cb_data,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ if (success && response != NULL) {
+ rexmpp_xml_t *prev_last = NULL, *last = NULL, *ciq = s->iq_cache;
+ uint32_t size = 0;
+ while (ciq != NULL && ciq->next != NULL) {
+ prev_last = last;
+ last = ciq;
+ size++;
+ ciq = ciq->next->next;
+ }
+ if (size >= s->iq_queue_size && prev_last != NULL) {
+ rexmpp_xml_free(last->next);
+ rexmpp_xml_free(last);
+ prev_last->next->next = NULL;
+ }
+ rexmpp_xml_t *req = rexmpp_xml_clone(request);
+ rexmpp_xml_t *resp = rexmpp_xml_clone(response);
+ req->next = resp;
+ resp->next = s->iq_cache;
+ s->iq_cache = req;
+ }
+ struct rexmpp_iq_cacher *cacher = cb_data;
+ if (cacher->cb != NULL) {
+ cacher->cb(s, cacher->cb_data, request, response, success);
+ }
+ free(cacher);
+}
+
+rexmpp_err_t rexmpp_cached_iq_new (rexmpp_t *s,
+ const char *type,
+ const char *to,
+ rexmpp_xml_t *payload,
+ rexmpp_iq_callback_t cb,
+ void *cb_data,
+ int fresh)
+{
+ if (! fresh) {
+ rexmpp_xml_t *ciq = s->iq_cache;
+ while (ciq != NULL && ciq->next != NULL) {
+ rexmpp_xml_t *ciq_pl = ciq->alt.elem.children;
+ const char *ciq_type = rexmpp_xml_find_attr_val(ciq, "type");
+ const char *ciq_to = rexmpp_xml_find_attr_val(ciq, "to");
+ int matches = (rexmpp_xml_eq(ciq_pl, payload) &&
+ strcmp(ciq_type, type) == 0 &&
+ strcmp(ciq_to, to) == 0);
+ if (matches) {
+ rexmpp_xml_free(payload);
+ if (cb != NULL) {
+ cb(s, cb_data, ciq, ciq->next, 1);
+ }
+ return REXMPP_SUCCESS;
+ }
+ ciq = ciq->next->next;
+ }
+ }
+ struct rexmpp_iq_cacher *cacher = malloc(sizeof(struct rexmpp_iq_cacher));
+ cacher->cb = cb;
+ cacher->cb_data = cb_data;
+ return rexmpp_iq_new(s, type, to, payload, rexmpp_iq_cache_cb, cacher);
+}
+
+
rexmpp_err_t rexmpp_sm_ack (rexmpp_t *s) {
char buf[11];
- xmlNodePtr ack = xmlNewNode(NULL, "a");
- xmlNewNs(ack, "urn:xmpp:sm:3", NULL);
+ rexmpp_xml_t *ack = rexmpp_xml_new_elem("a", "urn:xmpp:sm:3");
snprintf(buf, 11, "%u", s->stanzas_in_count);
- xmlNewProp(ack, "h", buf);
+ rexmpp_xml_add_attr(ack, "h", buf);
return rexmpp_send(s, ack);
}
rexmpp_err_t rexmpp_sm_send_req (rexmpp_t *s) {
- xmlNodePtr ack = xmlNewNode(NULL, "r");
- xmlNewNs(ack, "urn:xmpp:sm:3", NULL);
- return rexmpp_send(s, ack);
+ rexmpp_xml_t *req = rexmpp_xml_new_elem("r", "urn:xmpp:sm:3");
+ return rexmpp_send(s, req);
}
-void rexmpp_recv (rexmpp_t *s) {
+rexmpp_err_t rexmpp_process_element (rexmpp_t *s, rexmpp_xml_t *elem);
+
+rexmpp_err_t rexmpp_recv (rexmpp_t *s) {
char chunk_raw[4096], *chunk;
- ssize_t chunk_raw_len, chunk_len;
+ size_t chunk_len;
+ ssize_t chunk_raw_len;
int sasl_err;
+ rexmpp_tls_err_t recv_err;
+ rexmpp_err_t err = REXMPP_SUCCESS;
+ int tls_was_active;
/* Loop here in order to consume data from TLS buffers, which
wouldn't show up on select(). */
do {
- if (s->tls_state == REXMPP_TLS_ACTIVE) {
- chunk_raw_len = gnutls_record_recv(s->gnutls_session, chunk_raw, 4096);
+ tls_was_active = (s->tls_state == REXMPP_TLS_ACTIVE);
+ if (tls_was_active) {
+ recv_err = rexmpp_tls_recv(s, s->tls, chunk_raw, 4096, &chunk_raw_len);
} else {
chunk_raw_len = recv(s->server_socket, chunk_raw, 4096, 0);
}
if (chunk_raw_len > 0) {
- s->last_network_activity = time(NULL);
+ clock_gettime(CLOCK_MONOTONIC, &(s->last_network_activity));
if (s->sasl_state == REXMPP_SASL_ACTIVE) {
- sasl_err = gsasl_decode(s->sasl_session, chunk_raw, chunk_raw_len,
- &chunk, &chunk_len);
- if (sasl_err != GSASL_OK) {
- rexmpp_log(s, LOG_ERR, "SASL decoding error: %s",
- gsasl_strerror(sasl_err));
+ sasl_err = rexmpp_sasl_decode(s, chunk_raw, chunk_raw_len,
+ &chunk, &chunk_len);
+ if (sasl_err) {
s->sasl_state = REXMPP_SASL_ERROR;
- return;
+ return REXMPP_E_SASL;
}
} else {
chunk = chunk_raw;
chunk_len = chunk_raw_len;
}
- xmlParseChunk(s->xml_parser, chunk, chunk_len, 0);
+ rexmpp_xml_parser_feed(s->xml_parser, chunk, chunk_len, 0);
+ if (chunk != chunk_raw && chunk != NULL) {
+ free(chunk);
+ }
+ chunk = NULL;
+
+ rexmpp_xml_t *elem;
+ for (elem = s->input_queue;
+ /* Skipping everything after an error. Might be better to
+ process it anyway, but it could lead to more errors if
+ the processing isn't done carefully. */
+ elem != NULL && (err == REXMPP_SUCCESS || err == REXMPP_E_AGAIN);
+ elem = elem->next)
+ {
+ if (s->xml_in_cb != NULL && s->xml_in_cb(s, elem) != 0) {
+ rexmpp_log(s, LOG_WARNING,
+ "Message processing was cancelled by xml_in_cb.");
+ } else {
+ err = rexmpp_process_element(s, elem);
+ }
+ }
+ rexmpp_xml_free_list(s->input_queue);
+ s->input_queue = NULL;
+ s->input_queue_last = NULL;
+ if (err != REXMPP_SUCCESS && err != REXMPP_E_AGAIN) {
+ return err;
+ }
} else if (chunk_raw_len == 0) {
- if (s->tls_state == REXMPP_TLS_ACTIVE) {
+ if (tls_was_active) {
s->tls_state = REXMPP_TLS_CLOSED;
- rexmpp_log(s, LOG_INFO, "TLS disconnected");
+ rexmpp_log(s, LOG_DEBUG, "TLS disconnected");
}
- rexmpp_log(s, LOG_INFO, "TCP disconnected");
+ rexmpp_log(s, LOG_DEBUG, "TCP disconnected");
rexmpp_cleanup(s);
- s->tcp_state = REXMPP_TCP_CLOSED;
- if (s->stream_state == REXMPP_STREAM_READY) {
+ if (s->stream_state == REXMPP_STREAM_READY ||
+ s->stream_state == REXMPP_STREAM_ERROR_RECONNECT) {
+ s->tcp_state = REXMPP_TCP_NONE;
rexmpp_schedule_reconnect(s);
+ return REXMPP_E_AGAIN;
+ } else {
+ s->tcp_state = REXMPP_TCP_CLOSED;
}
} else {
- if (s->tls_state == REXMPP_TLS_ACTIVE) {
- if (chunk_raw_len != GNUTLS_E_AGAIN) {
+ if (tls_was_active) {
+ if (recv_err != REXMPP_TLS_E_AGAIN) {
s->tls_state = REXMPP_TLS_ERROR;
/* Assume a TCP error for now as well. */
- rexmpp_log(s, LOG_ERR, "TLS recv error: %s",
- gnutls_strerror(chunk_raw_len));
rexmpp_cleanup(s);
s->tcp_state = REXMPP_TCP_ERROR;
rexmpp_schedule_reconnect(s);
+ return REXMPP_E_AGAIN;
}
} else if (errno != EAGAIN) {
rexmpp_log(s, LOG_ERR, "TCP recv error: %s", strerror(errno));
rexmpp_cleanup(s);
s->tcp_state = REXMPP_TCP_ERROR;
rexmpp_schedule_reconnect(s);
+ return REXMPP_E_AGAIN;
}
}
} while (chunk_raw_len > 0 && s->tcp_state == REXMPP_TCP_CONNECTED);
+ return err;
}
rexmpp_err_t rexmpp_stream_open (rexmpp_t *s) {
@@ -887,203 +1399,153 @@ rexmpp_err_t rexmpp_stream_open (rexmpp_t *s) {
"<stream:stream to='%s' version='1.0' "
"xml:lang='en' xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams'>",
- jid_bare_to_host(s->initial_jid));
+ s->initial_jid.domain);
s->stream_state = REXMPP_STREAM_OPENING;
return rexmpp_send_raw(s, buf, strlen(buf));
}
-void rexmpp_process_conn_err (rexmpp_t *s, enum rexmpp_tcp_conn_error err);
+rexmpp_err_t
+rexmpp_process_conn_err (rexmpp_t *s, enum rexmpp_tcp_conn_error err);
-void rexmpp_start_connecting (rexmpp_t *s) {
+rexmpp_err_t rexmpp_start_connecting (rexmpp_t *s) {
if (s->socks_host == NULL) {
rexmpp_log(s, LOG_DEBUG, "Connecting to %s:%u",
s->server_host, s->server_port);
- rexmpp_process_conn_err(s,
- rexmpp_tcp_conn_init(&s->server_connection,
- s->server_host,
- s->server_port));
+ return
+ rexmpp_process_conn_err(s,
+ rexmpp_tcp_conn_init(s,
+ &s->server_connection,
+ s->server_host,
+ s->server_port));
} else {
rexmpp_log(s, LOG_DEBUG, "Connecting to %s:%u via %s:%u",
s->server_host, s->server_port,
s->socks_host, s->socks_port);
- rexmpp_process_conn_err(s,
- rexmpp_tcp_conn_init(&s->server_connection,
- s->socks_host,
- s->socks_port));
+ return rexmpp_process_conn_err(s,
+ rexmpp_tcp_conn_init(s,
+ &s->server_connection,
+ s->socks_host,
+ s->socks_port));
}
}
-void rexmpp_try_next_host (rexmpp_t *s) {
+rexmpp_err_t rexmpp_try_next_host (rexmpp_t *s) {
+ rexmpp_dns_result_t *cur_result;
+ int cur_number;
/* todo: check priorities and weights */
s->tls_state = REXMPP_TLS_INACTIVE;
- if (s->server_srv_tls != NULL && s->server_srv_tls_cur == NULL) {
+ if (s->server_srv_tls != NULL && s->server_srv_tls_cur == -1) {
/* We have xmpps-client records available, but haven't tried any
of them yet. */
- s->server_srv_tls_cur = s->server_srv_tls;
- s->server_host = s->server_srv_tls_cur->host;
- s->server_port = s->server_srv_tls_cur->port;
+ s->server_srv_tls_cur = 0;
+ cur_result = s->server_srv_tls;
+ cur_number = s->server_srv_tls_cur;
s->tls_state = REXMPP_TLS_AWAITING_DIRECT;
- } else if (s->server_srv_tls_cur != NULL &&
- s->server_srv_tls_cur->next != NULL) {
+ } else if (s->server_srv_tls_cur != -1 &&
+ s->server_srv_tls->data[s->server_srv_tls_cur + 1] != NULL) {
/* We have tried some xmpps-client records, but there is more. */
- s->server_srv_tls_cur = s->server_srv_tls_cur->next;
- s->server_host = s->server_srv_tls_cur->host;
- s->server_port = s->server_srv_tls_cur->port;
+ s->server_srv_tls_cur++;
+ cur_result = s->server_srv_tls;
+ cur_number = s->server_srv_tls_cur;
s->tls_state = REXMPP_TLS_AWAITING_DIRECT;
- } else if (s->server_srv != NULL && s->server_srv_cur == NULL) {
+ } else if (s->server_srv != NULL && s->server_srv_cur == -1) {
/* Starting with xmpp-client records. */
- s->server_srv_cur = s->server_srv;
- s->server_host = s->server_srv_cur->host;
- s->server_port = s->server_srv_cur->port;
- } else if (s->server_srv_tls_cur != NULL &&
- s->server_srv_tls_cur->next != NULL) {
+ s->server_srv_cur = 0;
+ cur_result = s->server_srv;
+ cur_number = s->server_srv_cur;
+ } else if (s->server_srv_cur != -1 &&
+ s->server_srv->data[s->server_srv_cur + 1] != NULL) {
/* Advancing in xmpp-client records. */
- s->server_srv_cur = s->server_srv_cur->next;
- s->server_host = s->server_srv_cur->host;
- s->server_port = s->server_srv_cur->port;
+ s->server_srv_cur++;
+ cur_result = s->server_srv;
+ cur_number = s->server_srv_cur;
} else {
/* No candidate records left to try. Schedule a reconnect. */
+ rexmpp_log(s, LOG_DEBUG,
+ "No candidate hosts left to try, scheduling a reconnect");
rexmpp_cleanup(s);
rexmpp_schedule_reconnect(s);
- return;
+ return REXMPP_E_AGAIN;
}
- rexmpp_start_connecting(s);
+
+ s->server_active_srv = (rexmpp_dns_srv_t *)cur_result->data[cur_number];
+
+ s->server_host = s->server_active_srv->target;
+ s->server_port = s->server_active_srv->port;
+ return rexmpp_start_connecting(s);
}
-rexmpp_err_t rexmpp_tls_handshake (rexmpp_t *s) {
- s->tls_state = REXMPP_TLS_HANDSHAKE;
- int ret = gnutls_handshake(s->gnutls_session);
- if (ret == GNUTLS_E_AGAIN) {
- rexmpp_log(s, LOG_DEBUG, "Waiting for TLS handshake to complete");
+rexmpp_err_t
+rexmpp_process_tls_conn_err (rexmpp_t *s,
+ rexmpp_tls_err_t err)
+{
+ if (err == REXMPP_TLS_E_OTHER) {
+ s->tls_state = REXMPP_TLS_ERROR;
+ rexmpp_cleanup(s);
+ rexmpp_schedule_reconnect(s);
return REXMPP_E_AGAIN;
- } else if (ret == 0) {
- int status;
- ret = gnutls_certificate_verify_peers3(s->gnutls_session,
- jid_bare_to_host(s->initial_jid),
- &status);
- if (ret || status) {
- s->tls_state = REXMPP_TLS_ERROR;
- if (ret) {
- rexmpp_log(s, LOG_ERR, "Certificate parsing error: %s",
- gnutls_strerror(ret));
- } else if (status & GNUTLS_CERT_UNEXPECTED_OWNER) {
- rexmpp_log(s, LOG_ERR, "Unexpected certificate owner");
- } else {
- rexmpp_log(s, LOG_ERR, "Untrusted certificate");
- }
- gnutls_bye(s->gnutls_session, GNUTLS_SHUT_RDWR);
- rexmpp_cleanup(s);
- rexmpp_schedule_reconnect(s);
- return REXMPP_E_TLS;
- }
+ } else if (err == REXMPP_TLS_SUCCESS) {
+ rexmpp_log(s, LOG_DEBUG, "A TLS connection is established");
s->tls_state = REXMPP_TLS_ACTIVE;
- rexmpp_log(s, LOG_DEBUG, "TLS ready");
-
- if (gnutls_session_is_resumed(s->gnutls_session)) {
- rexmpp_log(s, LOG_INFO, "TLS session is resumed");
- } else {
- if (s->tls_session_data != NULL) {
- rexmpp_log(s, LOG_DEBUG, "TLS session is not resumed");
- free(s->tls_session_data);
- s->tls_session_data = NULL;
- }
- gnutls_session_get_data(s->gnutls_session, NULL,
- &s->tls_session_data_size);
- s->tls_session_data = malloc(s->tls_session_data_size);
- ret = gnutls_session_get_data(s->gnutls_session, s->tls_session_data,
- &s->tls_session_data_size);
- if (ret != GNUTLS_E_SUCCESS) {
- rexmpp_log(s, LOG_ERR, "Failed to get TLS session data: %s",
- gnutls_strerror(ret));
- return REXMPP_E_TLS;
- }
- }
-
if (s->stream_state == REXMPP_STREAM_NONE) {
/* It's a direct TLS connection, so open a stream after
connecting. */
return rexmpp_stream_open(s);
} else {
/* A STARTTLS connection, restart the stream. */
- s->stream_state = REXMPP_STREAM_RESTART;
- return REXMPP_SUCCESS;
+ s->xml_parser = rexmpp_xml_parser_reset(s->xml_parser);
+ return rexmpp_stream_open(s);
}
} else {
- rexmpp_log(s, LOG_ERR, "Unexpected TLS handshake error: %s",
- gnutls_strerror(ret));
- rexmpp_cleanup(s);
- rexmpp_schedule_reconnect(s);
- return REXMPP_E_TLS;
+ s->tls_state = REXMPP_TLS_HANDSHAKE;
+ return REXMPP_E_AGAIN;
}
}
-rexmpp_err_t rexmpp_tls_start (rexmpp_t *s) {
- gnutls_datum_t xmpp_client_protocol = {"xmpp-client", strlen("xmpp-client")};
- rexmpp_log(s, LOG_DEBUG, "starting TLS");
- gnutls_init(&s->gnutls_session, GNUTLS_CLIENT);
- gnutls_session_set_ptr(s->gnutls_session, s);
- gnutls_alpn_set_protocols(s->gnutls_session, &xmpp_client_protocol, 1, 0);
- gnutls_server_name_set(s->gnutls_session, GNUTLS_NAME_DNS,
- jid_bare_to_host(s->initial_jid),
- strlen(jid_bare_to_host(s->initial_jid)));
- gnutls_set_default_priority(s->gnutls_session);
- gnutls_credentials_set(s->gnutls_session, GNUTLS_CRD_CERTIFICATE,
- s->gnutls_cred);
- gnutls_transport_set_int(s->gnutls_session, s->server_socket);
- gnutls_handshake_set_timeout(s->gnutls_session,
- GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
- if (s->tls_session_data != NULL) {
- int ret = gnutls_session_set_data(s->gnutls_session,
- s->tls_session_data,
- s->tls_session_data_size);
- if (ret != GNUTLS_E_SUCCESS) {
- rexmpp_log(s, LOG_WARNING, "Failed to set TLS session data: %s",
- gnutls_strerror(ret));
- free(s->tls_session_data);
- s->tls_session_data = NULL;
- s->tls_session_data_size = 0;
- }
- }
- s->tls_state = REXMPP_TLS_HANDSHAKE;
- return rexmpp_tls_handshake(s);
-}
-
rexmpp_err_t rexmpp_connected_to_server (rexmpp_t *s) {
s->tcp_state = REXMPP_TCP_CONNECTED;
- rexmpp_log(s, LOG_INFO, "Connected to the server");
+ rexmpp_log(s, LOG_INFO,
+ "Connected to the server, the used address record was %s",
+ s->server_socket_dns_secure ? "secure" : "not secure");
s->reconnect_number = 0;
- xmlCtxtResetPush(s->xml_parser, "", 0, "", "utf-8");
+ s->xml_parser = rexmpp_xml_parser_reset(s->xml_parser);
if (s->tls_state == REXMPP_TLS_AWAITING_DIRECT) {
- return rexmpp_tls_start(s);
+ return rexmpp_process_tls_conn_err(s, rexmpp_tls_connect(s));
} else {
return rexmpp_stream_open(s);
}
}
-void rexmpp_process_socks_err (rexmpp_t *s, enum socks_err err) {
+rexmpp_err_t rexmpp_process_socks_err (rexmpp_t *s, enum socks_err err) {
if (err == REXMPP_SOCKS_CONNECTED) {
- rexmpp_connected_to_server(s);
+ return rexmpp_connected_to_server(s);
} else if (err != REXMPP_SOCKS_E_AGAIN) {
rexmpp_log(s, LOG_ERR, "SOCKS5 connection failed.");
s->tcp_state = REXMPP_TCP_CONNECTION_FAILURE;
close(s->server_socket);
s->server_socket = -1;
- rexmpp_try_next_host(s);
+ return rexmpp_try_next_host(s);
}
+ return REXMPP_E_AGAIN;
}
-void rexmpp_process_conn_err (rexmpp_t *s, enum rexmpp_tcp_conn_error err) {
+rexmpp_err_t
+rexmpp_process_conn_err (rexmpp_t *s,
+ enum rexmpp_tcp_conn_error err)
+{
s->tcp_state = REXMPP_TCP_CONNECTING;
if (err == REXMPP_CONN_DONE) {
+ s->server_socket_dns_secure = s->server_connection.dns_secure;
s->server_socket = rexmpp_tcp_conn_finish(&s->server_connection);
if (s->socks_host == NULL) {
- rexmpp_connected_to_server(s);
+ return rexmpp_connected_to_server(s);
} else {
s->tcp_state = REXMPP_TCP_SOCKS;
- rexmpp_process_socks_err(s, rexmpp_socks_init(&s->server_socks_conn,
- s->server_socket,
- s->server_host,
- s->server_port));
+ return
+ rexmpp_process_socks_err(s, rexmpp_socks_init(&s->server_socks_conn,
+ s->server_socket,
+ s->server_host,
+ s->server_port));
}
} else if (err != REXMPP_CONN_IN_PROGRESS) {
if (err == REXMPP_CONN_ERROR) {
@@ -1092,80 +1554,45 @@ void rexmpp_process_conn_err (rexmpp_t *s, enum rexmpp_tcp_conn_error err) {
s->tcp_state = REXMPP_TCP_CONNECTION_FAILURE;
}
rexmpp_tcp_conn_finish(&s->server_connection);
- rexmpp_try_next_host(s);
+ return rexmpp_try_next_host(s);
}
+ return REXMPP_E_AGAIN;
}
-void rexmpp_after_srv (rexmpp_t *s) {
+void rexmpp_srv_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_dns_result_t *result)
+{
+ char *type = ptr;
+ if (result != NULL) {
+ rexmpp_log(s,
+ result->secure ? LOG_DEBUG : LOG_WARNING,
+ "Resolved a %s SRV record (%s)",
+ type, result->secure ? "secure" : "not secure");
+ if (strncmp("xmpp", type, 5) == 0) {
+ s->server_srv = result;
+ } else {
+ s->server_srv_tls = result;
+ }
+ }
if (s->resolver_state == REXMPP_RESOLVER_SRV) {
s->resolver_state = REXMPP_RESOLVER_SRV_2;
} else if (s->resolver_state == REXMPP_RESOLVER_SRV_2) {
s->resolver_state = REXMPP_RESOLVER_READY;
}
- if (s->resolver_state != REXMPP_RESOLVER_READY) {
- return;
- }
-
- /* todo: sort the records */
-
- if (s->server_srv == NULL && s->server_srv_tls == NULL) {
- /* Failed to resolve anything: a fallback. */
- s->server_host = jid_bare_to_host(s->initial_jid);
- s->server_port = 5222;
- rexmpp_start_connecting(s);
- } else {
- rexmpp_try_next_host(s);
- }
-}
-
-void rexmpp_srv_tls_cb (void *s_ptr,
- int status,
- int timeouts,
- unsigned char *abuf,
- int alen)
-{
- rexmpp_t *s = s_ptr;
- if (status == ARES_SUCCESS) {
- ares_parse_srv_reply(abuf, alen, &(s->server_srv_tls));
- } else {
- rexmpp_log(s, LOG_WARNING, "Failed to query an xmpps-client SRV record: %s",
- ares_strerror(status));
- }
- if (status != ARES_EDESTRUCTION) {
- rexmpp_after_srv(s);
- }
-}
-
-void rexmpp_srv_cb (void *s_ptr,
- int status,
- int timeouts,
- unsigned char *abuf,
- int alen)
-{
- rexmpp_t *s = s_ptr;
- if (status == ARES_SUCCESS) {
- ares_parse_srv_reply(abuf, alen, &(s->server_srv));
- } else {
- rexmpp_log(s, LOG_WARNING, "Failed to query an xmpp-client SRV record: %s",
- ares_strerror(status));
- }
- if (status != ARES_EDESTRUCTION) {
- rexmpp_after_srv(s);
- }
}
-
/* Should be called after reconnect, and after rexmpp_sm_handle_ack in
case of resumption. */
rexmpp_err_t rexmpp_resend_stanzas (rexmpp_t *s) {
uint32_t i, count;
rexmpp_err_t ret = REXMPP_SUCCESS;
- xmlNodePtr sq;
+ rexmpp_xml_t *sq;
count = s->stanzas_out_count - s->stanzas_out_acknowledged;
for (i = 0; i < count && s->stanza_queue != NULL; i++) {
- sq = xmlNextElementSibling(s->stanza_queue);
+ sq = s->stanza_queue->next;
ret = rexmpp_send(s, s->stanza_queue);
- if (ret != REXMPP_SUCCESS && ret != REXMPP_E_AGAIN) {
+ if (ret > REXMPP_E_AGAIN) {
return ret;
}
s->stanza_queue = sq;
@@ -1180,12 +1607,11 @@ rexmpp_err_t rexmpp_resend_stanzas (rexmpp_t *s) {
return ret;
}
-void rexmpp_sm_handle_ack (rexmpp_t *s, xmlNodePtr elem) {
- char *h = xmlGetProp(elem, "h");
+void rexmpp_sm_handle_ack (rexmpp_t *s, rexmpp_xml_t *elem) {
+ const char *h = rexmpp_xml_find_attr_val(elem, "h");
if (h != NULL) {
uint32_t prev_ack = s->stanzas_out_acknowledged;
s->stanzas_out_acknowledged = strtoul(h, NULL, 10);
- xmlFree(h);
rexmpp_log(s, LOG_DEBUG,
"server acknowledged %u out of %u sent stanzas",
s->stanzas_out_acknowledged,
@@ -1194,8 +1620,8 @@ void rexmpp_sm_handle_ack (rexmpp_t *s, xmlNodePtr elem) {
if (prev_ack <= s->stanzas_out_acknowledged) {
uint32_t i;
for (i = prev_ack; i < s->stanzas_out_acknowledged; i++) {
- xmlNodePtr sq = xmlNextElementSibling(s->stanza_queue);
- xmlFreeNode(s->stanza_queue);
+ rexmpp_xml_t *sq = s->stanza_queue->next;
+ rexmpp_xml_free(s->stanza_queue);
s->stanza_queue = sq;
}
} else {
@@ -1213,10 +1639,14 @@ void rexmpp_sm_handle_ack (rexmpp_t *s, xmlNodePtr elem) {
}
void rexmpp_carbons_enabled (rexmpp_t *s,
- xmlNodePtr req,
- xmlNodePtr response,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
int success)
{
+ (void)ptr;
+ (void)req; /* The request is always the same. */
+ (void)response; /* Only checking whether it's a success. */
if (success) {
rexmpp_log(s, LOG_INFO, "carbons enabled");
s->carbons_state = REXMPP_CARBONS_ACTIVE;
@@ -1227,44 +1657,34 @@ void rexmpp_carbons_enabled (rexmpp_t *s,
}
void rexmpp_pong (rexmpp_t *s,
- xmlNodePtr req,
- xmlNodePtr response,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
int success)
{
+ (void)ptr;
+ (void)req;
+ (void)response;
+ (void)success;
s->ping_requested = 0;
}
-void rexmpp_iq_discovery_info (rexmpp_t *s,
- xmlNodePtr req,
- xmlNodePtr response,
- int success)
-{
- if (! success) {
- rexmpp_log(s, LOG_ERR, "Failed to discover features");
- return;
- }
- xmlNodePtr query = xmlFirstElementChild(response);
- if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#info",
- "query")) {
- xmlNodePtr child;
- for (child = xmlFirstElementChild(query);
- child != NULL;
- child = xmlNextElementSibling(child))
- {
- if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#info",
- "feature")) {
- char *var = xmlGetProp(child, "var");
- if (s->enable_carbons &&
- strcmp(var, "urn:xmpp:carbons:2") == 0) {
- xmlNodePtr carbons_enable = xmlNewNode(NULL, "enable");
- xmlNewNs(carbons_enable, "urn:xmpp:carbons:2", NULL);
- s->carbons_state = REXMPP_CARBONS_NEGOTIATION;
- rexmpp_iq_new(s, "set", NULL, carbons_enable,
- rexmpp_carbons_enabled);
- }
- free(var);
- }
- }
+void rexmpp_disco_carbons_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success) {
+ (void)ptr;
+ (void)req;
+ (void)response;
+ if (success) {
+ rexmpp_xml_t *carbons_enable =
+ rexmpp_xml_new_elem("enable", "urn:xmpp:carbons:2");
+ s->carbons_state = REXMPP_CARBONS_NEGOTIATION;
+ rexmpp_iq_new(s, "set", NULL, carbons_enable,
+ rexmpp_carbons_enabled, NULL);
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Failed to discover the carbons feature.");
}
}
@@ -1272,66 +1692,76 @@ void rexmpp_stream_is_ready(rexmpp_t *s) {
s->stream_state = REXMPP_STREAM_READY;
rexmpp_resend_stanzas(s);
- if (s->enable_service_discovery) {
- xmlNodePtr disco_query = xmlNewNode(NULL, "query");
- xmlNewNs(disco_query, "http://jabber.org/protocol/disco#info", NULL);
- rexmpp_iq_new(s, "get", jid_bare_to_host(s->initial_jid),
- disco_query, rexmpp_iq_discovery_info);
+ if (s->enable_carbons) {
+ rexmpp_disco_find_feature (s, s->initial_jid.domain,
+ "urn:xmpp:carbons:2",
+ rexmpp_disco_carbons_cb,
+ NULL, 0, 1);
}
if (s->manage_roster) {
if (s->roster_cache_file != NULL) {
rexmpp_roster_cache_read(s);
}
- xmlNodePtr roster_query = xmlNewNode(NULL, "query");
- xmlNewNs(roster_query, "jabber:iq:roster", NULL);
+ rexmpp_xml_t *roster_query =
+ rexmpp_xml_new_elem("query", "jabber:iq:roster");
if (s->roster_ver != NULL) {
- xmlNewProp(roster_query, "ver", s->roster_ver);
+ rexmpp_xml_add_attr(roster_query, "ver", s->roster_ver);
} else {
- xmlNewProp(roster_query, "ver", "");
+ rexmpp_xml_add_attr(roster_query, "ver", "");
}
rexmpp_iq_new(s, "get", NULL,
- roster_query, rexmpp_iq_roster_get);
+ roster_query, rexmpp_iq_roster_get, NULL);
}
- xmlNodePtr presence = xmlNewNode(NULL, "presence");
- char *caps_hash = rexmpp_capabilities_hash(s, s->disco_info);
+ rexmpp_xml_t *presence =
+ rexmpp_xml_new_elem("presence", "jabber:client");
+ rexmpp_xml_add_id(presence);
+
+ char *caps_hash = rexmpp_capabilities_hash(s, rexmpp_disco_info(s));
if (caps_hash != NULL) {
- xmlNodePtr c = xmlNewNode(NULL, "c");
- xmlNewNs(c, "http://jabber.org/protocol/caps", NULL);
- xmlNewProp(c, "hash", "sha-1");
- xmlNewProp(c, "node", s->disco_node);
- xmlNewProp(c, "ver", caps_hash);
- xmlAddChild(presence, c);
+ rexmpp_xml_t *c =
+ rexmpp_xml_new_elem("c", "http://jabber.org/protocol/caps");
+ rexmpp_xml_add_attr(c, "hash", "sha-1");
+ rexmpp_xml_add_attr(c, "node", s->disco_node);
+ rexmpp_xml_add_attr(c, "ver", caps_hash);
+ rexmpp_xml_add_child(presence, c);
free(caps_hash);
}
+
rexmpp_send(s, presence);
}
/* Resource binding,
https://tools.ietf.org/html/rfc6120#section-7 */
-void rexmpp_bound (rexmpp_t *s, xmlNodePtr req, xmlNodePtr response, int success) {
+void rexmpp_bound (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)req;
if (! success) {
/* todo: reconnect here? */
rexmpp_log(s, LOG_ERR, "Resource binding failed.");
return;
}
/* todo: handle errors */
- xmlNodePtr child = xmlFirstElementChild(response);
+ rexmpp_xml_t *child = rexmpp_xml_first_elem_child(response);
if (rexmpp_xml_match(child, "urn:ietf:params:xml:ns:xmpp-bind", "bind")) {
- xmlNodePtr jid = xmlFirstElementChild(child);
+ rexmpp_xml_t *jid = rexmpp_xml_first_elem_child(child);
if (rexmpp_xml_match(jid, "urn:ietf:params:xml:ns:xmpp-bind", "jid")) {
- rexmpp_log(s, LOG_INFO, "jid: %s", xmlNodeGetContent(jid));
- s->assigned_jid = malloc(strlen(xmlNodeGetContent(jid)) + 1);
- strcpy(s->assigned_jid, xmlNodeGetContent(jid));
+ const char *jid_str = rexmpp_xml_text_child(jid);
+ rexmpp_log(s, LOG_INFO, "jid: %s", jid_str);
+ rexmpp_jid_parse(jid_str, &(s->assigned_jid));
}
if (s->stream_id == NULL &&
- (child = rexmpp_xml_find_child(s->stream_features, "urn:xmpp:sm:3",
- "sm"))) {
+ (rexmpp_xml_find_child(s->stream_features, "urn:xmpp:sm:3",
+ "sm") != NULL)) {
/* Try to resume a stream. */
s->sm_state = REXMPP_SM_NEGOTIATION;
s->stream_state = REXMPP_STREAM_SM_FULL;
- xmlNodePtr sm_enable = xmlNewNode(NULL, "enable");
- xmlNewNs(sm_enable, "urn:xmpp:sm:3", NULL);
- xmlNewProp(sm_enable, "resume", "true");
+ rexmpp_xml_t *sm_enable =
+ rexmpp_xml_new_elem("enable", "urn:xmpp:sm:3");
rexmpp_send(s, sm_enable);
s->stanzas_out_count = 0;
s->stanzas_out_acknowledged = 0;
@@ -1343,265 +1773,413 @@ void rexmpp_bound (rexmpp_t *s, xmlNodePtr req, xmlNodePtr response, int success
}
}
-void rexmpp_stream_bind (rexmpp_t *s) {
+rexmpp_err_t rexmpp_stream_bind (rexmpp_t *s) {
/* Issue a bind request. */
s->stream_state = REXMPP_STREAM_BIND;
- xmlNodePtr bind_cmd = xmlNewNode(NULL, "bind");
- xmlNewNs(bind_cmd, "urn:ietf:params:xml:ns:xmpp-bind", NULL);
- rexmpp_iq_new(s, "set", NULL, bind_cmd, rexmpp_bound);
+ rexmpp_xml_t *bind_cmd =
+ rexmpp_xml_new_elem("bind", "urn:ietf:params:xml:ns:xmpp-bind");
+ return rexmpp_iq_new(s, "set", NULL, bind_cmd, rexmpp_bound, NULL);
}
-void rexmpp_process_element (rexmpp_t *s) {
- xmlNodePtr elem = s->current_element;
+rexmpp_err_t rexmpp_process_element (rexmpp_t *s, rexmpp_xml_t *elem) {
+ rexmpp_console_on_recv(s, elem);
+
+ /* Stream negotiation,
+ https://tools.ietf.org/html/rfc6120#section-4.3 */
+ if (s->stream_state == REXMPP_STREAM_NEGOTIATION) {
+ if (rexmpp_xml_match(elem, "http://etherx.jabber.org/streams", "features")) {
+
+ /* Remember features. */
+ if (s->stream_features != NULL) {
+ rexmpp_xml_free(s->stream_features);
+ }
+ s->stream_features = rexmpp_xml_clone(elem);
+
+ /* TODO: check for required features properly here. Currently
+ assuming that STARTTLS, SASL, and BIND (with an exception for
+ SM) are always required if they are present. */
+ rexmpp_xml_t *starttls =
+ rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-tls",
+ "starttls");
+ rexmpp_xml_t *sasl =
+ rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-sasl",
+ "mechanisms");
+ rexmpp_xml_t *bind =
+ rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-bind", "bind");
+ rexmpp_xml_t *sm =
+ rexmpp_xml_find_child(elem, "urn:xmpp:sm:3", "sm");
+
+ if (starttls != NULL) {
+ /* Go for TLS, unless we're both trying to avoid it, and have
+ other options. */
+ if (! (s->tls_policy == REXMPP_TLS_AVOID &&
+ (sasl != NULL || bind != NULL || sm != NULL))) {
+ s->stream_state = REXMPP_STREAM_STARTTLS;
+ rexmpp_xml_t *starttls_cmd =
+ rexmpp_xml_new_elem("starttls", "urn:ietf:params:xml:ns:xmpp-tls");
+ rexmpp_send(s, starttls_cmd);
+ return REXMPP_SUCCESS;
+ }
+ } else if (s->tls_policy == REXMPP_TLS_REQUIRE &&
+ s->tls_state != REXMPP_TLS_ACTIVE) {
+ /* TLS is required, not established, and there's no such
+ feature available; fail here. */
+ rexmpp_log(s, LOG_ERR,
+ "TLS is required, but the server doesn't advertise such a feature");
+ return REXMPP_E_TLS;
+ }
+
+ /* Nothing to negotiate. */
+ if (rexmpp_xml_first_elem_child(elem) == NULL) {
+ rexmpp_stream_is_ready(s);
+ return REXMPP_SUCCESS;
+ }
+
+ if (sasl != NULL) {
+ s->stream_state = REXMPP_STREAM_SASL;
+ s->sasl_state = REXMPP_SASL_NEGOTIATION;
+ char mech_list[2048]; /* todo: perhaps grow it dynamically */
+ mech_list[0] = '\0';
+ rexmpp_xml_t *mechanism;
+ for (mechanism = rexmpp_xml_first_elem_child(sasl);
+ mechanism != NULL;
+ mechanism = rexmpp_xml_next_elem_sibling(mechanism)) {
+ if (rexmpp_xml_match(mechanism, "urn:ietf:params:xml:ns:xmpp-sasl",
+ "mechanism")) {
+ const char *mech_str = rexmpp_xml_text_child(mechanism);
+ snprintf(mech_list + strlen(mech_list),
+ 2048 - strlen(mech_list),
+ "%s ",
+ mech_str);
+ }
+ }
+ const char *mech = rexmpp_sasl_suggest_mechanism(s, mech_list);
+ if (mech == NULL) {
+ rexmpp_log(s, LOG_CRIT, "Failed to decide on a SASL mechanism");
+ s->sasl_state = REXMPP_SASL_ERROR;
+ return REXMPP_E_SASL;
+ }
+ rexmpp_log(s, LOG_INFO, "Selected SASL mechanism: %s", mech);
+ char *sasl_buf;
+ if (rexmpp_sasl_start(s, mech)) {
+ s->sasl_state = REXMPP_SASL_ERROR;
+ return REXMPP_E_SASL;
+ }
+ if (rexmpp_sasl_step64(s, "", (char**)&sasl_buf)) {
+ s->sasl_state = REXMPP_SASL_ERROR;
+ return REXMPP_E_SASL;
+ }
+ rexmpp_xml_t *auth_cmd =
+ rexmpp_xml_new_elem("auth", "urn:ietf:params:xml:ns:xmpp-sasl");
+ rexmpp_xml_add_attr(auth_cmd, "mechanism", mech);
+ rexmpp_xml_add_text(auth_cmd, sasl_buf);
+ free(sasl_buf);
+ rexmpp_send(s, auth_cmd);
+ return REXMPP_SUCCESS;
+ }
+
+ if (s->stream_id != NULL && sm != NULL) {
+ s->stream_state = REXMPP_STREAM_SM_RESUME;
+ char buf[11];
+ snprintf(buf, 11, "%u", s->stanzas_in_count);
+ rexmpp_xml_t *sm_resume =
+ rexmpp_xml_new_elem("resume", "urn:xmpp:sm:3");
+ rexmpp_xml_add_attr(sm_resume, "previd", s->stream_id);
+ rexmpp_xml_add_attr(sm_resume, "h", buf);
+ rexmpp_send(s, sm_resume);
+ return REXMPP_SUCCESS;
+ }
+
+ if (bind != NULL) {
+ return rexmpp_stream_bind(s);
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR, "Expected stream features, received %s",
+ elem->alt.elem.qname.name);
+ return REXMPP_E_STREAM;
+ }
+ }
/* 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
errors for unhandled IQs here). */
if (rexmpp_xml_match(elem, "jabber:client", "iq")) {
- char *type = xmlGetProp(elem, "type");
+ const char *type = rexmpp_xml_find_attr_val(elem, "type");
/* IQ responses. */
if (strcmp(type, "result") == 0 || strcmp(type, "error") == 0) {
- char *id = xmlGetProp(elem, "id");
- rexmpp_iq_t *req = s->active_iq;
+ const char *id = rexmpp_xml_find_attr_val(elem, "id");
+ rexmpp_iq_t *req = s->active_iq, *prev_req = NULL;
int found = 0;
while (req != NULL && found == 0) {
- char *req_id = xmlGetProp(req->request, "id");
- char *req_to = xmlGetProp(req->request, "to");
- char *rep_from = xmlGetProp(elem, "from");
- int id_matches = (strcmp(id, req_id) == 0);
+ const char *req_id = rexmpp_xml_find_attr_val(req->request, "id");
+ const char *req_to = rexmpp_xml_find_attr_val(req->request, "to");
+ const char *rep_from = rexmpp_xml_find_attr_val(elem, "from");
+ rexmpp_iq_t *req_next = req->next;
+ int id_matches = (req_id != NULL) && (strcmp(id, req_id) == 0);
int jid_matches = 0;
- if (req_to == NULL && rep_from == NULL) {
+ if (rep_from == NULL) {
jid_matches = 1;
} else if (req_to != NULL && rep_from != NULL) {
jid_matches = (strcmp(req_to, rep_from) == 0);
}
if (id_matches && jid_matches) {
found = 1;
- if (req->cb != NULL) {
- char *iq_type = xmlGetProp(elem, "type");
- int success = 0;
- if (strcmp(type, "result") == 0) {
- success = 1;
- }
- free(iq_type);
- req->cb(s, req->request, elem, success);
+ int success = 0;
+ if (strcmp(type, "result") == 0) {
+ success = 1;
}
- /* Remove the callback from the list, but keep in mind that
- it could have added more entries. */
- if (s->active_iq == req) {
- s->active_iq = req->next;
+ /* Remove the callback from the list. */
+ if (prev_req == NULL) {
+ s->active_iq = req_next;
} else {
- rexmpp_iq_t *prev_req = s->active_iq;
- for (prev_req = s->active_iq;
- prev_req != NULL;
- prev_req = prev_req->next)
- {
- if (prev_req->next == req) {
- prev_req->next = req->next;
- break;
- }
- }
+ prev_req->next = req_next;
}
- xmlFreeNode(req->request);
- free(req);
+ /* Finish and free the IQ request structure. */
+ rexmpp_iq_finish(s, req, success, elem);
}
- if (req_to != NULL) {
- free(req_to);
- }
- if (rep_from != NULL) {
- free(rep_from);
- }
- free(req_id);
- req = req->next;
+ prev_req = req;
+ req = req_next;
}
- free(id);
- }
- /* IQ "set" requests. */
- if (strcmp(type, "set") == 0) {
- xmlNodePtr query = xmlFirstElementChild(elem);
- int from_server = 0;
- char *from = xmlGetProp(elem, "from");
- if (from == NULL) {
- from_server = 1;
- } else {
- if (strcmp(from, jid_bare_to_host(s->assigned_jid)) == 0) {
+ } else if (! rexmpp_jingle_iq(s, elem)) {
+ if (strcmp(type, "set") == 0) {
+ rexmpp_xml_t *query = rexmpp_xml_first_elem_child(elem);
+ int from_server = 0;
+ const char *from = rexmpp_xml_find_attr_val(elem, "from");
+ if (from == NULL) {
from_server = 1;
+ } else {
+ if (strcmp(from, s->initial_jid.domain) == 0) {
+ from_server = 1;
+ }
}
- free(from);
- }
- if (from_server &&
- s->manage_roster &&
- rexmpp_xml_match(query, "jabber:iq:roster", "query")) {
- /* Roster push. */
- if (s->roster_ver != NULL) {
- free(s->roster_ver);
- }
- s->roster_ver = xmlGetProp(query, "ver");
- rexmpp_modify_roster(s, xmlFirstElementChild(query));
- /* todo: check for errors */
- rexmpp_iq_reply(s, elem, "result", NULL);
- if (s->roster_cache_file != NULL) {
- rexmpp_roster_cache_write(s);
- }
- } else {
- /* An unknown request. */
- rexmpp_iq_reply(s, elem, "error",
- rexmpp_xml_error("cancel", "service-unavailable"));
- }
- }
- /* IQ "get" requests. */
- if (strcmp(type, "get") == 0) {
- xmlNodePtr query = xmlFirstElementChild(elem);
- if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#info", "query")) {
- char *node = xmlGetProp(query, "node");
- char *caps_hash = rexmpp_capabilities_hash(s, s->disco_info);
- if (node == NULL ||
- (caps_hash != NULL &&
- s->disco_node != NULL &&
- strlen(node) == strlen(s->disco_node) + 1 + strlen(caps_hash) &&
- strncmp(node, s->disco_node, strlen(s->disco_node)) == 0 &&
- node[strlen(s->disco_node)] == '#' &&
- strcmp(node + strlen(s->disco_node) + 1, caps_hash) == 0)) {
- xmlNodePtr result = xmlNewNode(NULL, "query");
- xmlNewNs(result, "http://jabber.org/protocol/disco#info", NULL);
- if (node != NULL) {
- xmlNewProp(result, "node", node);
+ if (from_server &&
+ s->manage_roster &&
+ rexmpp_xml_match(query, "jabber:iq:roster", "query")) {
+ /* Roster push. */
+ if (s->roster_ver != NULL) {
+ free(s->roster_ver);
+ }
+ s->roster_ver = NULL;
+ const char *roster_ver = rexmpp_xml_find_attr_val(query, "ver");
+ if (roster_ver != NULL) {
+ s->roster_ver = strdup(roster_ver);
+ }
+ rexmpp_modify_roster(s, rexmpp_xml_first_elem_child(query));
+ /* todo: check for errors */
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ if (s->roster_cache_file != NULL) {
+ rexmpp_roster_cache_write(s);
}
- xmlAddChild(result, xmlCopyNodeList(s->disco_info));
- rexmpp_iq_reply(s, elem, "result", result);
} else {
- rexmpp_log(s, LOG_WARNING,
- "Service discovery request for an unknown node: %s", node);
+ /* An unknown request. */
rexmpp_iq_reply(s, elem, "error",
- rexmpp_xml_error("cancel", "item-not-found"));
+ rexmpp_xml_error("cancel", "service-unavailable"));
}
- if (caps_hash != NULL) {
- free(caps_hash);
- }
- if (node != NULL) {
- free(node);
+ } else if (strcmp(type, "get") == 0) {
+ rexmpp_xml_t *query = rexmpp_xml_first_elem_child(elem);
+ if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#info", "query")) {
+ const char *node = rexmpp_xml_find_attr_val(query, "node");
+ char *caps_hash = rexmpp_capabilities_hash(s, rexmpp_disco_info(s));
+ if (node == NULL ||
+ (caps_hash != NULL &&
+ s->disco_node != NULL &&
+ strlen(node) == strlen(s->disco_node) + 1 + strlen(caps_hash) &&
+ strncmp(node, s->disco_node, strlen(s->disco_node)) == 0 &&
+ node[strlen(s->disco_node)] == '#' &&
+ strcmp(node + strlen(s->disco_node) + 1, caps_hash) == 0)) {
+ rexmpp_xml_t *result =
+ rexmpp_xml_new_elem("query", "http://jabber.org/protocol/disco#info");
+ if (node != NULL) {
+ rexmpp_xml_add_attr(result, "node", node);
+ }
+ rexmpp_xml_add_child(result,
+ rexmpp_xml_clone_list(rexmpp_disco_info(s)));
+ rexmpp_iq_reply(s, elem, "result", result);
+ } else {
+ rexmpp_log(s, LOG_WARNING,
+ "Service discovery request for an unknown node: %s", node);
+ rexmpp_iq_reply(s, elem, "error",
+ rexmpp_xml_error("cancel", "item-not-found"));
+ }
+ if (caps_hash != NULL) {
+ free(caps_hash);
+ }
+ } else if (rexmpp_xml_match(query, "urn:xmpp:ping", "ping")) {
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ } else if (rexmpp_xml_match(query, "jabber:iq:version", "query")) {
+ rexmpp_xml_t *reply =
+ rexmpp_xml_new_elem("query", "jabber:iq:version");
+ rexmpp_xml_t *name = rexmpp_xml_new_elem("name", NULL);
+ rexmpp_xml_add_text(name, s->client_name);
+ rexmpp_xml_add_child(reply, name);
+ rexmpp_xml_t *version = rexmpp_xml_new_elem("version", NULL);
+ rexmpp_xml_add_text(version, s->client_version);
+ rexmpp_xml_add_child(reply, version);
+ rexmpp_iq_reply(s, elem, "result", reply);
+ } else {
+ /* An unknown request. */
+ rexmpp_iq_reply(s, elem, "error",
+ rexmpp_xml_error("cancel", "service-unavailable"));
}
- } else if (rexmpp_xml_match(query, "urn:xmpp:ping", "ping")) {
- rexmpp_iq_reply(s, elem, "result", NULL);
- } else {
- /* An unknown request. */
- rexmpp_iq_reply(s, elem, "error",
- rexmpp_xml_error("cancel", "service-unavailable"));
}
}
- free(type);
}
- /* Stream negotiation,
- https://tools.ietf.org/html/rfc6120#section-4.3 */
- if (s->stream_state == REXMPP_STREAM_NEGOTIATION &&
- rexmpp_xml_match(elem, "http://etherx.jabber.org/streams", "features")) {
-
- /* Remember features. */
- if (s->stream_features != NULL) {
- xmlFreeNode(s->stream_features);
- }
- s->stream_features = xmlCopyNode(elem, 1);
-
- /* Nothing to negotiate. */
- if (xmlFirstElementChild(elem) == NULL) {
- rexmpp_stream_is_ready(s);
- return;
- }
-
- /* TODO: check for required features properly here. Currently
- assuming that STARTTLS, SASL, and BIND (with an exception for
- SM) are always required if they are present. */
- xmlNodePtr child =
- rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-tls",
- "starttls");
- if (child != NULL) {
- s->stream_state = REXMPP_STREAM_STARTTLS;
- xmlNodePtr starttls_cmd = xmlNewNode(NULL, "starttls");
- xmlNewNs(starttls_cmd, "urn:ietf:params:xml:ns:xmpp-tls", NULL);
- rexmpp_send(s, starttls_cmd);
- return;
- }
-
- child = rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-sasl",
- "mechanisms");
- if (child != NULL) {
- s->stream_state = REXMPP_STREAM_SASL;
- s->sasl_state = REXMPP_SASL_NEGOTIATION;
- char mech_list[2048]; /* todo: perhaps grow it dynamically */
- mech_list[0] = '\0';
- xmlNodePtr mechanism;
- for (mechanism = xmlFirstElementChild(child);
- mechanism != NULL;
- mechanism = xmlNextElementSibling(mechanism)) {
- if (rexmpp_xml_match(mechanism, "urn:ietf:params:xml:ns:xmpp-sasl",
- "mechanism")) {
- snprintf(mech_list + strlen(mech_list),
- 2048 - strlen(mech_list),
- "%s ",
- xmlNodeGetContent(mechanism));
+ /* Incoming presence information. */
+ if (rexmpp_xml_match(elem, "jabber:client", "presence") &&
+ s->manage_roster &&
+ s->track_roster_presence) {
+ const char *from = rexmpp_xml_find_attr_val(elem, "from");
+ if (from != NULL) {
+ struct rexmpp_jid from_jid;
+ rexmpp_jid_parse(from, &from_jid);
+ if (rexmpp_roster_find_item(s, from_jid.bare, NULL) != NULL) {
+ /* The bare JID is in the roster. */
+ const char *type = rexmpp_xml_find_attr_val(elem, "type");
+ rexmpp_xml_t *cur, *prev;
+ if (type == NULL || strcmp(type, "unavailable") == 0) {
+ /* Either a new "available" presence or an "unavailable"
+ one: remove the previously stored presence for this
+ JID. */
+ for (prev = NULL, cur = s->roster_presence;
+ cur != NULL;
+ prev = cur, cur = cur->next) {
+ const char *cur_from = rexmpp_xml_find_attr_val(cur, "from");
+ if (strcmp(cur_from, from_jid.full) == 0) {
+ if (prev == NULL) {
+ s->roster_presence = cur->next;
+ } else {
+ prev->next = cur->next;
+ }
+ rexmpp_xml_free(cur);
+ break;
+ }
+ }
}
- }
- const char *mech =
- gsasl_client_suggest_mechanism(s->sasl_ctx, mech_list);
- rexmpp_log(s, LOG_INFO, "Selected SASL mechanism: %s", mech);
- int sasl_err;
- char *sasl_buf;
- sasl_err = gsasl_client_start(s->sasl_ctx, mech, &(s->sasl_session));
- if (sasl_err != GSASL_OK) {
- rexmpp_log(s, LOG_CRIT, "Failed to initialise SASL session: %s",
- gsasl_strerror(sasl_err));
- s->sasl_state = REXMPP_SASL_ERROR;
- return;
- }
- sasl_err = gsasl_step64 (s->sasl_session, "", (char**)&sasl_buf);
- if (sasl_err != GSASL_OK) {
- if (sasl_err == GSASL_NEEDS_MORE) {
- rexmpp_log(s, LOG_DEBUG, "SASL needs more data");
- } else {
- rexmpp_log(s, LOG_ERR, "SASL error: %s",
- gsasl_strerror(sasl_err));
- s->sasl_state = REXMPP_SASL_ERROR;
- return;
+ if (type == NULL) {
+ /* An "available" presence: add it. */
+ rexmpp_xml_t *presence = rexmpp_xml_clone(elem);
+ presence->next = s->roster_presence;
+ s->roster_presence = presence;
}
}
- xmlNodePtr auth_cmd = xmlNewNode(NULL, "auth");
- xmlNewProp(auth_cmd, "mechanism", mech);
- xmlNewNs(auth_cmd, "urn:ietf:params:xml:ns:xmpp-sasl", NULL);
- xmlNodeAddContent(auth_cmd, sasl_buf);
- free(sasl_buf);
- rexmpp_send(s, auth_cmd);
- return;
}
+ }
- child = rexmpp_xml_find_child(elem, "urn:xmpp:sm:3", "sm");
- if (s->stream_id != NULL && child != NULL) {
- s->stream_state = REXMPP_STREAM_SM_RESUME;
- char buf[11];
- snprintf(buf, 11, "%u", s->stanzas_in_count);
- xmlNodePtr sm_resume = xmlNewNode(NULL, "resume");
- xmlNewNs(sm_resume, "urn:xmpp:sm:3", NULL);
- xmlNewProp(sm_resume, "previd", s->stream_id);
- xmlNewProp(sm_resume, "h", buf);
- rexmpp_send(s, sm_resume);
- return;
- }
+ /* Incoming messages. */
+ if (rexmpp_xml_match(elem, "jabber:client", "message")) {
+ const char *from = rexmpp_xml_find_attr_val(elem, "from");
+ if (from != NULL) {
+ struct rexmpp_jid from_jid;
+ rexmpp_jid_parse(from, &from_jid);
+ if (rexmpp_roster_find_item(s, from_jid.bare, NULL) != NULL ||
+ strcmp(from_jid.bare, s->assigned_jid.bare) == 0) {
+ rexmpp_xml_t *event =
+ rexmpp_xml_find_child(elem,
+ "http://jabber.org/protocol/pubsub#event",
+ "event");
+ if (event != NULL && s->manage_roster && s->track_roster_events) {
+ rexmpp_xml_t *items =
+ rexmpp_xml_find_child(event,
+ "http://jabber.org/protocol/pubsub#event",
+ "items");
+ if (items != NULL) {
+ const char *node = rexmpp_xml_find_attr_val(items, "node");
+ if (node != NULL) {
+ /* Remove the previously stored items for the same sender
+ and node, if any. */
+ rexmpp_xml_t *prev, *cur;
+ cur = rexmpp_find_event(s, from_jid.bare, node, &prev);
+ if (cur) {
+ if (prev == NULL) {
+ s->roster_events = cur->next;
+ } else {
+ prev->next = cur->next;
+ }
+ rexmpp_xml_free(cur);
+ cur = NULL;
+ }
+
+ /* Add the new message. */
+ rexmpp_xml_t *message = rexmpp_xml_clone(elem);
+ message->next = s->roster_events;
+ s->roster_events = message;
- child =
- rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-bind", "bind");
- if (child != NULL) {
- rexmpp_stream_bind(s);
- return;
+ /* Process the node at once. */
+ if (s->retrieve_openpgp_keys &&
+ strcmp(node, "urn:xmpp:openpgp:0:public-keys") == 0) {
+ rexmpp_openpgp_check_keys(s, from_jid.bare, items);
+ }
+ if (s->autojoin_bookmarked_mucs &&
+ strcmp(node, "urn:xmpp:bookmarks:1") == 0 &&
+ strcmp(from_jid.bare, s->assigned_jid.bare) == 0) {
+ rexmpp_xml_t *item;
+ for (item = rexmpp_xml_first_elem_child(items);
+ item != NULL;
+ item = rexmpp_xml_next_elem_sibling(item)) {
+ rexmpp_xml_t *conference =
+ rexmpp_xml_find_child(item,
+ "urn:xmpp:bookmarks:1",
+ "conference");
+ if (conference == NULL) {
+ continue;
+ }
+ const char *item_id = rexmpp_xml_find_attr_val(item, "id");
+ if (item_id == NULL) {
+ continue;
+ }
+ const char *autojoin =
+ rexmpp_xml_find_attr_val(conference, "autojoin");
+ if (autojoin == NULL) {
+ continue;
+ }
+ if (strcmp(autojoin, "true") == 0 ||
+ strcmp(autojoin, "1") == 0) {
+ rexmpp_xml_t *nick =
+ rexmpp_xml_find_child(conference,
+ "urn:xmpp:bookmarks:1",
+ "nick");
+ const char *nick_str = NULL;
+ if (nick != NULL) {
+ nick_str = rexmpp_xml_text_child(nick);
+ }
+ if (nick_str == NULL) {
+ nick_str = s->initial_jid.local;
+ }
+ char *occupant_jid =
+ malloc(strlen(item_id) + strlen(nick_str) + 2);
+ sprintf(occupant_jid, "%s/%s", item_id, nick_str);
+ const char *password =
+ rexmpp_xml_find_attr_val(conference, "password");
+ rexmpp_muc_join(s, occupant_jid, password,
+ s->muc_ping_default_delay);
+ free(occupant_jid);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
}
/* Stream errors, https://tools.ietf.org/html/rfc6120#section-4.9 */
if (rexmpp_xml_match(elem, "http://etherx.jabber.org/streams",
"error")) {
- rexmpp_log(s, LOG_ERR, "stream error");
- s->stream_state = REXMPP_STREAM_ERROR;
- return;
+ if (rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-streams",
+ "reset") != NULL ||
+ rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-streams",
+ "system-shutdown") != NULL) {
+ rexmpp_log(s, LOG_WARNING, "Server reset or shutdown.");
+ s->stream_state = REXMPP_STREAM_ERROR_RECONNECT;
+ return REXMPP_E_AGAIN;
+ } else {
+ rexmpp_log(s, LOG_ERR, "Stream error");
+ s->stream_state = REXMPP_STREAM_ERROR;
+ return REXMPP_E_STREAM;
+ }
}
/* STARTTLS negotiation,
@@ -1609,12 +2187,11 @@ void rexmpp_process_element (rexmpp_t *s) {
if (s->stream_state == REXMPP_STREAM_STARTTLS) {
if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-tls",
"proceed")) {
- rexmpp_tls_start(s);
- return;
+ return rexmpp_process_tls_conn_err(s, rexmpp_tls_connect(s));
} else if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-tls",
"failure")) {
rexmpp_log(s, LOG_ERR, "STARTTLS failure");
- return;
+ return REXMPP_E_TLS;
}
}
@@ -1625,46 +2202,36 @@ void rexmpp_process_element (rexmpp_t *s) {
int sasl_err;
if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-sasl",
"challenge")) {
- sasl_err = gsasl_step64 (s->sasl_session, xmlNodeGetContent(elem),
- (char**)&sasl_buf);
- if (sasl_err != GSASL_OK) {
- if (sasl_err == GSASL_NEEDS_MORE) {
- rexmpp_log(s, LOG_DEBUG, "SASL needs more data");
- } else {
- rexmpp_log(s, LOG_ERR, "SASL error: %s",
- gsasl_strerror(sasl_err));
- s->sasl_state = REXMPP_SASL_ERROR;
- return;
- }
+ const char *challenge = rexmpp_xml_text_child(elem);
+ sasl_err = rexmpp_sasl_step64 (s, challenge, (char**)&sasl_buf);
+ if (sasl_err) {
+ s->sasl_state = REXMPP_SASL_ERROR;
+ return REXMPP_E_SASL;
}
- xmlNodePtr response = xmlNewNode(NULL, "response");
- xmlNewNs(response, "urn:ietf:params:xml:ns:xmpp-sasl", NULL);
- xmlNodeAddContent(response, sasl_buf);
+ rexmpp_xml_t *response =
+ rexmpp_xml_new_elem("response", "urn:ietf:params:xml:ns:xmpp-sasl");
+ rexmpp_xml_add_text(response, sasl_buf);
free(sasl_buf);
rexmpp_send(s, response);
- return;
} else if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-sasl",
"success")) {
- sasl_err = gsasl_step64 (s->sasl_session, xmlNodeGetContent(elem),
- (char**)&sasl_buf);
+ const char *success = rexmpp_xml_text_child(elem);
+ sasl_err = rexmpp_sasl_step64 (s, success, (char**)&sasl_buf);
free(sasl_buf);
- if (sasl_err == GSASL_OK) {
+ if (! sasl_err) {
rexmpp_log(s, LOG_DEBUG, "SASL success");
} else {
- rexmpp_log(s, LOG_ERR, "SASL error: %s",
- gsasl_strerror(sasl_err));
s->sasl_state = REXMPP_SASL_ERROR;
- return;
+ return REXMPP_E_SASL;
}
s->sasl_state = REXMPP_SASL_ACTIVE;
- s->stream_state = REXMPP_STREAM_RESTART;
- return;
+ s->xml_parser = rexmpp_xml_parser_reset(s->xml_parser);
+ return rexmpp_stream_open(s);
} else if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-sasl",
"failure")) {
/* todo: would be nice to retry here, but just giving up for now */
rexmpp_log(s, LOG_ERR, "SASL failure");
- rexmpp_stop(s);
- return;
+ return rexmpp_stop(s);
}
}
@@ -1672,20 +2239,23 @@ void rexmpp_process_element (rexmpp_t *s) {
if (s->stream_state == REXMPP_STREAM_SM_FULL) {
if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "enabled")) {
s->sm_state = REXMPP_SM_ACTIVE;
- char *resume = xmlGetProp(elem, "resume");
+ const char *resume = rexmpp_xml_find_attr_val(elem, "resume");
if (resume != NULL) {
if (s->stream_id != NULL) {
free(s->stream_id);
}
- s->stream_id = xmlGetProp(elem, "id");
- xmlFree(resume);
+ const char *stream_id = rexmpp_xml_find_attr_val(elem, "id");
+ s->stream_id = NULL;
+ if (stream_id != NULL) {
+ s->stream_id = strdup(stream_id);
+ }
}
rexmpp_stream_is_ready(s);
} else if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "failed")) {
s->stream_state = REXMPP_STREAM_SM_ACKS;
s->sm_state = REXMPP_SM_NEGOTIATION;
- xmlNodePtr sm_enable = xmlNewNode(NULL, "enable");
- xmlNewNs(sm_enable, "urn:xmpp:sm:3", NULL);
+ rexmpp_xml_t *sm_enable =
+ rexmpp_xml_new_elem("enable", "urn:xmpp:sm:3");
rexmpp_send(s, sm_enable);
}
} else if (s->stream_state == REXMPP_STREAM_SM_ACKS) {
@@ -1697,8 +2267,8 @@ void rexmpp_process_element (rexmpp_t *s) {
}
} else if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "failed")) {
s->sm_state = REXMPP_SM_INACTIVE;
- xmlNodePtr sm_enable = xmlNewNode(NULL, "enable");
- xmlNewNs(sm_enable, "urn:xmpp:sm:3", NULL);
+ rexmpp_xml_t *sm_enable =
+ rexmpp_xml_new_elem("enable", "urn:xmpp:sm:3");
rexmpp_send(s, sm_enable);
}
rexmpp_stream_is_ready(s);
@@ -1715,17 +2285,16 @@ void rexmpp_process_element (rexmpp_t *s) {
while (s->active_iq != NULL) {
/* todo: check that those are not queued for resending? */
rexmpp_iq_t *next = s->active_iq->next;
- xmlFreeNode(s->active_iq->request);
- free(s->active_iq);
+ rexmpp_iq_t *iq = s->active_iq;
s->active_iq = next;
+ rexmpp_iq_finish(s, iq, 0, NULL);
}
- xmlNodePtr child =
+ rexmpp_xml_t *child =
rexmpp_xml_find_child(s->stream_features,
"urn:ietf:params:xml:ns:xmpp-bind",
"bind");
if (child != NULL) {
- rexmpp_stream_bind(s);
- return;
+ return rexmpp_stream_bind(s);
}
}
}
@@ -1734,74 +2303,80 @@ void rexmpp_process_element (rexmpp_t *s) {
s->stanzas_in_count++;
}
if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "r")) {
- rexmpp_sm_ack(s);
+ return rexmpp_sm_ack(s);
} else if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "a")) {
rexmpp_sm_handle_ack(s, elem);
}
+ return REXMPP_SUCCESS;
}
-void rexmpp_sax_characters (rexmpp_t *s, const char *ch, int len)
+/* These SAX handlers are similar to those in rexmpp_xml.c, might be
+ nice to reuse them. */
+void rexmpp_sax_characters (rexmpp_t *s, const char *ch, size_t len)
{
if (s->current_element != NULL) {
- xmlNodeAddContentLen(s->current_element, ch, len);
+ rexmpp_xml_t *last_node = s->current_element->alt.elem.children;
+ if (last_node != NULL && last_node->type == REXMPP_XML_TEXT) {
+ /* The last child is textual as well, just extend it */
+ size_t last_len = strlen(last_node->alt.text);
+ char *new_alt_text = realloc(last_node->alt.text, last_len + len + 1);
+ if (new_alt_text == NULL) {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to reallocate the XML element text buffer: %s",
+ strerror(errno));
+ return;
+ }
+ last_node->alt.text = new_alt_text;
+ strncpy(last_node->alt.text + last_len, ch, len);
+ last_node->alt.text[last_len + len] = '\0';
+ } else {
+ rexmpp_xml_t *text_node = rexmpp_xml_new_text_len(ch, len);
+ if (text_node != NULL) {
+ text_node->next = s->current_element->alt.elem.children;
+ s->current_element->alt.elem.children = text_node;
+ }
+ }
}
}
void rexmpp_sax_start_elem_ns (rexmpp_t *s,
- const char *localname,
- const char *prefix,
- const char *URI,
- int nb_namespaces,
- const char **namespaces,
- int nb_attributes,
- int nb_defaulted,
- const char **attributes)
+ const char *name,
+ const char *namespace,
+ rexmpp_xml_attr_t *attributes)
{
- int i;
if (s->stream_state == REXMPP_STREAM_OPENING &&
- strcmp(localname, "stream") == 0 &&
- strcmp(URI, "http://etherx.jabber.org/streams") == 0) {
+ s->current_element == NULL &&
+ strcmp(name, "stream") == 0 &&
+ strcmp(namespace, "http://etherx.jabber.org/streams") == 0) {
rexmpp_log(s, LOG_DEBUG, "stream start");
s->stream_state = REXMPP_STREAM_NEGOTIATION;
+ rexmpp_xml_attribute_free_list(attributes);
return;
}
if (s->stream_state != REXMPP_STREAM_OPENING) {
if (s->current_element == NULL) {
- s->current_element = xmlNewNode(NULL, localname);
+ s->current_element = rexmpp_xml_new_elem(name, namespace);
s->current_element_root = s->current_element;
} else {
- xmlNodePtr node = xmlNewNode(NULL, localname);
- xmlAddChild(s->current_element, node);
+ rexmpp_xml_t *node = rexmpp_xml_new_elem(name, namespace);
+ node->next = s->current_element->alt.elem.children;
+ s->current_element->alt.elem.children = node;
s->current_element = node;
}
- xmlNsPtr ns = xmlNewNs(s->current_element, URI, prefix);
- s->current_element->ns = ns;
- for (i = 0; i < nb_attributes; i++) {
- size_t attr_len = attributes[i * 5 + 4] - attributes[i * 5 + 3];
- char *attr_val = malloc(attr_len + 1);
- attr_val[attr_len] = '\0';
- strncpy(attr_val, attributes[i * 5 + 3], attr_len);
- xmlNewProp(s->current_element, attributes[i * 5], attr_val);
- free(attr_val);
- }
+ s->current_element->alt.elem.attributes = attributes;
}
}
-void rexmpp_sax_end_elem_ns (rexmpp_t *s,
- const char *localname,
- const char *prefix,
- const char *URI)
+void rexmpp_sax_end_elem_ns (rexmpp_t *s)
{
if ((s->stream_state == REXMPP_STREAM_CLOSING ||
s->stream_state == REXMPP_STREAM_ERROR) &&
- strcmp(localname, "stream") == 0 &&
- strcmp(URI, "http://etherx.jabber.org/streams") == 0) {
+ s->current_element == NULL) {
rexmpp_log(s, LOG_DEBUG, "stream end");
if (s->sasl_state == REXMPP_SASL_ACTIVE) {
- gsasl_finish(s->sasl_session);
- s->sasl_session = NULL;
+ rexmpp_sasl_ctx_cleanup(s);
s->sasl_state = REXMPP_SASL_INACTIVE;
}
s->stream_state = REXMPP_STREAM_CLOSED;
@@ -1818,16 +2393,23 @@ void rexmpp_sax_end_elem_ns (rexmpp_t *s,
}
if (s->current_element != s->current_element_root) {
- s->current_element = s->current_element->parent;
+ /* Find the parent, set it as current element. */
+ rexmpp_xml_t *parent = s->current_element_root;
+ while (parent->alt.elem.children != s->current_element) {
+ parent = parent->alt.elem.children;
+ }
+ s->current_element = parent;
} else {
- if (s->xml_in_cb != NULL && s->xml_in_cb(s, s->current_element) != 0) {
- rexmpp_log(s, LOG_WARNING,
- "Message processing was cancelled by xml_in_cb.");
+ /* Done parsing this element; reverse all the lists of children
+ and queue it. */
+ rexmpp_xml_reverse_children(s->current_element);
+ if (s->input_queue == NULL) {
+ s->input_queue = s->current_element;
+ s->input_queue_last = s->current_element;
} else {
- rexmpp_process_element(s);
+ s->input_queue_last->next = s->current_element;
+ s->input_queue_last = s->current_element;
}
-
- xmlFreeNode(s->current_element);
s->current_element = NULL;
s->current_element_root = NULL;
}
@@ -1841,17 +2423,21 @@ rexmpp_err_t rexmpp_close (rexmpp_t *s) {
rexmpp_err_t rexmpp_stop (rexmpp_t *s) {
if (s->stream_state == REXMPP_STREAM_READY) {
- xmlNodePtr presence = xmlNewNode(NULL, "presence");
- xmlNewProp(presence, "type", "unavailable");
+ rexmpp_xml_t *presence =
+ rexmpp_xml_new_elem("presence", "jabber:client");
+ rexmpp_xml_add_id(presence);
+ rexmpp_xml_add_attr(presence, "type", "unavailable");
rexmpp_send(s, presence);
}
+
+ s->stream_state = REXMPP_STREAM_CLOSE_REQUESTED;
+
if (s->sm_state == REXMPP_SM_ACTIVE) {
int ret = rexmpp_sm_ack(s);
- if (ret != REXMPP_SUCCESS && ret != REXMPP_E_AGAIN) {
+ if (ret > REXMPP_E_AGAIN) {
return ret;
}
}
- s->stream_state = REXMPP_STREAM_CLOSE_REQUESTED;
if (s->send_buffer == NULL) {
return rexmpp_close(s);
} else {
@@ -1860,8 +2446,34 @@ rexmpp_err_t rexmpp_stop (rexmpp_t *s) {
}
rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
- struct timeval now;
- gettimeofday(&now, NULL);
+ struct timespec now;
+ if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
+ rexmpp_log(s, LOG_ERR, "Failed to get time: %s", strerror(errno));
+ return REXMPP_E_OTHER;
+ }
+
+#ifdef HAVE_CURL
+ /* curl may work independently from everything else. */
+ int curl_running_handles;
+ CURLMcode curl_code;
+ do {
+ curl_code = curl_multi_perform(s->curl_multi, &curl_running_handles);
+ } while (curl_code == CURLM_CALL_MULTI_PERFORM);
+ CURLMsg *cmsg;
+ int curl_queue;
+ do {
+ cmsg = curl_multi_info_read(s->curl_multi, &curl_queue);
+ if (cmsg != NULL && cmsg->msg == CURLMSG_DONE) {
+ CURL *e = cmsg->easy_handle;
+ struct rexmpp_http_upload_task *task;
+ curl_easy_getinfo(e, CURLINFO_PRIVATE, &task);
+ rexmpp_log(s, LOG_DEBUG, "%s upload is finished", task->fname);
+ rexmpp_upload_task_finish(task);
+ curl_multi_remove_handle(s->curl_multi, e);
+ curl_easy_cleanup(e);
+ }
+ } while (cmsg != NULL);
+#endif
/* Inactive: start or reconnect. */
if ((s->resolver_state == REXMPP_RESOLVER_NONE ||
@@ -1874,19 +2486,22 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
if (s->manual_host == NULL) {
/* Start by querying SRV records. */
rexmpp_log(s, LOG_DEBUG, "start (or reconnect)");
- size_t srv_query_buf_len = strlen(jid_bare_to_host(s->initial_jid)) +
+ size_t srv_query_buf_len = strlen(s->initial_jid.domain) +
strlen("_xmpps-client._tcp..") +
1;
char *srv_query = malloc(srv_query_buf_len);
+ if (srv_query == NULL) {
+ return REXMPP_E_MALLOC;
+ }
+ s->resolver_state = REXMPP_RESOLVER_SRV;
snprintf(srv_query, srv_query_buf_len,
- "_xmpps-client._tcp.%s.", jid_bare_to_host(s->initial_jid));
- ares_query(s->resolver_channel, srv_query,
- ns_c_in, ns_t_srv, rexmpp_srv_tls_cb, s);
+ "_xmpps-client._tcp.%s.", s->initial_jid.domain);
+ rexmpp_dns_resolve(s, srv_query, 33, 1,
+ "xmpps", rexmpp_srv_cb);
snprintf(srv_query, srv_query_buf_len,
- "_xmpp-client._tcp.%s.", jid_bare_to_host(s->initial_jid));
- ares_query(s->resolver_channel, srv_query,
- ns_c_in, ns_t_srv, rexmpp_srv_cb, s);
- s->resolver_state = REXMPP_RESOLVER_SRV;
+ "_xmpp-client._tcp.%s.", s->initial_jid.domain);
+ rexmpp_dns_resolve(s, srv_query, 33, 1,
+ "xmpp", rexmpp_srv_cb);
free(srv_query);
} else {
/* A host is configured manually, connect there. */
@@ -1897,31 +2512,64 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
} else {
s->tls_state = REXMPP_TLS_INACTIVE;
}
- rexmpp_start_connecting(s);
+ rexmpp_err_t err = rexmpp_start_connecting(s);
+ if (err > REXMPP_E_AGAIN) {
+ return err;
+ }
}
}
+ /* Don't try to reconnect if a stream is requested to be closed. */
+ if (s->tcp_state == REXMPP_TCP_ERROR &&
+ (s->stream_state == REXMPP_STREAM_CLOSE_REQUESTED ||
+ s->stream_state == REXMPP_STREAM_CLOSING)) {
+ return REXMPP_E_TCP;
+ }
+
/* Resolving SRV records. This continues in rexmpp_srv_tls_cb,
- rexmpp_srv_cb, and rexmpp_after_srv, possibly leading to
- connection initiation. */
- if (s->resolver_state != REXMPP_RESOLVER_NONE &&
- s->resolver_state != REXMPP_RESOLVER_READY) {
- ares_process(s->resolver_channel, read_fds, write_fds);
+ rexmpp_srv_cb. */
+ if (rexmpp_dns_process(s, read_fds, write_fds)) {
+ return REXMPP_E_DNS;
+ }
+
+ /* Initiating a connection after SRV resolution. */
+ if (s->resolver_state == REXMPP_RESOLVER_READY) {
+ s->resolver_state = REXMPP_RESOLVER_NONE;
+ /* todo: sort the records */
+ if (s->server_srv == NULL && s->server_srv_tls == NULL) {
+ /* Failed to resolve anything: a fallback. */
+ s->server_host = s->initial_jid.domain;
+ s->server_port = 5222;
+ rexmpp_start_connecting(s);
+ } else {
+ rexmpp_try_next_host(s);
+ }
}
/* Connecting. Continues in rexmpp_process_conn_err, possibly
leading to stream opening. */
if (s->tcp_state == REXMPP_TCP_CONNECTING) {
- rexmpp_process_conn_err(s,
- rexmpp_tcp_conn_proceed(&s->server_connection,
- read_fds, write_fds));
+ rexmpp_err_t err =
+ rexmpp_process_conn_err(s,
+ rexmpp_tcp_conn_proceed(s, &s->server_connection,
+ read_fds, write_fds));
+ if (err > REXMPP_E_AGAIN) {
+ return err;
+ }
}
/* SOCKS5 connection. */
if (s->tcp_state == REXMPP_TCP_SOCKS) {
- rexmpp_process_socks_err(s, rexmpp_socks_proceed(&s->server_socks_conn));
+ rexmpp_err_t err =
+ rexmpp_process_socks_err(s, rexmpp_socks_proceed(&s->server_socks_conn));
+ if (err > REXMPP_E_AGAIN) {
+ return err;
+ }
}
+ /* Jingle activity. */
+ rexmpp_jingle_run(s, read_fds, write_fds);
+
/* The things we do while connected. */
/* Sending queued data. */
@@ -1934,22 +2582,62 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
s->stream_state != REXMPP_STREAM_ERROR) &&
s->sasl_state != REXMPP_SASL_ERROR &&
s->send_buffer != NULL) {
- rexmpp_send_continue(s);
+ rexmpp_err_t err = rexmpp_send_continue(s);
+ if (err > REXMPP_E_AGAIN) {
+ return err;
+ }
}
+ /* MUC self-pinging. */
+ if (s->tcp_state == REXMPP_TCP_CONNECTED &&
+ s->stream_state == REXMPP_STREAM_READY)
+ {
+ rexmpp_muc_ping_t *mp = s->muc_ping;
+ while (mp != NULL) {
+ if (mp->last_activity.tv_sec + mp->delay <= now.tv_sec) {
+ if (mp->requested == 0) {
+ clock_gettime(CLOCK_MONOTONIC, &(mp->last_activity));
+ mp->requested = 1;
+ rexmpp_xml_t *ping_cmd =
+ rexmpp_xml_new_elem("ping", "urn:xmpp:ping");
+ rexmpp_iq_new(s, "get", mp->jid,
+ ping_cmd, rexmpp_muc_pong, mp);
+ } else {
+ /* Requested already, and delay time passed again, without
+ a reply (not even an error). Warn the user, remove this
+ MUC. */
+ char *occupant_jid_to_remove = mp->jid;
+ rexmpp_log(s, LOG_WARNING,
+ "No MUC self-ping reply for %s, "
+ "disabling self-ping for it",
+ mp->jid);
+ mp = mp->next;
+ rexmpp_muc_ping_remove(s, occupant_jid_to_remove);
+ continue;
+ }
+ }
+ mp = mp->next;
+ }
+ }
+
/* Pinging the server. */
if (s->tcp_state == REXMPP_TCP_CONNECTED &&
- s->last_network_activity + s->ping_delay <= time(NULL)) {
+ s->stream_state == REXMPP_STREAM_READY &&
+ s->last_network_activity.tv_sec + s->ping_delay <= now.tv_sec) {
if (s->ping_requested == 0) {
s->ping_requested = 1;
- xmlNodePtr ping_cmd = xmlNewNode(NULL, "ping");
- xmlNewNs(ping_cmd, "urn:xmpp:ping", NULL);
- rexmpp_iq_new(s, "get", jid_bare_to_host(s->initial_jid),
- ping_cmd, rexmpp_pong);
+ rexmpp_xml_t *ping_cmd =
+ rexmpp_xml_new_elem("ping", "urn:xmpp:ping");
+ rexmpp_iq_new(s, "get", s->initial_jid.domain,
+ ping_cmd, rexmpp_pong, NULL);
} else {
+ /* Last network activity is updated on sending as well as on
+ receiving, so this will not be triggered right after sending
+ the request. */
rexmpp_log(s, LOG_WARNING, "Ping timeout, reconnecting.");
rexmpp_cleanup(s);
rexmpp_schedule_reconnect(s);
+ return REXMPP_E_AGAIN;
}
}
@@ -1962,32 +2650,30 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
s->stream_state != REXMPP_STREAM_CLOSED &&
s->stream_state != REXMPP_STREAM_ERROR) &&
s->sasl_state != REXMPP_SASL_ERROR) {
- rexmpp_recv(s);
+ rexmpp_err_t err = rexmpp_recv(s);
+ if (err > REXMPP_E_AGAIN) {
+ return err;
+ }
}
/* Performing a TLS handshake. A stream restart happens after
this, if everything goes well. */
if (s->tcp_state == REXMPP_TCP_CONNECTED &&
s->tls_state == REXMPP_TLS_HANDSHAKE) {
- rexmpp_tls_handshake(s);
- }
-
- /* Restarting a stream if needed after the above actions. Since it
- involves resetting the parser, functions called by that parser
- can't do it on their own. */
- if (s->tcp_state == REXMPP_TCP_CONNECTED &&
- (s->tls_state == REXMPP_TLS_ACTIVE ||
- s->tls_state == REXMPP_TLS_INACTIVE) &&
- s->stream_state == REXMPP_STREAM_RESTART) {
- xmlCtxtResetPush(s->xml_parser, "", 0, "", "utf-8");
- rexmpp_stream_open(s);
+ rexmpp_err_t err = rexmpp_process_tls_conn_err(s, rexmpp_tls_connect(s));
+ if (err > REXMPP_E_AGAIN) {
+ return err;
+ }
}
/* Closing the stream once everything is sent. */
if (s->tcp_state == REXMPP_TCP_CONNECTED &&
s->stream_state == REXMPP_STREAM_CLOSE_REQUESTED &&
s->send_buffer == NULL) {
- rexmpp_close(s);
+ rexmpp_err_t err = rexmpp_close(s);
+ if (err > REXMPP_E_AGAIN) {
+ return err;
+ }
}
/* Closing TLS and TCP connections once stream is closed. If
@@ -1996,31 +2682,48 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
if (s->tcp_state == REXMPP_TCP_CONNECTED &&
s->stream_state == REXMPP_STREAM_CLOSED &&
s->tls_state == REXMPP_TLS_CLOSING) {
- int ret = gnutls_bye(s->gnutls_session, GNUTLS_SHUT_RDWR);
- if (ret == GNUTLS_E_SUCCESS) {
+ rexmpp_tls_err_t err = rexmpp_tls_disconnect(s, s->tls);
+ if (err == REXMPP_TLS_SUCCESS) {
+ rexmpp_log(s, LOG_DEBUG, "TLS disconnected");
s->tls_state = REXMPP_TLS_INACTIVE;
rexmpp_cleanup(s);
s->tcp_state = REXMPP_TCP_CLOSED;
+ } else if (err != REXMPP_TLS_E_AGAIN) {
+ s->tls_state = REXMPP_TLS_ERROR;
+ return REXMPP_E_TLS;
}
}
- if (s->tcp_state == REXMPP_TCP_CLOSED) {
+ if (s->tcp_state == REXMPP_TCP_CLOSED &&
+ s->stream_state != REXMPP_STREAM_ERROR_RECONNECT) {
+ rexmpp_console_on_run(s, REXMPP_SUCCESS);
return REXMPP_SUCCESS;
} else {
+ rexmpp_console_on_run(s, REXMPP_E_AGAIN);
return REXMPP_E_AGAIN;
}
}
-int rexmpp_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
- int conn_fd, max_fd = 0;
+int rexmpp_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
+ int conn_fd, tls_fd, jingle_fd, max_fd = 0;
- if (s->resolver_state != REXMPP_RESOLVER_NONE &&
- s->resolver_state != REXMPP_RESOLVER_READY) {
- max_fd = ares_fds(s->resolver_channel, read_fds, write_fds);
+ max_fd = rexmpp_dns_fds(s, read_fds, write_fds);
+
+ jingle_fd = rexmpp_jingle_fds(s, read_fds, write_fds);
+ if (jingle_fd > max_fd) {
+ max_fd = jingle_fd;
+ }
+
+#ifdef HAVE_CURL
+ int curl_fd;
+ curl_multi_fdset(s->curl_multi, read_fds, write_fds, NULL, &curl_fd);
+ if (curl_fd >= max_fd) {
+ max_fd = curl_fd + 1;
}
+#endif
if (s->tcp_state == REXMPP_TCP_CONNECTING) {
- conn_fd = rexmpp_tcp_conn_fds(&s->server_connection, read_fds, write_fds);
+ conn_fd = rexmpp_tcp_conn_fds(s, &s->server_connection, read_fds, write_fds);
if (conn_fd > max_fd) {
max_fd = conn_fd;
}
@@ -2038,13 +2741,9 @@ int rexmpp_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
}
if (s->tls_state == REXMPP_TLS_HANDSHAKE) {
- if (gnutls_record_get_direction(s->gnutls_session) == 0) {
- FD_SET(s->server_socket, read_fds);
- } else {
- FD_SET(s->server_socket, write_fds);
- }
- if (s->server_socket + 1 > max_fd) {
- max_fd = s->server_socket + 1;
+ tls_fd = rexmpp_tls_fds(s, read_fds, write_fds);
+ if (tls_fd > max_fd) {
+ max_fd = tls_fd;
}
}
@@ -2061,38 +2760,73 @@ int rexmpp_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
return max_fd;
}
-struct timeval *rexmpp_timeout (rexmpp_t *s,
- struct timeval *max_tv,
- struct timeval *tv)
+struct timespec *rexmpp_timeout (rexmpp_t *s,
+ struct timespec *max_tv,
+ struct timespec *tv)
{
- struct timeval *ret = max_tv;
+ struct timespec *ret = max_tv;
if (s->resolver_state != REXMPP_RESOLVER_NONE &&
s->resolver_state != REXMPP_RESOLVER_READY) {
- ret = ares_timeout(s->resolver_channel, max_tv, tv);
+
} else if (s->tcp_state == REXMPP_TCP_CONNECTING) {
- ret = rexmpp_tcp_conn_timeout(&s->server_connection, max_tv, tv);
+ ret = rexmpp_tcp_conn_timeout(s, &s->server_connection, max_tv, tv);
}
- struct timeval now;
- gettimeofday(&now, NULL);
+
+ ret = rexmpp_jingle_timeout(s, ret, tv);
+
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
if (s->reconnect_number > 0 &&
s->next_reconnect_time.tv_sec > now.tv_sec &&
(ret == NULL ||
s->next_reconnect_time.tv_sec - now.tv_sec < ret->tv_sec)) {
tv->tv_sec = s->next_reconnect_time.tv_sec - now.tv_sec;
- tv->tv_usec = 0;
+ tv->tv_nsec = 0;
ret = tv;
}
if (s->tcp_state == REXMPP_TCP_CONNECTED &&
- s->last_network_activity + s->ping_delay > now.tv_sec) {
- time_t next_ping = s->last_network_activity + s->ping_delay - now.tv_sec;
+ s->stream_state == REXMPP_STREAM_READY) {
+ rexmpp_muc_ping_t *mp = s->muc_ping;
+ while (mp != NULL) {
+ if (mp->last_activity.tv_sec + mp->delay > now.tv_sec) {
+ time_t next_ping =
+ mp->last_activity.tv_sec + mp->delay - now.tv_sec;
+ if (ret == NULL || next_ping < ret->tv_sec) {
+ tv->tv_sec = next_ping;
+ tv->tv_nsec = 0;
+ ret = tv;
+ }
+ }
+ mp = mp->next;
+ }
+ }
+
+ if (s->tcp_state == REXMPP_TCP_CONNECTED &&
+ s->stream_state == REXMPP_STREAM_READY &&
+ s->last_network_activity.tv_sec + s->ping_delay > now.tv_sec) {
+ time_t next_ping =
+ s->last_network_activity.tv_sec + s->ping_delay - now.tv_sec;
if (ret == NULL || next_ping < ret->tv_sec) {
tv->tv_sec = next_ping;
- tv->tv_usec = 0;
+ tv->tv_nsec = 0;
ret = tv;
}
}
+#ifdef HAVE_CURL
+ long curl_timeout; /* in milliseconds */
+ curl_multi_timeout(s->curl_multi, &curl_timeout);
+ if (curl_timeout >= 0 &&
+ (curl_timeout / 1000 < ret->tv_sec ||
+ (curl_timeout / 1000 == ret->tv_sec &&
+ (curl_timeout % 1000) * 1000000 < ret->tv_nsec))) {
+ tv->tv_sec = curl_timeout / 1000;
+ tv->tv_nsec = (curl_timeout % 1000) * 1000000;
+ ret = tv;
+ }
+#endif
+
return ret;
}
diff --git a/src/rexmpp.h b/src/rexmpp.h
index 6157f86..0aa6252 100644
--- a/src/rexmpp.h
+++ b/src/rexmpp.h
@@ -9,45 +9,67 @@
#ifndef REXMPP_H
#define REXMPP_H
-#include <ares.h>
-#include <gnutls/gnutls.h>
-#include <gsasl.h>
-#include <libxml/tree.h>
-#include "rexmpp_tcp.h"
-#include "rexmpp_socks.h"
+#include <stdint.h>
+#include <stdbool.h>
+#include "config.h"
-typedef struct rexmpp rexmpp_t;
-
-/**
- @brief An info/query callback function type.
- @param[in,out] s A ::rexmpp structure.
- @param[in] request A request that was made.
- @param[in] response A response we have received. @c NULL if we are
- giving up on this IQ.
-
- A callback must not free the request or the response, but merely
- inspect those and react.
-*/
-typedef void (*rexmpp_iq_callback_t) (rexmpp_t *s,
- xmlNodePtr request,
- xmlNodePtr response,
- int success);
+#ifdef HAVE_GPGME
+#include <gpgme.h>
+#endif
+#ifdef HAVE_CURL
+#include <curl/curl.h>
+#endif
-typedef struct rexmpp_iq rexmpp_iq_t;
+typedef struct rexmpp rexmpp_t;
-/** @brief A pending info/query request. */
-struct rexmpp_iq
-{
- /** @brief The sent request. */
- xmlNodePtr request;
- /** @brief A callback to call on reply. */
- rexmpp_iq_callback_t cb;
- /** @brief Next pending IQ. */
- rexmpp_iq_t *next;
+/** Error codes. */
+enum rexmpp_err {
+ /** An operation is finished. */
+ REXMPP_SUCCESS,
+ /** An operation is in progress. */
+ REXMPP_E_AGAIN,
+ /** A message can't be queued for sending, because the queue is
+ full. */
+ REXMPP_E_SEND_QUEUE_FULL,
+ /** The library can't take responsibility for message delivery (and
+ doesn't try to send it), because XEP-0198 stanza queue is
+ full. */
+ REXMPP_E_STANZA_QUEUE_FULL,
+ /** An operation (reading or sending) was cancelled by a user. */
+ REXMPP_E_CANCELLED,
+ /** An attempt to send while send buffer is empty. */
+ REXMPP_E_SEND_BUFFER_EMPTY,
+ /** An attempt to start sending while send buffer is not empty. */
+ REXMPP_E_SEND_BUFFER_NOT_EMPTY,
+ /** SASL-related error. */
+ REXMPP_E_SASL,
+ /** OpenPGP-related error. */
+ REXMPP_E_PGP,
+ /** TLS-related error. */
+ REXMPP_E_TLS,
+ /** TCP-related error. */
+ REXMPP_E_TCP,
+ /** DNS-related error. */
+ REXMPP_E_DNS,
+ /** XML-related error. */
+ REXMPP_E_XML,
+ /** JID-related error. */
+ REXMPP_E_JID,
+ /** Failure to allocate memory. */
+ REXMPP_E_MALLOC,
+ /** Roster-related error. */
+ REXMPP_E_ROSTER,
+ /** A roster item is not found. */
+ REXMPP_E_ROSTER_ITEM_NOT_FOUND,
+ /** An erroneous parameter is supplied. */
+ REXMPP_E_PARAM,
+ /** A stream error. */
+ REXMPP_E_STREAM,
+ /** An unspecified error. */
+ REXMPP_E_OTHER
};
-
/** @brief DNS resolver state */
enum resolver_st {
REXMPP_RESOLVER_NONE,
@@ -96,8 +118,6 @@ enum stream_st {
REXMPP_STREAM_SM_ACKS,
/** Resuming a stream. */
REXMPP_STREAM_SM_RESUME,
- /** Restarting a stream. */
- REXMPP_STREAM_RESTART,
/** The streams are ready for use: messaging and other higher-level
things not covered here. */
REXMPP_STREAM_READY,
@@ -111,7 +131,9 @@ enum stream_st {
/** The server-to-client stream is closed. */
REXMPP_STREAM_CLOSED,
/** A stream error was detected in the server-to-client stream. */
- REXMPP_STREAM_ERROR
+ REXMPP_STREAM_ERROR,
+ /** A stream error that should be fixed by a reconnect. */
+ REXMPP_STREAM_ERROR_RECONNECT
};
/** @brief TLS state */
@@ -156,53 +178,81 @@ enum carbons_st {
REXMPP_CARBONS_ACTIVE
};
-/** Error codes. */
-enum rexmpp_err {
- /** An operation is finished. */
- REXMPP_SUCCESS,
- /** An operation is in progress. */
- REXMPP_E_AGAIN,
- /** A message can't be queued for sending, because the queue is
- full. */
- REXMPP_E_SEND_QUEUE_FULL,
- /** The library can't take responsibility for message delivery (and
- doesn't try to send it), because XEP-0198 stanza queue is
- full. */
- REXMPP_E_STANZA_QUEUE_FULL,
- /** An operation (reading or sending) was cancelled by a user. */
- REXMPP_E_CANCELLED,
- /** An attempt to send while send buffer is empty. */
- REXMPP_E_SEND_BUFFER_EMPTY,
- /** An attempt to start sending while send buffer is not empty. */
- REXMPP_E_SEND_BUFFER_NOT_EMPTY,
- /** SASL-related error. */
- REXMPP_E_SASL,
- /** TLS-related error. */
- REXMPP_E_TLS,
- /** TCP-related error. */
- REXMPP_E_TCP,
- /** DNS-related error. */
- REXMPP_E_DNS,
- /** XML-related error. */
- REXMPP_E_XML,
- /** JID-related error. */
- REXMPP_E_JID,
- /** Failure to allocate memory. */
- REXMPP_E_MALLOC,
- /** Roster-related error. */
- REXMPP_E_ROSTER,
- /** A roster item is not found. */
- REXMPP_E_ROSTER_ITEM_NOT_FOUND,
- /** An erroneous parameter is supplied. */
- REXMPP_E_PARAM
+/** @brief TLS policy */
+enum tls_pol {
+ REXMPP_TLS_REQUIRE,
+ REXMPP_TLS_PREFER,
+ REXMPP_TLS_AVOID
};
+
typedef enum rexmpp_err rexmpp_err_t;
+#include "rexmpp_xml.h"
+#include "rexmpp_xml_parser.h"
+#include "rexmpp_tcp.h"
+#include "rexmpp_socks.h"
+#include "rexmpp_dns.h"
+#include "rexmpp_tls.h"
+#include "rexmpp_jid.h"
+#include "rexmpp_jingle.h"
+#include "rexmpp_sasl.h"
+
+/**
+ @brief An info/query callback function type.
+ @param[in,out] s A ::rexmpp structure.
+ @param[in] request A request that was made.
+ @param[in] response A response we have received. @c NULL if we are
+ giving up on this IQ.
+
+ A callback must not free the request or the response, but merely
+ inspect those and react.
+*/
+typedef void (*rexmpp_iq_callback_t) (rexmpp_t *s,
+ void *cb_data,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success);
+
+typedef struct rexmpp_iq rexmpp_iq_t;
+
+/** @brief A pending info/query request. */
+struct rexmpp_iq
+{
+ /** @brief The sent request. */
+ rexmpp_xml_t *request;
+ /** @brief A callback to call on reply. */
+ rexmpp_iq_callback_t cb;
+ /** @brief User-supplied data, to pass to a callback function. */
+ void *cb_data;
+ /** @brief Next pending IQ. */
+ rexmpp_iq_t *next;
+};
+
+typedef struct rexmpp_muc_ping rexmpp_muc_ping_t;
+
+/** @brief MUC self-ping data. */
+struct rexmpp_muc_ping
+{
+ /** @brief Own occupant JID to ping. */
+ char *jid;
+ /** @brief Optional password to rejoin with. */
+ char *password;
+ /** @brief Ping delay, in seconds. */
+ unsigned int delay;
+ /** @brief Whether a ping is requested (pending) already. */
+ int requested;
+ /** @brief When the MUC was active. */
+ struct timespec last_activity;
+ rexmpp_muc_ping_t *next;
+};
+
typedef void (*log_function_t) (rexmpp_t *s, int priority, const char *format, va_list args);
-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 (*sasl_property_cb_t) (rexmpp_t *s, rexmpp_sasl_property prop);
+typedef int (*xml_in_cb_t) (rexmpp_t *s, rexmpp_xml_t *node);
+typedef int (*xml_out_cb_t) (rexmpp_t *s, rexmpp_xml_t *node);
+typedef void (*roster_modify_cb_t) (rexmpp_t *s, rexmpp_xml_t *item);
+typedef int (*console_print_cb_t) (rexmpp_t *s, const char *format, va_list args);
+typedef void (*socket_cb_t) (rexmpp_t *s, int socket);
/** @brief Complete connection state */
struct rexmpp
@@ -219,12 +269,12 @@ struct rexmpp
enum carbons_st carbons_state;
/* Basic configuration. */
- char *initial_jid;
+ struct rexmpp_jid initial_jid;
/* Manual host/port configuration. */
const char *manual_host;
uint16_t manual_port;
- int manual_direct_tls;
+ bool manual_direct_tls;
/* Miscellaneous settings */
const char *disco_node;
@@ -234,15 +284,36 @@ struct rexmpp
uint16_t socks_port;
/* Various knobs (these are used instead of loadable modules). */
- int enable_carbons;
- int enable_service_discovery;
- int manage_roster;
+ bool enable_carbons; /* XEP-0280 */
+ bool manage_roster;
const char *roster_cache_file;
+ bool track_roster_presence;
+ bool track_roster_events; /* XEP-0163 */
+ bool nick_notifications; /* XEP-0172 */
+ bool retrieve_openpgp_keys; /* XEP-0373 */
+ bool autojoin_bookmarked_mucs; /* XEP-0402 */
+ enum tls_pol tls_policy;
+ bool enable_jingle;
+ const char *client_name; /* XEP-0030, XEP-0092 */
+ const char *client_type; /* XEP-0030 */
+ const char *client_version; /* XEP-0092 */
+ const char *local_address; /* For ICE, XEP-0176 */
+ bool jingle_prefer_rtcp_mux;
+ /* A delay in seconds, to use for MUC self-ping by default */
+ unsigned int muc_ping_default_delay;
/* Resource limits. */
uint32_t stanza_queue_size;
uint32_t send_queue_size;
uint32_t iq_queue_size;
+ uint32_t iq_cache_size;
+ uint32_t max_jingle_sessions;
+
+ /* X.509 settings: for TLS and DTLS, to use for SASL EXTERNAL
+ authentication and DTLS-SRTP on Jingle calls. */
+ const char *x509_key_file;
+ const char *x509_cert_file;
+ const char *x509_trust_file;
/* Callbacks. */
log_function_t log_function;
@@ -250,41 +321,57 @@ 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;
+ socket_cb_t socket_cb;
/* Stream-related state. */
- char *assigned_jid;
- xmlNodePtr stream_features;
- xmlNodePtr roster_items;
+ struct rexmpp_jid assigned_jid;
+ rexmpp_xml_t *stream_features;
+ rexmpp_xml_t *roster_items;
char *roster_ver;
+ rexmpp_xml_t *roster_presence;
+ rexmpp_xml_t *roster_events;
/* Other dynamic data. */
- xmlNodePtr disco_info;
+ rexmpp_xml_t *disco_info;
+ /* Includes Jingle RTP session candidates; rexmpp prioritizes the
+ ones listed earlier on incoming calls. */
+ rexmpp_xml_t *jingle_rtp_description;
/* IQs we're waiting for responses to. */
rexmpp_iq_t *active_iq;
+ /* Cached IQ requests and responses. */
+ rexmpp_xml_t *iq_cache;
+
+ /* Jingle context. */
+ rexmpp_jingle_ctx_t *jingle;
+
/* Connection and stream management. */
- unsigned int id_counter;
unsigned int reconnect_number;
time_t reconnect_seconds;
- struct timeval next_reconnect_time;
- xmlNodePtr stanza_queue;
+ struct timespec next_reconnect_time;
+ rexmpp_xml_t *stanza_queue;
uint32_t stanzas_out_count;
uint32_t stanzas_out_acknowledged;
uint32_t stanzas_in_count;
char *stream_id;
/* Server ping configuration and state. */
- int ping_delay;
- int ping_requested;
- time_t last_network_activity;
+ unsigned int ping_delay;
+ bool ping_requested;
+ struct timespec last_network_activity;
+
+ /* MUC self-ping */
+ rexmpp_muc_ping_t *muc_ping;
/* DNS-related structures. */
- ares_channel resolver_channel;
- struct ares_srv_reply *server_srv;
- struct ares_srv_reply *server_srv_cur;
- struct ares_srv_reply *server_srv_tls;
- struct ares_srv_reply *server_srv_tls_cur;
+ rexmpp_dns_ctx_t resolver;
+ rexmpp_dns_result_t *server_srv;
+ int server_srv_cur;
+ rexmpp_dns_result_t *server_srv_tls;
+ int server_srv_tls_cur;
+ struct rexmpp_dns_srv *server_active_srv;
/* The XMPP server we are connecting to. */
const char *server_host;
@@ -292,6 +379,9 @@ struct rexmpp
/* The primary socket used for communication with the server. */
int server_socket;
+ /* Whether the address it's connected to was verified with
+ DNSSEC. */
+ bool server_socket_dns_secure;
/* A structure used to establish a TCP connection. */
rexmpp_tcp_conn_t server_connection;
@@ -302,27 +392,43 @@ struct rexmpp
NULL if there is anything in the send queue). Not appending data
to it, see send_queue for queuing. */
char *send_buffer;
- ssize_t send_buffer_len;
- ssize_t send_buffer_sent;
+ size_t send_buffer_len;
+ size_t send_buffer_sent;
/* A queue of XML elements to send. */
- xmlNodePtr send_queue;
+ rexmpp_xml_t *send_queue;
+
+ /* An input queue of parsed XML structures. */
+ rexmpp_xml_t *input_queue;
+ rexmpp_xml_t *input_queue_last;
/* XML parser context, and current element pointer for building
XML nodes with a SAX2 parser interface. */
- xmlParserCtxtPtr xml_parser;
- xmlNodePtr current_element_root;
- xmlNodePtr current_element;
+ rexmpp_xml_parser_ctx_t xml_parser;
+
+ /* The children are stored in reverse order during building. */
+ rexmpp_xml_t *current_element_root;
+ rexmpp_xml_t *current_element;
/* TLS structures. */
- void *tls_session_data;
- size_t tls_session_data_size;
- gnutls_session_t gnutls_session;
- gnutls_certificate_credentials_t gnutls_cred;
+ rexmpp_tls_t *tls;
/* SASL structures. */
- Gsasl *sasl_ctx;
- Gsasl_session *sasl_session;
+ rexmpp_sasl_ctx_t *sasl;
+
+ /* OpenPGP structures */
+#ifdef HAVE_GPGME
+ gpgme_ctx_t pgp_ctx;
+#else
+ void *pgp_ctx;
+#endif
+
+ /* curl structures */
+#ifdef HAVE_CURL
+ CURLM *curl_multi;
+#else
+ void *curl_multi;
+#endif
};
/**
@@ -331,7 +437,9 @@ struct rexmpp
@param[in] jid Initial bare JID.
@returns ::REXMPP_SUCCESS or some ::rexmpp_err error.
*/
-rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid);
+rexmpp_err_t rexmpp_init (rexmpp_t *s,
+ const char *jid,
+ log_function_t log_function);
/**
@brief ::rexmpp structure deinitialisation. This will free all the
@@ -342,8 +450,8 @@ void rexmpp_done (rexmpp_t *s);
/**
@brief Runs a single iteration.
- @param[in,out] s An initialised :rexmpp structure.
- @param[in] File descriptors available for reading from.
+ @param[in,out] s An initialised ::rexmpp structure.
+ @param[in] read_fds File descriptors available for reading from.
@param[in] write_fds File descriptors available for writing to.
\callergraph
@@ -362,7 +470,7 @@ rexmpp_err_t rexmpp_stop (rexmpp_t *s);
@param[in] node An XML element to send. The library assumes
ownership of the element, so it must not be freed by the caller.
*/
-rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node);
+rexmpp_err_t rexmpp_send (rexmpp_t *s, rexmpp_xml_t *node);
/**
@brief Prepare and send a new info/query request.
@@ -372,29 +480,171 @@ rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node);
@param[in] payload IQ payload, the library assumes ownership of it.
@param[in] cb A ::rexmpp_iq_callback_t function to call on reply
(or if we will give up on it), can be NULL.
+ @param[in] cb_data A data pointer to pass to cb.
This function is specifically for IQs that should be tracked by the
library. If an application wants to track replies on its own, it
should use ::rexmpp_send.
*/
-void rexmpp_iq_new (rexmpp_t *s,
- const char *type,
- const char *to,
- xmlNodePtr payload,
- rexmpp_iq_callback_t cb);
+rexmpp_err_t rexmpp_iq_new (rexmpp_t *s,
+ const char *type,
+ const char *to,
+ rexmpp_xml_t *payload,
+ rexmpp_iq_callback_t cb,
+ void *cb_data);
-struct timeval *rexmpp_timeout (rexmpp_t *s, struct timeval *max_tv, struct timeval *tv);
-int rexmpp_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds);
+/**
+ @brief Same as ::rexmpp_iq_new, but caches responses, and can use
+ cached ones.
+ @param[in] fresh Do not read cache, make a new request.
+*/
+rexmpp_err_t rexmpp_cached_iq_new (rexmpp_t *s,
+ const char *type,
+ const char *to,
+ rexmpp_xml_t *payload,
+ rexmpp_iq_callback_t cb,
+ void *cb_data,
+ int fresh);
+
+/**
+ @brief Reply to an IQ.
+*/
+void rexmpp_iq_reply (rexmpp_t *s,
+ rexmpp_xml_t *req,
+ const char *type,
+ rexmpp_xml_t *payload);
+
+/**
+ @brief Determines the maximum time to wait before the next
+ ::rexmpp_run call.
+ @param[in] s ::rexmpp
+ @param[in] max_tv An existing timeout (can be NULL), to return if
+ there's no more urgent timeouts.
+ @param[in,out] tv An allocated timespec structure, to store the
+ time in.
+ @returns A pointer to either max_tv or tv.
+*/
+struct timespec *rexmpp_timeout (rexmpp_t *s,
+ struct timespec *max_tv,
+ struct timespec *tv);
+/**
+ @brief Sets file descriptors to watch.
+ @param[in] s ::rexmpp
+ @param[out] read_fds File descriptor set to monitor for read
+ events.
+ @param[out] write_fds File descriptor set to monitor for write
+ events.
+ @returns The highest-numbered file descriptor, plus 1. Suitable for
+ select(2) calls.
+*/
+int rexmpp_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds);
-char *rexmpp_xml_serialize (xmlNodePtr node);
-xmlNodePtr rexmpp_xml_add_id (rexmpp_t *s, xmlNodePtr node);
+/**
+ @brief The logging function.
+ @param[in] s ::rexmpp
+ @param[in] priority A syslog priority.
+ @param[in] format
+*/
void rexmpp_log (rexmpp_t *s, int priority, const char *format, ...);
-int rexmpp_xml_match (xmlNodePtr node,
- const char *namespace,
- const char *name);
-xmlNodePtr rexmpp_xml_find_child (xmlNodePtr node,
- const char *namespace,
- const char *name);
+
+/**
+ @brief Gets an appropriate display name for a JID.
+ @param[in] s ::rexmpp
+ @param[in] jid_str A JID string.
+ @returns A newly allocated null-terminated string, or NULL on
+ error.
+*/
+char *rexmpp_get_name (rexmpp_t *s, const char *jid_str);
+
+/**
+ @brief Finds a PEP event.
+ @param[in] s ::rexmpp
+ @param[in] from JID.
+ @param[in] node PEP node.
+ @param[out] prev_event The event preceding the returned one.
+ @returns A pointer to the message announcing an event, or NULL on
+ failure.
+*/
+rexmpp_xml_t *rexmpp_find_event (rexmpp_t *s,
+ const char *from,
+ const char *node,
+ rexmpp_xml_t **prev_event);
+
+void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len);
+
+/**
+ @brief A strerror function for ::rexmpp_err
+ @param[in] error Error code, as returned by rexmpp functions.
+ @returns A string explaining the error.
+*/
+const char *rexmpp_strerror (rexmpp_err_t error);
+
+
+/**
+ @brief Recurisevly searches for a given feature, using service
+ discovery, starting from a given JID. If it finds such a feature,
+ it call the provided callback, providing it both IQ request and
+ response for the entity that provided the feature; if the feature
+ isn't found, it calls the callback with NULL values.
+
+ @param[in,out] s ::rexmpp
+ @param[in] jid An XMPP address to start searching from.
+ @param[in] feature_var A feature to search for.
+ @param[in] cb A ::rexmpp_iq_callback_t function to call on reply.
+ @param[in] cb_data A data pointer to pass to cb.
+ @param[in] fresh Force a new request, instead of looking up the
+ cache.
+ @param[in] max_requests Maximum number of IQ requests to perform
+ before giving up.
+*/
+rexmpp_err_t
+rexmpp_disco_find_feature (rexmpp_t *s,
+ const char *jid,
+ const char *feature_var,
+ rexmpp_iq_callback_t cb,
+ void *cb_data,
+ int fresh,
+ int max_requests);
+
+/**
+ @brief Add a MUC JID to self-ping
+ @param[in,out] s ::rexmpp
+ @param[in] jid Own occupant JID to ping
+ @param[in] password Optional password to rejoin with
+ @param[in] delay How often to ping, in seconds
+*/
+rexmpp_err_t rexmpp_muc_ping_set (rexmpp_t *s,
+ const char *occupant_jid,
+ const char *password,
+ unsigned int delay);
+
+/**
+ @brief Remove a MUC JID to self-ping
+ @param[in,out] s ::rexmpp
+ @param[in] jid Own occupant JID
+*/
+rexmpp_err_t rexmpp_muc_ping_remove (rexmpp_t *s,
+ const char *occupant_jid);
+
+/**
+ @brief Join a MUC, optionally setting self-ping
+ @param[in,out] s ::rexmpp
+ @param[in] occupant_jid Occupant JID
+ @param[in] password Optional password
+ @param[in] ping_delay MUC self-ping delay, 0 to not set it
+*/
+rexmpp_err_t rexmpp_muc_join (rexmpp_t *s,
+ const char *occupant_jid,
+ const char *password,
+ unsigned int ping_delay);
+
+/**
+ @brief Leave a MUC, stop self-pinging it
+ @param[in,out] s ::rexmpp
+ @param[in] occupant_jid Occupant JID
+*/
+rexmpp_err_t rexmpp_muc_leave (rexmpp_t *s, const char *occupant_jid);
+
#endif
diff --git a/src/rexmpp.rs b/src/rexmpp.rs
new file mode 100644
index 0000000..250da00
--- /dev/null
+++ b/src/rexmpp.rs
@@ -0,0 +1,280 @@
+extern crate libc;
+use std::os::raw::{c_char, c_int, c_void, c_uint};
+use libc::{time_t, timespec};
+
+use super::{rexmpp_jid, rexmpp_xml, rexmpp_dns, rexmpp_tcp, rexmpp_socks};
+
+#[derive(PartialEq)]
+#[repr(C)]
+pub enum ResolverState {
+ Ready,
+ SRV,
+ SRV2,
+ Failure
+}
+
+#[derive(PartialEq)]
+#[repr(C)]
+pub enum TCPState {
+ None,
+ Connecting,
+ SOCKS,
+ Connected,
+ Closed,
+ ConnectionFailure,
+ Error
+}
+
+#[derive(PartialEq)]
+#[repr(C)]
+pub enum StreamState {
+ None,
+ Opening,
+ StartTLS,
+ SASL,
+ Bind,
+ SMFull,
+ SMAcks,
+ SMResume,
+ Ready,
+ CloseRequested,
+ Closing,
+ Closed,
+ Error,
+ ErrorReconnect
+}
+
+#[derive(PartialEq)]
+#[repr(C)]
+pub enum TLSState {
+ Inactive,
+ AwaitingDirect,
+ Handshake,
+ Active,
+ Closing,
+ Closed,
+ Error
+}
+
+#[derive(PartialEq)]
+#[repr(C)]
+pub enum SASLState {
+ Inactive,
+ Negotiation,
+ Active,
+ Error
+}
+
+#[derive(PartialEq)]
+#[repr(C)]
+pub enum SMState {
+ Inactive,
+ Negotiation,
+ Active
+}
+
+#[derive(PartialEq)]
+#[repr(C)]
+pub enum CarbonsState {
+ Inactive,
+ Negotiation,
+ Active
+}
+
+#[derive(PartialEq)]
+#[repr(C)]
+pub enum TLSPolicy {
+ Require,
+ Prefer,
+ Avoid
+}
+
+type IQCallback = unsafe extern "C"
+fn (s: *mut Rexmpp, cb_data: *mut c_void,
+ request: *mut rexmpp_xml::RexmppXML, response: *mut rexmpp_xml::RexmppXML,
+ success: c_int) -> ();
+
+type SocketCallback = unsafe extern "C"
+fn (s: *mut Rexmpp, socket: c_int) -> ();
+
+#[repr(C)]
+pub struct RexmppIQ {
+ pub requset: *mut rexmpp_xml::RexmppXML,
+ pub cb: IQCallback,
+ pub cb_data: *const c_void,
+ pub next: *mut RexmppIQ
+}
+
+#[repr(C)]
+pub struct RexmppMUCPing {
+ pub jid: *mut c_char,
+ pub password: *mut c_char,
+ pub delay: c_uint,
+ pub requested: bool,
+ pub last_activity: timespec,
+ pub next: *mut RexmppMUCPing
+}
+
+#[repr(C)]
+pub struct Rexmpp {
+ pub resolver_state: ResolverState,
+ pub tcp_state: TCPState,
+ pub stream_state: StreamState,
+ pub tls_state: TLSState,
+ pub sasl_state: SASLState,
+ pub sm_state: SMState,
+ pub carbons_state: CarbonsState,
+
+ // Basic configuration
+ pub initial_jid: rexmpp_jid::RexmppJID,
+
+ // Manual host/port configuration
+ pub manual_host: *const c_char,
+ pub manual_port: u16,
+ pub manual_direct_tls: bool,
+
+ // Miscellaneous settings
+ pub disco_node: *const c_char,
+
+ // SOCKS settings
+ pub socks_host: *const c_char,
+ pub socks_port: u16,
+
+ // Various knobs (these are used instead of loadable modules)
+ pub enable_carbons: bool, // XEP-0280
+ pub manage_roster: bool,
+ pub roster_cache_file: *const c_char,
+ pub track_roster_presence: bool,
+ pub track_roster_events: bool, // XEP-0163
+ pub nick_notifications: bool, // XEP-0172
+ pub retrieve_openpgp_keys: bool, // XEP-0373
+ pub autojoin_bookmarked_mucs: bool, // XEP-0402
+ pub tls_policy: TLSPolicy,
+ pub enable_jingle: bool,
+ pub client_name: *const c_char, // XEP-0030, XEP-0092
+ pub client_type: *const c_char, // XEP-0030
+ pub client_version: *const c_char, // XEP-0092
+ pub local_address: *const c_char, // For ICE, XEP-0176
+ pub jingle_prefer_rtcp_mux: bool,
+ // A delay in seconds, to use for MUC self-ping by default
+ pub muc_ping_default_delay: c_uint,
+ // Resource limits
+ pub stanza_queue_size: u32,
+ pub send_queue_size: u32,
+ pub iq_queue_size: u32,
+ pub iq_cache_size: u32,
+ pub max_jingle_sessions: u32,
+
+ // X.509 settings (for TLS and DTLS)
+ pub x509_key_file: *const c_char,
+ pub x509_cert_file: *const c_char,
+ pub x509_trust_file: *const c_char,
+
+ // Callbacks
+
+ // c_variadic is experimental and cannot be used on the stable
+ // release channel, so skipping the log function callback.
+ pub log_function: *const c_void,
+ // Actually skipping proper definitions of others for now as well
+ // (TODO: add them).
+ pub sasl_property_cb: *const c_void,
+ pub xml_in_cb: *const c_void,
+ pub xml_out_cb: *const c_void,
+ pub roster_modify_cb: *const c_void,
+ pub console_print_cb: *const c_void,
+ pub socket_cb: Option<SocketCallback>,
+
+ // Stream-related state
+ pub assigned_jid: rexmpp_jid::RexmppJID,
+ pub stream_features: *mut rexmpp_xml::RexmppXML,
+ pub roster_items: *mut rexmpp_xml::RexmppXML,
+ pub roster_ver: *mut c_char,
+ pub roster_presence: *mut rexmpp_xml::RexmppXML,
+ pub roster_events: *mut rexmpp_xml::RexmppXML,
+
+ // Other dynamic data
+ pub disco_info: *mut rexmpp_xml::RexmppXML,
+ // Includes Jingle RTP session candidates; rexmpp prioritizes the
+ // ones listed earlier on incoming calls
+ pub jingle_rtp_description: *mut rexmpp_xml::RexmppXML,
+
+ // IQs we're waiting for responses to
+ pub active_iq: *mut RexmppIQ,
+ pub iq_cache: *mut rexmpp_xml::RexmppXML,
+
+ // Jingle context
+ pub jingle: *const c_void, // TODO
+
+ // Connection and stream management
+ pub reconnect_number: c_uint,
+ pub reconnect_seconds: time_t,
+ pub next_reconnect_time: timespec,
+ pub stanza_queue: *mut rexmpp_xml::RexmppXML,
+ pub stanzas_out_count: u32,
+ pub stanzas_out_acknowledged: u32,
+ pub stanzas_in_count: u32,
+ pub stream_id: *mut c_char,
+
+ // Server ping configuration and state
+ pub ping_delay: c_uint,
+ pub ping_requested: bool,
+ pub last_network_activity: timespec,
+
+ // MUC self-ping
+ pub muc_ping: *mut RexmppMUCPing,
+
+ // DNS-related structures
+ pub resolver: *mut c_void,
+ pub server_srv: *mut rexmpp_dns::RexmppDNSResult,
+ pub server_srv_cur: c_int,
+ pub server_srv_tls: *mut rexmpp_dns::RexmppDNSResult,
+ pub server_srv_tls_cur: c_int,
+ pub server_active_srv: *mut rexmpp_dns::RexmppDNSSRV,
+
+ // The XMPP server we are connecting to
+ pub server_host: *const c_char,
+ pub server_port: u16,
+
+ // The primary socket used for communication with the server
+ pub server_socket: c_int,
+ // Whether the address it's connected to was verified with DNSSEC
+ pub server_socket_dns_secure: bool,
+
+ // A structure used to establish a TCP connection
+ pub server_connection: rexmpp_tcp::RexmppTCPConnection,
+ pub server_socks_conn: rexmpp_socks::RexmppSocks,
+
+ // Send buffer. NULL if there is nothing to send (and must not be
+ // NULL if there is anything in the send queue). Not appending
+ // data to it, see send_queue for queuing.
+ pub send_buffer: *mut c_char,
+ pub send_buffer_len: isize,
+ pub send_buffer_sent: isize,
+
+ // A queue of XML elements to send
+ pub send_queue: *mut rexmpp_xml::RexmppXML,
+
+ // An input queue of parsed XML structures
+ pub input_queue: *mut rexmpp_xml::RexmppXML,
+ pub input_queue_last: *mut rexmpp_xml::RexmppXML,
+
+ // XML parser context, and current element pointer for building
+ // XML nodes with a SAX2 parser interface
+ pub xml_parser: *mut c_void,
+
+ // The children are stored in reverse order during building
+ pub current_element_root: *mut rexmpp_xml::RexmppXML,
+ pub current_element: *mut rexmpp_xml::RexmppXML,
+
+ // TLS structures
+ pub tls: *mut c_void,
+
+ // SASL structures
+ pub sasl: *mut c_void,
+
+ // OpenPGP structures
+ pub pgp_ctx: *mut c_void,
+
+ // curl structures
+ pub curl_multi: *mut c_void
+}
diff --git a/src/rexmpp_base64.c b/src/rexmpp_base64.c
new file mode 100644
index 0000000..28afc7b
--- /dev/null
+++ b/src/rexmpp_base64.c
@@ -0,0 +1,127 @@
+/**
+ @file rexmpp_base64.c
+ @brief Base64 implementation
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+
+ Implements RFC 4648.
+*/
+
+#include <stdlib.h>
+#include <stddef.h>
+
+#include "rexmpp_base64.h"
+
+
+char map_range (char x, char from_start, char from_end, char to_start, char otherwise) {
+ if (x >= from_start && x <= from_end) {
+ return (to_start + x - from_start);
+ } else {
+ return otherwise;
+ }
+}
+
+char to_b64 (char x) {
+ return map_range(x, 0, 25, 'A',
+ map_range(x, 26, 51, 'a',
+ map_range(x, 52, 61, '0',
+ map_range(x, 62, 62, '+',
+ map_range(x, 63, 63, '/',
+ '=')))));
+}
+
+char from_b64 (char x) {
+ return map_range(x, 'A', 'Z', 0,
+ map_range(x, 'a', 'z', 26,
+ map_range(x, '0', '9', 52,
+ map_range(x, '+', '+', 62,
+ map_range(x, '/', '/', 63,
+ 64)))));
+}
+
+
+int rexmpp_base64_to (const char *in, size_t in_len, char **out, size_t *out_len) {
+ if (in_len == 0) {
+ return -1;
+ }
+ if (in == NULL) {
+ return -1;
+ }
+ *out_len = (in_len + 2) / 3 * 4;
+ char *res = malloc(*out_len + 1);
+ if (res == NULL) {
+ return -1;
+ }
+ res[*out_len] = '\0';
+ *out = res;
+ while (res < *out + *out_len) {
+ char a = in[0];
+ char b = in_len > 1 ? in[1] : 0;
+ char c = in_len > 2 ? in[2] : 0;
+ res[0] = to_b64((a & 0xFC) >> 2);
+ res[1] = to_b64(((a & 0x03) << 4) | ((b & 0xF0) >> 4));
+ res[2] = in_len > 1 ? to_b64((((b & 0x0F) << 2) | ((c & 0xC0) >> 6))) : '=';
+ res[3] = in_len > 2 ? to_b64((c & 0x3F)) : '=';
+ in_len -= 3;
+ in += 3;
+ res += 4;
+ }
+ return 0;
+}
+
+int rexmpp_base64_from (const char *in, size_t in_len, char **out, size_t *out_len) {
+ if (in_len == 0) {
+ return -1;
+ }
+ if (in == NULL) {
+ return -1;
+ }
+ if (in_len % 4) {
+ return -1;
+ }
+ *out_len = in_len / 4 * 3;
+ char *res = malloc(*out_len);
+ if (res == NULL) {
+ return -1;
+ }
+ *out = res;
+ while (res < *out + *out_len) {
+ char a = from_b64(in[0]);
+ char b = from_b64(in[1]);
+ char c = in[2] == '=' ? 0 : from_b64(in[2]);
+ char d = in[3] == '=' ? 0 : from_b64(in[3]);
+ if ((a | b | c | d) & 0xC0) {
+ free(*out);
+ *out = NULL;
+ *out_len = 0;
+ return -1;
+ }
+ res[0] = (a << 2) | ((b & 0x30) >> 4);
+ res[1] = ((b & 0x0F) << 4) | ((c & 0x3C) >> 2);
+ res[2] = ((c & 0x03) << 6) | d;
+
+ if (in[2] == '=') {
+ if (in[3] != '=') {
+ free(*out);
+ *out = NULL;
+ *out_len = 0;
+ return -1;
+ }
+ *out_len = *out_len - 1;
+ }
+ if (in[3] == '=') {
+ if (res + 3 < *out + *out_len) {
+ free(*out);
+ *out = NULL;
+ *out_len = 0;
+ return -1;
+ }
+ *out_len = *out_len - 1;
+ }
+
+ in += 4;
+ res += 3;
+ }
+ return 0;
+}
diff --git a/src/rexmpp_base64.h b/src/rexmpp_base64.h
new file mode 100644
index 0000000..d11cd91
--- /dev/null
+++ b/src/rexmpp_base64.h
@@ -0,0 +1,35 @@
+/**
+ @file rexmpp_base64.h
+ @brief Base64 implementation
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+
+ Implements RFC 4648, with API similar to gsasl's.
+*/
+
+#include <stddef.h>
+
+/**
+ @brief Encodes data in Base64
+ @param[in] in Data to encode
+ @param[in] in_len Length of the input data
+ @param[out] out A pointer to the output buffer; its memory will be
+ allocated by the function, the caller receives ownership over it
+ @param[out] out_len Length of the produced Base64-encoded string
+ @returns 0 on success, a non-zero value otherwise
+*/
+int rexmpp_base64_to (const char *in, size_t in_len,
+ char **out, size_t *out_len);
+
+/**
+ @brief Decodes data from Base64
+ @param[in] in Data to decode
+ @param[in] in_len Length of the input data
+ @param[out] out A pointer to the output buffer; its memory will be
+ allocated by the function, the caller receives ownership over it
+ @param[out] out_len Length of the decoded string
+ @returns 0 on success, a non-zero value otherwise
+*/
+int rexmpp_base64_from (const char *in, size_t in_len,
+ char **out, size_t *out_len);
diff --git a/src/rexmpp_console.c b/src/rexmpp_console.c
new file mode 100644
index 0000000..a6f859a
--- /dev/null
+++ b/src/rexmpp_console.c
@@ -0,0 +1,914 @@
+/**
+ @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 <stdlib.h>
+#include <stdarg.h>
+
+#include "rexmpp.h"
+#include "rexmpp_xml.h"
+#include "rexmpp_openpgp.h"
+#include "rexmpp_http_upload.h"
+#include "rexmpp_jingle.h"
+#include "rexmpp_pubsub.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);
+ }
+}
+
+const char *rexmpp_console_message_string (rexmpp_t *s, rexmpp_xml_t *node) {
+ const char *ret = NULL;
+ rexmpp_xml_t *openpgp =
+ rexmpp_xml_find_child(node, "urn:xmpp:openpgp:0", "openpgp");
+ if (openpgp != NULL) {
+ int valid;
+ rexmpp_xml_t *elem = rexmpp_openpgp_decrypt_verify_message(s, node, &valid);
+ if (! valid) {
+ rexmpp_console_printf(s, "An invalid OpenPGP message!\n");
+ }
+
+ if (elem != NULL) {
+ rexmpp_xml_t *payload =
+ rexmpp_xml_find_child(elem, "urn:xmpp:openpgp:0", "payload");
+ if (payload != NULL) {
+ rexmpp_xml_t *pl_body =
+ rexmpp_xml_find_child(payload, "jabber:client", "body");
+ if (pl_body != NULL) {
+ ret = rexmpp_xml_text_child(pl_body);
+ }
+ }
+ rexmpp_xml_free(elem);
+ }
+ }
+ if (ret == NULL) {
+ rexmpp_xml_t *body =
+ rexmpp_xml_find_child(node, "jabber:client", "body");
+ ret = rexmpp_xml_text_child(body);
+ }
+ return ret;
+}
+
+void rexmpp_console_on_send (rexmpp_t *s, rexmpp_xml_t *node) {
+ if (rexmpp_xml_match(node, "jabber:client", "message")) {
+ const char *to = rexmpp_xml_find_attr_val(node, "to");
+ if (to != NULL) {
+ /* "from" should be set for verification. */
+ int added_from = 0;
+ if (rexmpp_xml_find_attr_val(node, "from") == NULL) {
+ rexmpp_xml_add_attr(node, "from", to);
+ added_from = 1;
+ }
+ const char *str = rexmpp_console_message_string(s, node);
+ if (added_from) {
+ rexmpp_xml_remove_attr(node, "from");
+ }
+ if (str != NULL) {
+ rexmpp_console_printf(s, "You tell %s: %s\n", to, str);
+ }
+ }
+ }
+ if (rexmpp_xml_match(node, "jabber:client", "presence")) {
+ const char *presence_type = rexmpp_xml_find_attr_val(node, "type");
+ const char *presence_to = rexmpp_xml_find_attr_val(node, "to");
+ if (presence_to == NULL) {
+ rexmpp_console_printf(s, "Becoming %s\n",
+ (presence_type == NULL) ?
+ "available" :
+ presence_type);
+ } else {
+ if (presence_type != NULL && ! strcmp(presence_type, "subscribe")) {
+ rexmpp_console_printf(s,
+ "Requesting a subscription to %s's presence.\n",
+ presence_to);
+ }
+ if (presence_type != NULL && ! strcmp(presence_type, "subscribed")) {
+ rexmpp_console_printf(s,
+ "Approving %s's presence subscription request.\n",
+ presence_to);
+ }
+ if (presence_type != NULL && ! strcmp(presence_type, "unsubscribed")) {
+ rexmpp_console_printf(s,
+ "Denying %s's presence subscription request.\n",
+ presence_to);
+ }
+ }
+ }
+}
+
+void rexmpp_console_on_recv (rexmpp_t *s, rexmpp_xml_t *node) {
+ if (rexmpp_xml_match(node, "jabber:client", "message")) {
+ rexmpp_xml_t *sent = rexmpp_xml_find_child(node, "urn:xmpp:carbons:2", "sent");
+ if (sent != NULL) {
+ rexmpp_xml_t *fwd =
+ rexmpp_xml_find_child(sent, "urn:xmpp:forward:0", "forwarded");
+ if (fwd != NULL) {
+ rexmpp_xml_t *msg =
+ rexmpp_xml_find_child(fwd, "jabber:client", "message");
+ if (msg != NULL) {
+ const char *to = rexmpp_xml_find_attr_val(msg, "to");
+ const char *str = rexmpp_console_message_string(s, msg);
+ if (str != NULL) {
+ rexmpp_console_printf(s, "You tell %s: %s\n", to, str);
+ }
+ }
+ }
+ }
+
+ rexmpp_xml_t *received =
+ rexmpp_xml_find_child(node, "urn:xmpp:carbons:2", "received");
+ if (received != NULL) {
+ rexmpp_xml_t *fwd =
+ rexmpp_xml_find_child(received, "urn:xmpp:forward:0", "forwarded");
+ if (fwd != NULL) {
+ rexmpp_xml_t *msg =
+ rexmpp_xml_find_child(fwd, "jabber:client", "message");
+ if (msg != NULL) {
+ const char *from = rexmpp_xml_find_attr_val(msg, "from");
+ const char *str = rexmpp_console_message_string(s, msg);
+ if (str != NULL) {
+ rexmpp_console_printf(s, "%s tells you: %s\n", from, str);
+ }
+ }
+ }
+ }
+
+ const char *from = rexmpp_xml_find_attr_val(node, "from");
+ if (from != NULL) {
+ const char *str = rexmpp_console_message_string(s, node);
+ if (str != NULL) {
+ rexmpp_console_printf(s, "%s tells you: %s\n", from, str);
+ }
+ }
+ }
+ if (rexmpp_xml_match(node, "jabber:client", "presence")) {
+ const char *presence_type = rexmpp_xml_find_attr_val(node, "type");
+ const char *from = rexmpp_xml_find_attr_val(node, "from");
+ if (presence_type != NULL && ! strcmp(presence_type, "subscribe")) {
+ rexmpp_console_printf(s, "%s requests a presence subscription\n", from);
+ } else if (presence_type != NULL && ! strcmp(presence_type, "subscribed")) {
+ rexmpp_console_printf(s, "%s approves a presence subscription\n", from);
+ } else if (presence_type != NULL && ! strcmp(presence_type, "unsubscribed")) {
+ rexmpp_console_printf(s, "%s denies a presence subscription\n", from);
+ } else {
+ rexmpp_console_printf(s, "%s is %s", from,
+ (presence_type == NULL) ?
+ "available" :
+ presence_type);
+ rexmpp_xml_t *show =
+ rexmpp_xml_find_child(node, "jabber:client", "show");
+ if (show != NULL) {
+ rexmpp_console_printf(s, " (%s)",
+ rexmpp_xml_text_child(show));
+ }
+ rexmpp_xml_t *status =
+ rexmpp_xml_find_child(node, "jabber:client", "status");
+ if (status != NULL) {
+ rexmpp_console_printf(s, ": %s",
+ rexmpp_xml_text_child(status));
+ }
+ rexmpp_console_printf(s, "\n");
+ }
+ }
+}
+
+
+void rexmpp_console_roster_deleted (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)response;
+ rexmpp_xml_t *item =
+ rexmpp_xml_find_child(rexmpp_xml_find_child(req,
+ "jabber:iq:roster",
+ "query"),
+ "jabber:iq:roster", "item");
+ const char *jid = rexmpp_xml_find_attr_val(item, "jid");
+ if (success) {
+ rexmpp_console_printf(s, "Deleted %s from the roster.\n", jid);
+ } else {
+ rexmpp_console_printf(s, "Failed to delete %s from the roster.\n", jid);
+ }
+}
+
+void rexmpp_console_roster_added (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)response;
+ rexmpp_xml_t *item =
+ rexmpp_xml_find_child(rexmpp_xml_find_child(req,
+ "jabber:iq:roster",
+ "query"),
+ "jabber:iq:roster", "item");
+ const char *jid = rexmpp_xml_find_attr_val(item, "jid");
+ if (success) {
+ rexmpp_console_printf(s, "Added %s into the roster.\n", jid);
+ } else {
+ rexmpp_console_printf(s, "Failed to add %s into the roster.\n", jid);
+ }
+}
+
+void rexmpp_console_on_run (rexmpp_t *s, rexmpp_err_t result) {
+ if (result == REXMPP_SUCCESS) {
+ rexmpp_console_printf(s, "Done.\n");
+ return;
+ }
+}
+
+void rexmpp_console_on_upload (rexmpp_t *s, void *cb_data, const char *url) {
+ char *fpath = cb_data;
+ if (url == NULL) {
+ rexmpp_console_printf(s, "Failed to upload %s.\n", fpath);
+ } else {
+ rexmpp_console_printf(s, "Uploaded %s to <%s>.\n", fpath, url);
+ }
+ free(fpath);
+}
+
+void rexmpp_console_disco_info (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)req;
+ if (! success) {
+ rexmpp_console_printf(s, "Failed to discover info.\n");
+ return;
+ }
+ rexmpp_xml_t *query =
+ rexmpp_xml_find_child(response, "http://jabber.org/protocol/disco#info",
+ "query");
+ if (query == NULL) {
+ rexmpp_console_printf(s, "No disco#info query in response.\n");
+ return;
+ }
+ const char *from = rexmpp_xml_find_attr_val(response, "from");
+ if (from == NULL) {
+ rexmpp_console_printf(s, "No 'from' property in response.\n");
+ return;
+ }
+ rexmpp_console_printf(s, "Discovered info for %s:\n", from);
+ rexmpp_xml_t *child = rexmpp_xml_first_elem_child(query);
+ while (child != NULL) {
+ if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#info",
+ "feature")) {
+ const char *var = rexmpp_xml_find_attr_val(child, "var");
+ rexmpp_console_printf(s, "- feature var %s\n", var);
+ } else if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#info",
+ "identity")) {
+ const char *category = rexmpp_xml_find_attr_val(child, "category");
+ const char *type = rexmpp_xml_find_attr_val(child, "type");
+ const char *name = rexmpp_xml_find_attr_val(child, "name");
+ rexmpp_console_printf(s, "- identity name %s, type %s, category %s\n",
+ name, type, category);
+ } else {
+ rexmpp_console_printf(s, "Encountered an unknown disco#info element.\n");
+ }
+ child = rexmpp_xml_next_elem_sibling(child);
+ }
+ rexmpp_console_printf(s, "(end of discovered info for %s)\n", from);
+}
+
+void rexmpp_console_disco_items (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)req;
+ if (! success) {
+ rexmpp_console_printf(s, "Failed to discover items.\n");
+ return;
+ }
+ rexmpp_xml_t *query =
+ rexmpp_xml_find_child(response, "http://jabber.org/protocol/disco#items",
+ "query");
+ if (query == NULL) {
+ rexmpp_console_printf(s, "No disco#items query in response.\n");
+ return;
+ }
+ const char *from = rexmpp_xml_find_attr_val(response, "from");
+ if (from == NULL) {
+ rexmpp_console_printf(s, "No 'from' property in response.\n");
+ return;
+ }
+ rexmpp_console_printf(s, "Discovered items for %s:\n", from);
+ rexmpp_xml_t *child = rexmpp_xml_first_elem_child(query);
+ while (child != NULL) {
+ if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#items",
+ "item")) {
+ const char *jid = rexmpp_xml_find_attr_val(child, "jid");
+ const char *name = rexmpp_xml_find_attr_val(child, "name");
+ const char *node = rexmpp_xml_find_attr_val(child, "node");
+ rexmpp_console_printf(s, "- item jid %s, name %s, node %s\n", jid, name, node);
+ } else {
+ rexmpp_console_printf(s, "Encountered an unknown disco#items element.\n");
+ }
+ child = rexmpp_xml_next_elem_sibling(child);
+ }
+ rexmpp_console_printf(s, "(end of discovered items for %s)\n", from);
+}
+
+void rexmpp_console_pubsub_node_deleted (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)req;
+ (void)response;
+ if (success) {
+ rexmpp_console_printf(s, "Deleted the pubsub node.\n");
+ } else {
+ rexmpp_console_printf(s, "Failed to delete the pubsub node.\n");
+ }
+}
+
+void rexmpp_bookmark_added (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)req;
+ (void)response;
+ if (success) {
+ rexmpp_console_printf(s, "Added a bookmark.\n");
+ } else {
+ rexmpp_console_printf(s, "Failed to add a bookmark.\n");
+ }
+}
+
+void rexmpp_bookmark_removed (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)req;
+ (void)response;
+ if (success) {
+ rexmpp_console_printf(s, "Removed a bookmark.\n");
+ } else {
+ rexmpp_console_printf(s, "Failed to remove a bookmark.\n");
+ }
+}
+
+void rexmpp_console_blocklist (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)req;
+ if (success) {
+ rexmpp_xml_t *bl =
+ rexmpp_xml_find_child(response, "urn:xmpp:blocking", "blocklist");
+ if (bl == NULL) {
+ rexmpp_console_printf(s, "No blocklist element in the response.\n");
+ return;
+ }
+ rexmpp_console_printf(s, "Block list:");
+ rexmpp_xml_t *child = rexmpp_xml_first_elem_child(bl);
+ while (child != NULL) {
+ if (rexmpp_xml_match(child, "urn:xmpp:blocking", "item")) {
+ const char *jid = rexmpp_xml_find_attr_val(child, "jid");
+ rexmpp_console_printf(s, " %s", jid);
+ } else {
+ rexmpp_console_printf(s, "Encountered an unknown blocklist child element.\n");
+ }
+ child = rexmpp_xml_next_elem_sibling(child);
+ }
+ rexmpp_console_printf(s, "\n");
+ } else {
+ rexmpp_console_printf(s, "Failed to retrieve block list.\n");
+ }
+}
+
+void rexmpp_console_blocklist_blocked (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)req;
+ (void)response;
+ if (success) {
+ rexmpp_console_printf(s, "Blocklisted successfully.\n");
+ } else {
+ rexmpp_console_printf(s, "Failed to blocklist.\n");
+ }
+}
+
+void rexmpp_console_blocklist_unblocked (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)req;
+ (void)response;
+ if (success) {
+ rexmpp_console_printf(s, "Un-blocklisted successfully.\n");
+ } else {
+ rexmpp_console_printf(s, "Failed to un-blocklist.\n");
+ }
+}
+
+void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) {
+ /* todo: buffering */
+ (void)str_len; /* Unused for now (todo). */
+ char *words_save_ptr;
+ rexmpp_xml_t *presence;
+ 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"
+ "sign <jid> <message>\n"
+ "crypt <jid> <message>\n"
+ "key publish <fingerprint>\n"
+ "key retract <fingerprint>\n"
+ "muc join <conference> [as] <nick>\n"
+ "muc leave <conference> [as] <nick>\n"
+ "muc tell <conference> <message>\n"
+ "bookmark add <conference> [autojoin] [nick] [password]\n"
+ "bookmark remove <conference>\n"
+ "roster list\n"
+ "roster add <jid>\n"
+ "roster delete <jid>\n"
+ "subscription request <jid>\n"
+ "subscription approve <jid>\n"
+ "subscription deny <jid>\n"
+ "http-upload <file path>\n"
+ "jingle terminate <sid>\n"
+ "jingle decline <sid>\n"
+ "jingle accept-file <sid> <file path>\n"
+ "jingle send-file <jid> <file path>\n"
+ "jingle accept-call <sid>\n"
+ "jingle call <jid>\n"
+ "disco info <jid>\n"
+ "disco items <jid>\n"
+ "pubsub node delete <service_jid> <node>\n"
+ "blocklist\n"
+ "blocklist block <jid>\n"
+ "blocklist unblock <jid>\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, "key")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (! strcmp(word, "publish")) {
+ char *fingerprint = strtok_r(NULL, " ", &words_save_ptr);
+ rexmpp_openpgp_publish_key(s, fingerprint);
+ }
+ if (! strcmp(word, "retract")) {
+ char *fingerprint = strtok_r(NULL, " ", &words_save_ptr);
+ rexmpp_openpgp_retract_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;
+
+ rexmpp_xml_t *msg = rexmpp_xml_new_elem("message", "jabber:client");
+ rexmpp_xml_add_id(msg);
+ rexmpp_xml_add_attr(msg, "to", jid.full);
+ rexmpp_xml_add_attr(msg, "type", "chat");
+ rexmpp_xml_t *body = rexmpp_xml_new_elem("body", NULL);
+ rexmpp_xml_add_text(body, msg_text);
+ rexmpp_xml_add_child(msg, body);
+ rexmpp_send(s, msg);
+ }
+
+ if ((strcmp(word, "signcrypt") == 0) ||
+ (strcmp(word, "sign") == 0) ||
+ (strcmp(word, "crypt") == 0)) {
+ 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;
+ rexmpp_xml_t *body =
+ rexmpp_xml_new_elem("body", "jabber:client");
+ rexmpp_xml_add_text(body, msg_text);
+ const char *rcpt[2];
+ rcpt[0] = jid.full;
+ rcpt[1] = NULL;
+ char *b64 = NULL;
+ if (strcmp(word, "signcrypt") == 0) {
+ b64 = rexmpp_openpgp_payload(s, body, rcpt, NULL, REXMPP_OX_SIGNCRYPT);
+ } else if (strcmp(word, "sign") == 0) {
+ b64 = rexmpp_openpgp_payload(s, body, rcpt, NULL, REXMPP_OX_SIGN);
+ } else if (strcmp(word, "crypt") == 0) {
+ b64 = rexmpp_openpgp_payload(s, body, rcpt, NULL, REXMPP_OX_CRYPT);
+ }
+ rexmpp_xml_t *openpgp =
+ rexmpp_xml_new_elem("openpgp", "urn:xmpp:openpgp:0");
+ rexmpp_xml_add_text(openpgp, b64);
+ free(b64);
+
+ rexmpp_xml_t *msg = rexmpp_xml_new_elem("message", "jabber:client");
+ rexmpp_xml_add_id(msg);
+ rexmpp_xml_add_attr(msg, "to", jid.full);
+ rexmpp_xml_add_attr(msg, "type", "chat");
+ rexmpp_xml_add_child(msg, openpgp);
+
+ body = rexmpp_xml_new_elem("body", "jabber:client");
+ rexmpp_xml_add_text(body, "This is a secret message.");
+ rexmpp_xml_add_child(msg, body);
+
+ rexmpp_send(s, msg);
+ }
+
+ if (! strcmp(word, "muc")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ 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;
+
+ rexmpp_xml_t *msg = rexmpp_xml_new_elem("message", "jabber:client");
+ rexmpp_xml_add_id(msg);
+ rexmpp_xml_add_attr(msg, "to", jid.full);
+ rexmpp_xml_add_attr(msg, "type", "groupchat");
+ rexmpp_xml_t *body = rexmpp_xml_new_elem("body", NULL);
+ rexmpp_xml_add_text(body, msg_text);
+ rexmpp_xml_add_child(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 (word == NULL) {
+ return;
+ }
+ if (! strcmp(word, "as")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ }
+ if (word == NULL) {
+ return;
+ }
+ char *occupant_jid = malloc(strlen(jid.bare) + strlen(word) + 2);
+ snprintf(occupant_jid, strlen(jid_str) + strlen(word) + 2, "%s/%s",
+ jid.bare, word);
+ rexmpp_muc_join(s, occupant_jid, NULL, s->muc_ping_default_delay);
+ free(occupant_jid);
+ }
+ if (! strcmp(word, "leave")) {
+ 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 (word == NULL) {
+ return;
+ }
+ if (! strcmp(word, "as")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ }
+ if (word == NULL) {
+ return;
+ }
+ char *occupant_jid = malloc(strlen(jid.bare) + strlen(word) + 2);
+ snprintf(occupant_jid, strlen(jid_str) + strlen(word) + 2, "%s/%s",
+ jid.bare, word);
+ rexmpp_muc_leave(s, occupant_jid);
+ free(occupant_jid);
+ }
+ }
+
+ if (! strcmp(word, "bookmark")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (! strcmp(word, "add")) {
+ char *muc_jid = strtok_r(NULL, " ", &words_save_ptr);
+ if (muc_jid == NULL) {
+ return;
+ }
+ char *autojoin = strtok_r(NULL, " ", &words_save_ptr);
+ char *nick_str = strtok_r(NULL, " ", &words_save_ptr);
+ char *password = strtok_r(NULL, " ", &words_save_ptr);
+
+ rexmpp_xml_t *conference =
+ rexmpp_xml_new_elem("conference", "urn:xmpp:bookmarks:1");
+ if (autojoin != NULL) {
+ rexmpp_xml_add_attr(conference, "autojoin", autojoin);
+ }
+ if (password != NULL) {
+ rexmpp_xml_add_attr(conference, "password", password);
+ }
+ if (nick_str != NULL) {
+ rexmpp_xml_t *nick =
+ rexmpp_xml_new_elem("nick", "urn:xmpp:bookmarks:1");
+ rexmpp_xml_add_text(nick, nick_str);
+ rexmpp_xml_add_child(conference, nick);
+ }
+ rexmpp_pubsub_item_publish(s, NULL, "urn:xmpp:bookmarks:1", muc_jid,
+ conference, rexmpp_bookmark_added, NULL);
+ }
+ if (! strcmp(word, "remove")) {
+ char *muc_jid = strtok_r(NULL, " ", &words_save_ptr);
+ if (muc_jid == NULL) {
+ return;
+ }
+ rexmpp_pubsub_item_retract(s, NULL, "urn:xmpp:bookmarks:1", muc_jid,
+ rexmpp_bookmark_removed, NULL);
+ }
+ }
+
+ if (! strcmp(word, "roster")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ return;
+ }
+ if (! strcmp(word, "list")) {
+ rexmpp_xml_t *item;
+ for (item = s->roster_items;
+ item != NULL;
+ item = item->next) {
+ const char *item_jid = rexmpp_xml_find_attr_val(item, "jid");
+ const char *item_ask = rexmpp_xml_find_attr_val(item, "ask");
+ const char *item_subscription =
+ rexmpp_xml_find_attr_val(item, "subscription");
+ char *item_presence = "unavailable";
+ if (s->track_roster_presence) {
+ for (presence = s->roster_presence;
+ presence != NULL;
+ presence = presence->next) {
+ const char *presence_from =
+ rexmpp_xml_find_attr_val(presence, "from");
+ if (presence_from != NULL) {
+ rexmpp_jid_parse(presence_from, &jid);
+ if (! strcmp(jid.bare, item_jid)) {
+ item_presence = "available";
+ }
+ }
+ }
+ }
+ rexmpp_console_printf(s,
+ "%s: subscription = %s, ask = %s, "
+ "presence = %s\n",
+ item_jid, item_subscription, item_ask,
+ item_presence);
+ }
+ } else if (! strcmp(word, "delete")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ return;
+ }
+ rexmpp_xml_t *delete_item =
+ rexmpp_xml_new_elem("item", "jabber:iq:roster");
+ rexmpp_xml_add_attr(delete_item, "jid", word);
+ rexmpp_xml_add_attr(delete_item, "subscription", "remove");
+ rexmpp_xml_t *delete_query =
+ rexmpp_xml_new_elem("query", "jabber:iq:roster");
+ rexmpp_xml_add_child(delete_query, delete_item);
+ rexmpp_iq_new(s, "set", NULL, delete_query,
+ rexmpp_console_roster_deleted, NULL);
+ } else if (! strcmp(word, "add")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ return;
+ }
+ rexmpp_xml_t *add_item =
+ rexmpp_xml_new_elem("item", "jabber:iq:roster");
+ rexmpp_xml_add_attr(add_item, "jid", word);
+ rexmpp_xml_t *add_query =
+ rexmpp_xml_new_elem("query", "jabber:iq:roster");
+ rexmpp_xml_add_child(add_query, add_item);
+ rexmpp_iq_new(s, "set", NULL, add_query,
+ rexmpp_console_roster_added, NULL);
+ }
+ }
+
+ if (! strcmp(word, "subscription")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ return;
+ }
+ if (! strcmp(word, "request")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ return;
+ }
+ presence = rexmpp_xml_new_elem("presence", "jabber:client");
+ rexmpp_xml_add_id(presence);
+ rexmpp_xml_add_attr(presence, "to", word);
+ rexmpp_xml_add_attr(presence, "type", "subscribe");
+ rexmpp_send(s, presence);
+ }
+ if (! strcmp(word, "approve")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ return;
+ }
+ presence = rexmpp_xml_new_elem("presence", "jabber:client");
+ rexmpp_xml_add_id(presence);
+ rexmpp_xml_add_attr(presence, "to", word);
+ rexmpp_xml_add_attr(presence, "type", "subscribed");
+ rexmpp_send(s, presence);
+ }
+ if (! strcmp(word, "deny")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ return;
+ }
+ presence = rexmpp_xml_new_elem("presence", "jabber:client");
+ rexmpp_xml_add_id(presence);
+ rexmpp_xml_add_attr(presence, "to", word);
+ rexmpp_xml_add_attr(presence, "type", "unsubscribed");
+ rexmpp_send(s, presence);
+ }
+ }
+
+ if (! strcmp(word, "http-upload")) {
+ char *fpath = strtok_r(NULL, " ", &words_save_ptr);
+ rexmpp_http_upload_path(s, NULL, fpath, NULL,
+ rexmpp_console_on_upload, strdup(fpath));
+ }
+
+ if (! strcmp(word, "jingle")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ return;
+ }
+ if (! strcmp(word, "terminate")) {
+ char *sid = strtok_r(NULL, " ", &words_save_ptr);
+ if (sid != NULL) {
+ rexmpp_jingle_session_terminate(s, sid,
+ rexmpp_xml_new_elem("success",
+ "urn:xmpp:jingle:1"),
+ NULL);
+ }
+ } else if (! strcmp(word, "decline")) {
+ char *sid = strtok_r(NULL, " ", &words_save_ptr);
+ if (sid != NULL) {
+ rexmpp_jingle_session_terminate(s, sid,
+ rexmpp_xml_new_elem("decline",
+ "urn:xmpp:jingle:1"),
+ NULL);
+ }
+ } else if (! strcmp(word, "accept-file")) {
+ char *sid = strtok_r(NULL, " ", &words_save_ptr);
+ char *fpath = strtok_r(NULL, " ", &words_save_ptr);
+ if (sid != NULL && fpath != NULL) {
+ rexmpp_jingle_accept_file_by_id(s, sid, fpath);
+ }
+ } else if (! strcmp(word, "send-file")) {
+ char *jid = strtok_r(NULL, " ", &words_save_ptr);
+ char *fpath = strtok_r(NULL, " ", &words_save_ptr);
+ if (jid != NULL && fpath != NULL) {
+ rexmpp_jingle_send_file(s, jid, fpath);
+ }
+ } else if (! strcmp(word, "accept-call")) {
+ char *sid = strtok_r(NULL, " ", &words_save_ptr);
+ if (sid != NULL) {
+ rexmpp_jingle_call_accept(s, sid);
+ }
+ } else if (! strcmp(word, "call")) {
+ char *jid = strtok_r(NULL, " ", &words_save_ptr);
+ if (jid != NULL) {
+ rexmpp_jingle_call(s, jid);
+ }
+ }
+ }
+
+ if (! strcmp(word, "disco")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ return;
+ }
+ if (! strcmp(word, "info")) {
+ char *jid = strtok_r(NULL, " ", &words_save_ptr);
+ if (jid == NULL) {
+ return;
+ }
+ rexmpp_xml_t *query =
+ rexmpp_xml_new_elem("query",
+ "http://jabber.org/protocol/disco#info");
+ rexmpp_iq_new(s, "get", jid, query, rexmpp_console_disco_info, NULL);
+ }
+ if (! strcmp(word, "items")) {
+ char *jid = strtok_r(NULL, " ", &words_save_ptr);
+ if (jid == NULL) {
+ return;
+ }
+ rexmpp_xml_t *query =
+ rexmpp_xml_new_elem("query",
+ "http://jabber.org/protocol/disco#items");
+ rexmpp_iq_new(s, "get", jid, query, rexmpp_console_disco_items, NULL);
+ }
+ }
+
+ if (! strcmp(word, "pubsub")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ return;
+ }
+ if (! strcmp(word, "node")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ return;
+ }
+ if (! strcmp(word, "delete")) {
+ char *service_jid = strtok_r(NULL, " ", &words_save_ptr);
+ char *node = strtok_r(NULL, " ", &words_save_ptr);
+ if (service_jid == NULL || node == NULL) {
+ return;
+ }
+ rexmpp_pubsub_node_delete(s, service_jid, node, rexmpp_console_pubsub_node_deleted, NULL);
+ }
+ }
+ }
+
+ if (! strcmp(word, "blocklist")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ rexmpp_xml_t *bl =
+ rexmpp_xml_new_elem("blocklist", "urn:xmpp:blocking");
+ rexmpp_iq_new(s, "get", NULL, bl, rexmpp_console_blocklist, NULL);
+ } else if (! strcmp(word, "block")) {
+ char *jid = strtok_r(NULL, " ", &words_save_ptr);
+ if (jid == NULL) {
+ return;
+ }
+ rexmpp_xml_t *bl =
+ rexmpp_xml_new_elem("block", "urn:xmpp:blocking");
+ rexmpp_xml_t *item =
+ rexmpp_xml_new_elem("item", "urn:xmpp:blocking");
+ rexmpp_xml_add_attr(item, "jid", jid);
+ rexmpp_xml_add_child(bl, item);
+ rexmpp_iq_new(s, "set", NULL, bl, rexmpp_console_blocklist_blocked, NULL);
+ } else if (! strcmp(word, "unblock")) {
+ char *jid = strtok_r(NULL, " ", &words_save_ptr);
+ if (jid == NULL) {
+ return;
+ }
+ rexmpp_xml_t *bl =
+ rexmpp_xml_new_elem("unblock", "urn:xmpp:blocking");
+ rexmpp_xml_t *item =
+ rexmpp_xml_new_elem("item", "urn:xmpp:blocking");
+ rexmpp_xml_add_attr(item, "jid", jid);
+ rexmpp_xml_add_child(bl, item);
+ rexmpp_iq_new(s, "set", NULL, bl, rexmpp_console_blocklist_unblocked, NULL);
+ }
+ }
+}
diff --git a/src/rexmpp_console.h b/src/rexmpp_console.h
new file mode 100644
index 0000000..bb2aed7
--- /dev/null
+++ b/src/rexmpp_console.h
@@ -0,0 +1,19 @@
+/**
+ @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, rexmpp_xml_t *node);
+void rexmpp_console_on_recv (rexmpp_t *s, rexmpp_xml_t *node);
+void rexmpp_console_on_run (rexmpp_t *s, rexmpp_err_t result);
+void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len);
+
+#endif
diff --git a/src/rexmpp_digest.c b/src/rexmpp_digest.c
new file mode 100644
index 0000000..c1dd436
--- /dev/null
+++ b/src/rexmpp_digest.c
@@ -0,0 +1,125 @@
+/**
+ @file rexmpp_digest.c
+ @brief Cryptographic functions
+ @author defanor <defanor@uberspace.net>
+ @date 2023
+ @copyright MIT license.
+*/
+
+#include "config.h"
+#include "rexmpp_digest.h"
+
+#if defined(HAVE_GCRYPT)
+#include <gcrypt.h>
+#elif defined(HAVE_NETTLE)
+#include <nettle/nettle-meta.h>
+#include <stdlib.h>
+#elif defined(HAVE_OPENSSL)
+#include <openssl/evp.h>
+#endif
+
+size_t rexmpp_digest_len (rexmpp_digest_algorithm algo) {
+ switch (algo) {
+ case REXMPP_DIGEST_SHA1: return 20;
+ case REXMPP_DIGEST_SHA256: return 32;
+ case REXMPP_DIGEST_SHA3_256: return 32;
+ default: return 0;
+ }
+}
+
+int rexmpp_digest_buffer (rexmpp_digest_algorithm algo,
+ const void *in,
+ size_t in_len,
+ void *out,
+ size_t out_len)
+{
+ rexmpp_digest_t ctx;
+ int err = rexmpp_digest_init(&ctx, algo);
+ if (err) {
+ return err;
+ }
+ err = rexmpp_digest_update(&ctx, in, in_len);
+ if (err) {
+ return err;
+ }
+ return rexmpp_digest_finish(&ctx, out, out_len);
+}
+
+int rexmpp_digest_init (rexmpp_digest_t *ctx, rexmpp_digest_algorithm algo) {
+#if defined(HAVE_GCRYPT)
+ int gcry_algo = GCRY_MD_NONE;
+ switch (algo) {
+ case REXMPP_DIGEST_SHA1: gcry_algo = GCRY_MD_SHA1; break;
+ case REXMPP_DIGEST_SHA256: gcry_algo = GCRY_MD_SHA256; break;
+ case REXMPP_DIGEST_SHA3_256: gcry_algo = GCRY_MD_SHA3_256; break;
+ default: return -1;
+ }
+ gcry_error_t err = gcry_md_open(ctx, gcry_algo, 0);
+ if (err != GPG_ERR_NO_ERROR) {
+ return -1;
+ }
+#elif defined(HAVE_NETTLE)
+ ctx->nh = NULL;
+ switch (algo) {
+ case REXMPP_DIGEST_SHA1: ctx->nh = &nettle_sha1; break;
+ case REXMPP_DIGEST_SHA256: ctx->nh = &nettle_sha256; break;
+ case REXMPP_DIGEST_SHA3_256: ctx->nh = &nettle_sha3_256; break;
+ default: return -1;
+ }
+ ctx->nh_ctx = malloc(ctx->nh->context_size);
+ ctx->nh->init(ctx->nh_ctx);
+#elif defined(HAVE_OPENSSL)
+ const EVP_MD *md = NULL;
+ switch (algo) {
+ case REXMPP_DIGEST_SHA1: md = EVP_sha1(); break;
+ case REXMPP_DIGEST_SHA256: md = EVP_sha256(); break;
+ case REXMPP_DIGEST_SHA3_256: md = EVP_sha3_256(); break;
+ default: return -1;
+ }
+ *ctx = EVP_MD_CTX_new();
+ if (! EVP_DigestInit(*ctx, md)) {
+ EVP_MD_CTX_free(*ctx);
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+int rexmpp_digest_update (rexmpp_digest_t *ctx, const void *in, size_t len) {
+#if defined(HAVE_GCRYPT)
+ gcry_md_write(*ctx, in, len);
+#elif defined(HAVE_NETTLE)
+ ctx->nh->update(ctx->nh_ctx, len, in);
+#elif defined(HAVE_OPENSSL)
+ if (! EVP_DigestUpdate(*ctx, in, len)) {
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+int rexmpp_digest_finish (rexmpp_digest_t *ctx, void *out, size_t len) {
+ int ret = 0;
+#if defined(HAVE_GCRYPT)
+ if (out != NULL) {
+ unsigned char *result = gcry_md_read(*ctx, 0);
+ if (result != NULL) {
+ memcpy(out, result, len);
+ } else {
+ ret = -1;
+ }
+ }
+ gcry_md_close(*ctx);
+#elif defined(HAVE_NETTLE)
+ ctx->nh->digest(ctx->nh_ctx, len, out);
+ free(ctx->nh_ctx);
+#elif defined(HAVE_OPENSSL)
+ (void)len;
+ if (! EVP_DigestFinal_ex(*ctx, out, NULL)) {
+ ret = -1;
+ }
+ EVP_MD_CTX_free(*ctx);
+ *ctx = NULL;
+#endif
+ return ret;
+}
diff --git a/src/rexmpp_digest.h b/src/rexmpp_digest.h
new file mode 100644
index 0000000..5001e12
--- /dev/null
+++ b/src/rexmpp_digest.h
@@ -0,0 +1,85 @@
+/**
+ @file rexmpp_digest.h
+ @brief Cryptographic functions
+ @author defanor <defanor@uberspace.net>
+ @date 2023
+ @copyright MIT license.
+*/
+
+#ifndef REXMPP_DIGEST_H
+#define REXMPP_DIGEST_H
+
+typedef enum {
+ REXMPP_DIGEST_SHA1,
+ REXMPP_DIGEST_SHA256,
+ REXMPP_DIGEST_SHA3_256
+} rexmpp_digest_algorithm;
+
+
+#if defined(HAVE_GCRYPT)
+#include <gcrypt.h>
+typedef gcry_md_hd_t rexmpp_digest_t;
+#elif defined(HAVE_NETTLE)
+#include <nettle/nettle-meta.h>
+struct rexmpp_digest {
+ const struct nettle_hash *nh;
+ void *nh_ctx;
+};
+typedef struct rexmpp_digest rexmpp_digest_t;
+#elif defined(HAVE_OPENSSL)
+#include <openssl/evp.h>
+typedef EVP_MD_CTX* rexmpp_digest_t;
+#endif
+
+/**
+ @brief Finds the digest length for a given algorithm.
+ @param[in] algo An algorithm.
+ @returns Digest length in bytes.
+*/
+size_t rexmpp_digest_len (rexmpp_digest_algorithm algo);
+
+/**
+ @brief Computes a digest for a buffer.
+ @param[in] algo An algorithm.
+ @param[in] in Input data.
+ @param[in] in_len Input data length.
+ @param[out] out Output buffer.
+ @param[in] out_len Output buffer length.
+ @returns 0 on success, non-zero on failure.
+*/
+int rexmpp_digest_buffer (rexmpp_digest_algorithm algo,
+ const void *in,
+ size_t in_len,
+ void *out,
+ size_t out_len);
+
+/**
+ @brief Initializes a digest context.
+ @param[out] ctx Pointer to an allocated ::rexmpp_digest_t context
+ to initialize.
+ @param[in] algo An algorithm to use.
+ @returns 0 on success, non-zero on failure.
+*/
+int rexmpp_digest_init (rexmpp_digest_t *ctx, rexmpp_digest_algorithm algo);
+
+/**
+ @brief Updates a digest computation.
+ @param[in,out] ctx Context pointer.
+ @param[in] in Input data.
+ @param[in] len Length of the input buffer.
+ @returns 0 on success, non-zero on failure.
+*/
+int rexmpp_digest_update (rexmpp_digest_t *ctx, const void *in, size_t len);
+
+/**
+ @brief Finishes a digest computation, freeing the context and
+ providing the output.
+ @param[in,out] ctx Context pointer.
+ @param[out] out A place to write the computed digest into, can be
+ NULL to just free the context.
+ @param[in] len Length of the allocated output buffer.
+ @returns 0 on success, non-zero on failure.
+*/
+int rexmpp_digest_finish (rexmpp_digest_t *ctx, void *out, size_t len);
+
+#endif
diff --git a/src/rexmpp_dns.c b/src/rexmpp_dns.c
new file mode 100644
index 0000000..d56aa10
--- /dev/null
+++ b/src/rexmpp_dns.c
@@ -0,0 +1,485 @@
+/**
+ @file rexmpp_dns.c
+ @brief DNS helper functions
+ @author defanor <defanor@uberspace.net>
+ @date 2020
+ @copyright MIT license.
+*/
+
+#include <memory.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include "config.h"
+
+#if defined(USE_UNBOUND)
+#include <unbound.h>
+#elif defined(USE_CARES)
+#include <ares.h>
+#else
+#endif
+#include <netdb.h>
+
+
+#include "rexmpp.h"
+#include "rexmpp_dns.h"
+
+
+struct rexmpp_dns_query_cb_data {
+ rexmpp_t *s;
+ dns_query_cb_t cb;
+ void *ptr;
+};
+
+
+
+/* https://tools.ietf.org/html/rfc1035#section-3.1 */
+
+int rexmpp_dns_parse_qname (char *in, int in_len, char *out, int out_len) {
+ int i = 0;
+ while (i < in_len && in[i]) {
+ if (i + in[i] < in_len && i + in[i] < out_len) {
+ memcpy(out + i, in + i + 1, in[i]);
+ i += in[i];
+ out[i] = '.';
+ i++;
+ out[i] = '\0';
+ } else {
+ return -1;
+ }
+ }
+ return i;
+}
+
+int rexmpp_parse_srv (char *in, int in_len, struct rexmpp_dns_srv *out) {
+ if (in_len < 7 || in_len > 255 + 6) {
+ return -1;
+ }
+ out->priority = in[0] * 0x100 + in[1];
+ out->weight = in[2] * 0x100 + in[3];
+ out->port = in[4] * 0x100 + in[5];
+ if (rexmpp_dns_parse_qname(in + 6, in_len - 6, out->target, 255) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+
+#ifndef USE_RUST
+void rexmpp_dns_result_free (rexmpp_dns_result_t *result) {
+ if (result->data != NULL) {
+ int i;
+ for (i = 0; result->data[i] != NULL; i++) {
+ free(result->data[i]);
+ }
+ free(result->data);
+ result->data = NULL;
+ }
+ if (result->len != NULL) {
+ free(result->len);
+ result->len = NULL;
+ }
+ free(result);
+}
+#endif
+
+rexmpp_dns_result_t *result_from_hostent (struct hostent *hostinfo) {
+ rexmpp_dns_result_t *r = malloc(sizeof(rexmpp_dns_result_t));
+ if (r == NULL) {
+ return NULL;
+ }
+ r->secure = 0;
+ int i, size = 0;
+ while (hostinfo->h_addr_list[size] != NULL) {
+ size++;
+ }
+ r->data = malloc(sizeof(void *) * (size + 1));
+ if (r->data == NULL) {
+ free(r);
+ return NULL;
+ }
+ r->len = malloc(sizeof(int) * size);
+ if (r->len == NULL) {
+ free(r->data);
+ free(r);
+ return NULL;
+ }
+ for (i = 0; i < size; i++) {
+ r->len[i] = hostinfo->h_length;
+ r->data[i] = malloc(r->len[i]);
+ if (r->data[i] != NULL) {
+ memcpy(r->data[i], hostinfo->h_addr_list[i], hostinfo->h_length);
+ } else {
+ return r;
+ }
+ }
+ r->data[size] = NULL;
+ return r;
+}
+
+
+int rexmpp_dns_ctx_init (rexmpp_t *s) {
+#if defined(USE_UNBOUND)
+ int err;
+ s->resolver = ub_ctx_create();
+ if (s->resolver == NULL) {
+ rexmpp_log(s, LOG_CRIT, "Failed to create resolver context");
+ return 1;
+ }
+ err = ub_ctx_resolvconf(s->resolver, NULL);
+ if (err != 0) {
+ rexmpp_log(s, LOG_WARNING, "Failed to read resolv.conf: %s",
+ ub_strerror(err));
+ }
+ err = ub_ctx_hosts(s->resolver, NULL);
+ if (err != 0) {
+ rexmpp_log(s, LOG_WARNING, "Failed to read hosts file: %s",
+ ub_strerror(err));
+ }
+ err = ub_ctx_add_ta_file(s->resolver, DNSSEC_TRUST_ANCHOR_FILE);
+ if (err != 0) {
+ rexmpp_log(s, LOG_WARNING, "Failed to set root key file for DNSSEC: %s",
+ ub_strerror(err));
+ }
+ return 0;
+#elif defined(USE_CARES)
+ int err = ares_library_init(ARES_LIB_INIT_ALL);
+ if (err != 0) {
+ rexmpp_log(s, LOG_CRIT, "ares library initialisation error: %s",
+ ares_strerror(err));
+ return 1;
+ }
+ err = ares_init(&(s->resolver));
+ if (err) {
+ rexmpp_log(s, LOG_CRIT, "ares channel initialisation error: %s",
+ ares_strerror(err));
+ ares_library_cleanup();
+ return 1;
+ }
+ return 0;
+#else
+ s->resolver = NULL;
+ return 0;
+#endif
+}
+
+void rexmpp_dns_ctx_cleanup (rexmpp_t *s) {
+ (void)s;
+ return;
+}
+
+void rexmpp_dns_ctx_deinit (rexmpp_t *s) {
+#if defined(USE_UNBOUND)
+ if (s->resolver != NULL) {
+ ub_ctx_delete(s->resolver);
+ s->resolver = NULL;
+ }
+#elif defined(USE_CARES)
+ if (s->resolver != NULL) {
+ ares_destroy(s->resolver);
+ s->resolver = NULL;
+ }
+ ares_library_cleanup();
+#else
+ (void)s;
+#endif
+}
+
+int rexmpp_dns_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
+#if defined(USE_UNBOUND)
+ (void)write_fds;
+ int max_fd = ub_fd(s->resolver) + 1;
+ if (max_fd != 0) {
+ FD_SET(max_fd - 1, read_fds);
+ }
+ return max_fd;
+#elif defined(USE_CARES)
+ return ares_fds(s->resolver, read_fds, write_fds);
+#else
+ (void)s;
+ (void)read_fds;
+ (void)write_fds;
+ return 0;
+#endif
+}
+
+struct timespec * rexmpp_dns_timeout (rexmpp_t *s,
+ struct timespec *max_tv,
+ struct timespec *tv)
+{
+#if defined(USE_UNBOUND)
+ (void)s;
+ (void)tv;
+ return max_tv;
+#elif defined(USE_CARES)
+ struct timeval tv_ms;
+ struct timeval *max_tv_ms = NULL;
+ if (max_tv != NULL) {
+ max_tv_ms = &tv_ms;
+ tv_ms.tv_sec = tv->tv_sec;
+ tv_ms.tv_usec = tv->tv_nsec / 1000;
+ }
+ struct timeval *ret_ms = ares_timeout(s->resolver, max_tv_ms, &tv_ms);
+ if (ret_ms == max_tv_ms) {
+ return max_tv;
+ } else {
+ tv->tv_sec = tv_ms.tv_sec;
+ tv->tv_nsec = tv_ms.tv_usec * 1000;
+ return tv;
+ }
+#else
+ (void)s;
+ (void)max_tv;
+ (void)tv;
+ return max_tv;
+#endif
+}
+
+#if defined(USE_UNBOUND)
+void rexmpp_dns_cb (void *ptr,
+ int err,
+ struct ub_result *result)
+{
+ struct rexmpp_dns_query_cb_data *d = ptr;
+ rexmpp_t *s = d->s;
+
+ if (err != 0) {
+ rexmpp_log(s, LOG_WARNING, "DNS query failure: %s",
+ ub_strerror(err));
+ ub_resolve_free(result);
+ d->cb(s, d->ptr, NULL);
+ free(d);
+ return;
+ }
+
+ if (result->bogus) {
+ rexmpp_log(s, LOG_WARNING,
+ "Received a bogus DNS resolution result");
+ ub_resolve_free(result);
+ d->cb(s, d->ptr, NULL);
+ return;
+ }
+
+ if (! result->havedata) {
+ rexmpp_log(s, LOG_DEBUG, "No data in the query result");
+ ub_resolve_free(result);
+ d->cb(s, d->ptr, NULL);
+ free(d);
+ return;
+ }
+
+ int i, size = 0;
+ while (result->data[size] != NULL) {
+ size++;
+ }
+ rexmpp_dns_result_t *res = malloc(sizeof(rexmpp_dns_result_t));
+ if (res == NULL) {
+ rexmpp_log(s, LOG_DEBUG,
+ "Failed to allocate memory for a DNS resolution result");
+ ub_resolve_free(result);
+ d->cb(s, d->ptr, NULL);
+ free(d);
+ return;
+ }
+ res->data = calloc((size + 1), sizeof(void *));
+ if (res->data == NULL) {
+ rexmpp_log(s, LOG_DEBUG,
+ "Failed to allocate memory for a DNS resolution result's data");
+ free(res);
+ ub_resolve_free(result);
+ d->cb(s, d->ptr, NULL);
+ free(d);
+ return;
+ }
+ res->len = malloc(sizeof(int) * size);
+ if (res->len == NULL) {
+ rexmpp_log(s, LOG_DEBUG,
+ "Failed to allocate memory for a DNS resolution result's len");
+ free(res->data);
+ free(res);
+ ub_resolve_free(result);
+ d->cb(s, d->ptr, NULL);
+ free(d);
+ return;
+ }
+ for (i = 0; i < size; i++) {
+ if (result->qtype == 33) {
+ /* SRV */
+ res->len[i] = sizeof(rexmpp_dns_srv_t);
+ res->data[i] = malloc(res->len[i]);
+ if (res->data[i] == NULL) {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to allocate memory for an SRV record");
+ d->cb(s, d->ptr, res);
+ rexmpp_dns_result_free(res);
+ free(d);
+ return;
+ }
+ int err = rexmpp_parse_srv(result->data[i], result->len[i],
+ (rexmpp_dns_srv_t*)res->data[i]);
+ if (err) {
+ rexmpp_log(s, LOG_WARNING, "Failed to parse an SRV record");
+ d->cb(s, d->ptr, res);
+ rexmpp_dns_result_free(res);
+ free(d);
+ return;
+ }
+ } else {
+ /* Non-SRV, for now that's just A or AAAA */
+ res->len[i] = result->len[i];
+ res->data[i] = malloc(res->len[i]);
+ if (res->data[i] == NULL) {
+ rexmpp_log(s, LOG_WARNING,
+ "Failed to allocate memory for a DNS result's data record");
+ } else {
+ memcpy(res->data[i], result->data[i], res->len[i]);
+ }
+ }
+ }
+ res->data[size] = NULL;
+ res->secure = result->secure;
+ ub_resolve_free(result);
+ d->cb(s, d->ptr, res);
+ free(d);
+}
+#elif defined(USE_CARES)
+void rexmpp_dns_cb (void *ptr,
+ int err,
+ int timeouts,
+ unsigned char *abuf,
+ int alen)
+{
+ (void)timeouts;
+ struct rexmpp_dns_query_cb_data *d = ptr;
+ rexmpp_t *s = d->s;
+ if (err != ARES_SUCCESS) {
+ rexmpp_log(s, LOG_WARNING, "A DNS query failure: %s",
+ ares_strerror(err));
+ d->cb(s, d->ptr, NULL);
+ free(d);
+ return;
+ }
+ /* c-ares won't just tell us the type, but it does check for it in
+ the parsing functions, so we just try them out. */
+ struct hostent *hostinfo;
+ struct ares_srv_reply *srv, *cur_srv;
+ if (ares_parse_a_reply(abuf, alen, &hostinfo, NULL, NULL) == ARES_SUCCESS ||
+ ares_parse_aaaa_reply(abuf, alen, &hostinfo, NULL, NULL) == ARES_SUCCESS) {
+ rexmpp_dns_result_t *r = result_from_hostent(hostinfo);
+ ares_free_hostent(hostinfo);
+ d->cb(s, d->ptr, r);
+ } else if (ares_parse_srv_reply(abuf, alen, &srv) == ARES_SUCCESS) {
+ int i, size;
+ for (size = 0, cur_srv = srv; cur_srv != NULL; size++, cur_srv = cur_srv->next);
+ rexmpp_dns_result_t *r = malloc(sizeof(rexmpp_dns_result_t));
+ r->secure = 0;
+ r->data = malloc(sizeof(void*) * (size + 1));
+ r->len = malloc(sizeof(int) * size);
+ for (cur_srv = srv, i = 0; i < size; i++, cur_srv = cur_srv->next) {
+ r->len[i] = sizeof(rexmpp_dns_srv_t);
+ rexmpp_dns_srv_t *r_srv = malloc(sizeof(rexmpp_dns_srv_t));
+ r_srv->priority = cur_srv->priority;
+ r_srv->weight = cur_srv->weight;
+ r_srv->port = cur_srv->port;
+ strncpy(r_srv->target, cur_srv->host, 255);
+ r_srv->target[255] = '\0';
+ r->data[i] = r_srv;
+ }
+ r->data[size] = NULL;
+ ares_free_data(srv);
+ d->cb(s, d->ptr, r);
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to parse a query");
+ d->cb(s, d->ptr, NULL);
+ }
+ free(d);
+}
+#endif
+
+
+int rexmpp_dns_resolve (rexmpp_t *s,
+ const char *query,
+ int rrtype,
+ int rrclass,
+ void* ptr,
+ dns_query_cb_t callback)
+{
+#if defined(USE_UNBOUND)
+ struct rexmpp_dns_query_cb_data *d =
+ malloc(sizeof(struct rexmpp_dns_query_cb_data));
+ if (d == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to allocate memory for a DNS query");
+ return 1;
+ }
+ d->s = s;
+ d->cb = callback;
+ d->ptr = ptr;
+ int err = ub_resolve_async(s->resolver, query, rrtype, rrclass,
+ d, rexmpp_dns_cb, NULL);
+ if (err) {
+ rexmpp_log(s, LOG_ERR, "Failed to query %s: %s",
+ query, ub_strerror(err));
+ return 1;
+ }
+#elif defined(USE_CARES)
+ struct rexmpp_dns_query_cb_data *d =
+ malloc(sizeof(struct rexmpp_dns_query_cb_data));
+ if (d == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to allocate memory for a DNS query");
+ return 1;
+ }
+ d->s = s;
+ d->cb = callback;
+ d->ptr = ptr;
+ ares_query(s->resolver, query, rrclass, rrtype, rexmpp_dns_cb, d);
+#else
+ if (rrclass == 1) {
+ if (rrtype == 1 || rrtype == 28) {
+ struct hostent *hostinfo = gethostbyname(query);
+ if (hostinfo == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to lookup %s", query);
+ callback(s, ptr, NULL);
+ } else {
+ rexmpp_dns_result_t *r = result_from_hostent(hostinfo);
+ callback(s, ptr, r);
+ }
+ } else if (rrtype == 33) {
+ rexmpp_log(s, LOG_WARNING, "rexmpp is built without SRV lookup support");
+ callback(s, ptr, NULL);
+ } else {
+ rexmpp_log(s, LOG_ERR, "A DNS lookup of unrecognized type is requested");
+ callback(s, ptr, NULL);
+ return -1;
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR, "A DNS lookup of unrecognized class is requested");
+ callback(s, ptr, NULL);
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+int rexmpp_dns_process (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
+#if defined(USE_UNBOUND)
+ (void)read_fds;
+ (void)write_fds;
+ if (ub_poll(s->resolver)) {
+ int err = ub_process(s->resolver);
+ if (err != 0) {
+ rexmpp_log(s, LOG_ERR, "DNS query processing error: %s",
+ ub_strerror(err));
+ return 1;
+ }
+ }
+ return 0;
+#elif defined(USE_CARES)
+ ares_process(s->resolver, read_fds, write_fds);
+ return 0;
+#else
+ (void)s;
+ (void)read_fds;
+ (void)write_fds;
+ return 0;
+#endif
+}
diff --git a/src/rexmpp_dns.h b/src/rexmpp_dns.h
new file mode 100644
index 0000000..7abd2ef
--- /dev/null
+++ b/src/rexmpp_dns.h
@@ -0,0 +1,130 @@
+/**
+ @file rexmpp_dns.h
+ @brief DNS resolution
+ @author defanor <defanor@uberspace.net>
+ @date 2020
+ @copyright MIT license.
+
+*/
+
+
+#ifndef REXMPP_DNS_H
+#define REXMPP_DNS_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "config.h"
+
+#include "rexmpp.h"
+
+/**
+ @brief DNS context.
+*/
+#if defined(USE_UNBOUND)
+#include <unbound.h>
+typedef struct ub_ctx* rexmpp_dns_ctx_t;
+/* struct rexmpp_dns_ctx { */
+/* struct ub_ctx *ctx; */
+/* }; */
+#elif defined(USE_CARES)
+#include <ares.h>
+typedef ares_channel rexmpp_dns_ctx_t;
+/* struct rexmpp_dns_ctx { */
+/* ares_channel channel; */
+/* }; */
+#else
+typedef void* rexmpp_dns_ctx_t;
+#endif
+
+/* typedef struct rexmpp_dns_ctx rexmpp_dns_ctx_t; */
+
+struct rexmpp_dns_srv {
+ uint16_t priority;
+ uint16_t weight;
+ uint16_t port;
+ char target[256];
+};
+
+typedef struct rexmpp_dns_srv rexmpp_dns_srv_t;
+
+/**
+ @brief DNS query result.
+*/
+struct rexmpp_dns_result {
+ /** @brief NULL-terminated array of data pointers. They contain
+ ::rexmpp_dns_srv for SRV lookups, host addresses for A and AAAA
+ ones. */
+ void **data;
+ /** @brief An array of data structure lengths. */
+ int *len;
+ /** @brief Whether the result was retrieved securely (that is,
+ verified with DNSSEC). */
+ bool secure;
+};
+
+typedef struct rexmpp_dns_result rexmpp_dns_result_t;
+
+/**
+ @brief Parses an SRV DNS RR's RDATA.
+ @param[in] in SRV record's RDATA.
+ @param[in] in_len Length of the input data in octets.
+ @param[out] out A structure to fill with data.
+ @returns 0 on success, non-zero on parsing failure.
+*/
+int
+rexmpp_parse_srv (char *in, int in_len, struct rexmpp_dns_srv *out);
+
+/**
+ @brief Frees a ::rexmpp_dns_result structure and its members.
+ @param[in] result A pointer to a ::rexmpp_dns_result structure.
+*/
+void rexmpp_dns_result_free (rexmpp_dns_result_t *result);
+
+/**
+ @brief Initializes a DNS resolver context.
+*/
+int rexmpp_dns_ctx_init (rexmpp_t *s);
+
+/**
+ @brief Cleans up the state that can be discarded between XMPP
+ connections, to be called from rexmpp_cleanup.
+*/
+void rexmpp_dns_ctx_cleanup (rexmpp_t *s);
+
+/**
+ @brief Deinitializes a DNS resolver context.
+*/
+void rexmpp_dns_ctx_deinit (rexmpp_t *s);
+
+/**
+ @brief Sets file descriptors to select/poll.
+*/
+int rexmpp_dns_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds);
+
+/**
+ @brief Reports timeouts.
+*/
+struct timespec * rexmpp_dns_timeout (rexmpp_t *s,
+ struct timespec *max_tv,
+ struct timespec *tv);
+
+typedef void (*dns_query_cb_t) (rexmpp_t *s, void *ptr, rexmpp_dns_result_t *result);
+
+/**
+ @brief Initiates a query.
+*/
+int rexmpp_dns_resolve (rexmpp_t *s,
+ const char *query,
+ int rrtype,
+ int rrclass,
+ void* ptr,
+ dns_query_cb_t callback);
+
+/**
+ @brief Processes active queries, should be called based on the
+ reported timeouts and file descriptors.
+*/
+int rexmpp_dns_process (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds);
+
+
+#endif
diff --git a/src/rexmpp_dns.rs b/src/rexmpp_dns.rs
new file mode 100644
index 0000000..1489835
--- /dev/null
+++ b/src/rexmpp_dns.rs
@@ -0,0 +1,62 @@
+use std::os::raw::{c_int, c_void, c_char};
+use libc::*;
+use std::ptr;
+
+use super::rexmpp;
+
+type DNSQueryCB = unsafe extern "C"
+fn (s: *mut rexmpp::Rexmpp, ptr: *mut c_void, result: *mut RexmppDNSResult) -> ();
+
+extern {
+ pub fn rexmpp_dns_resolve (s: *mut rexmpp::Rexmpp,
+ query: *const c_char,
+ rrtype: c_int,
+ rrclass: c_int,
+ ptr: *mut c_void,
+ callback: DNSQueryCB) -> c_int;
+ pub fn rexmpp_dns_process (s: *mut rexmpp::Rexmpp,
+ read_fds: *mut fd_set,
+ write_fds: *mut fd_set) -> c_int;
+ pub fn rexmpp_dns_fds (s: *mut rexmpp::Rexmpp,
+ read_fds: *mut fd_set,
+ write_fds: *mut fd_set) -> c_int;
+ pub fn rexmpp_dns_timeout (s: *mut rexmpp::Rexmpp,
+ max_tv: *mut timespec,
+ tv: *mut timespec) -> *mut timespec;
+}
+
+#[repr(C)]
+pub struct RexmppDNSResult {
+ pub data: *mut *mut c_void,
+ pub len: *mut c_int,
+ pub secure: bool
+}
+
+#[repr(C)]
+pub struct RexmppDNSSRV {
+ pub priority: u16,
+ pub weight: u16,
+ pub port: u16,
+ pub target: [c_char; 256]
+}
+
+
+#[no_mangle]
+pub unsafe extern "C"
+fn rexmpp_dns_result_free (result: *mut RexmppDNSResult) {
+ if (*result).data != ptr::null_mut() {
+ let mut i = 0;
+ let data_ptr: *mut *mut c_void = (*result).data;
+ while *(data_ptr.offset(i)) != ptr::null_mut() {
+ free(*(data_ptr.offset(i)));
+ i += 1;
+ }
+ free((*result).data as *mut c_void);
+ (*result).data = ptr::null_mut();
+ }
+ if (*result).len != ptr::null_mut() {
+ free((*result).len as *mut c_void);
+ (*result).len = ptr::null_mut();
+ }
+ free(result as *mut c_void);
+}
diff --git a/src/rexmpp_http_upload.c b/src/rexmpp_http_upload.c
new file mode 100644
index 0000000..18a3302
--- /dev/null
+++ b/src/rexmpp_http_upload.c
@@ -0,0 +1,216 @@
+/**
+ @file rexmpp_http_upload.c
+ @brief XEP-0363: HTTP file upload
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+*/
+
+#include <syslog.h>
+#include <string.h>
+#include <libgen.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "config.h"
+
+#ifdef HAVE_CURL
+#include <curl/curl.h>
+#endif
+
+#include "rexmpp.h"
+#include "rexmpp_xml.h"
+#include "rexmpp_http_upload.h"
+
+
+
+#ifdef HAVE_CURL
+void rexmpp_upload_task_finish (struct rexmpp_http_upload_task *task) {
+ task->cb(task->s, task->cb_data, task->get_url);
+ free(task->fname);
+ if (task->fh != NULL) {
+ fclose(task->fh);
+ }
+ if (task->content_type != NULL) {
+ free(task->content_type);
+ }
+ if (task->get_url != NULL) {
+ free(task->get_url);
+ }
+ if (task->http_headers != NULL) {
+ curl_slist_free_all(task->http_headers);
+ }
+ free(task);
+}
+
+void rexmpp_http_upload_slot_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ struct rexmpp_http_upload_task *task = ptr;
+ if (success) {
+ rexmpp_xml_t *slot = rexmpp_xml_find_child(response, "urn:xmpp:http:upload:0", "slot");
+ rexmpp_xml_t *put = rexmpp_xml_find_child(slot, "urn:xmpp:http:upload:0", "put");
+ rexmpp_xml_t *get = rexmpp_xml_find_child(slot, "urn:xmpp:http:upload:0", "get");
+ if (put != NULL && get != NULL) {
+ const char *put_url = rexmpp_xml_find_attr_val(put, "url");
+ const char *get_url = rexmpp_xml_find_attr_val(get, "url");
+ if (put_url != NULL && get_url != NULL) {
+ task->get_url = strdup(get_url);
+
+ CURL *ce = curl_easy_init();
+ curl_easy_setopt(ce, CURLOPT_PRIVATE, task);
+ curl_easy_setopt(ce, CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(ce, CURLOPT_READDATA, task->fh);
+ curl_easy_setopt(ce, CURLOPT_URL, put_url);
+ curl_easy_setopt(ce, CURLOPT_INFILESIZE, task->fsize);
+
+ rexmpp_xml_t *header = rexmpp_xml_first_elem_child(put);
+ while (header) {
+ const char *header_name = rexmpp_xml_find_attr_val(header, "name");
+ if (header_name != NULL) {
+ const char *header_str = rexmpp_xml_text_child(header);
+ if (header_str != NULL) {
+ size_t full_header_str_len =
+ strlen(header_name) + 3 + strlen(header_str);
+ char *full_header_str = malloc(full_header_str_len);
+ if (full_header_str != NULL) {
+ snprintf(full_header_str, full_header_str_len, "%s: %s",
+ header_name, header_str);
+ task->http_headers =
+ curl_slist_append(task->http_headers, full_header_str);
+ free(full_header_str);
+ } else {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to allocate memory for a header");
+ }
+ }
+ }
+ header = rexmpp_xml_next_elem_sibling(header);
+ }
+ curl_easy_setopt(ce, CURLOPT_HTTPHEADER, task->http_headers);
+
+ curl_multi_add_handle(s->curl_multi, ce);
+ rexmpp_log(s, LOG_DEBUG, "Uploading %s to %s", task->fname, put_url);
+ return;
+ } else {
+ rexmpp_log(s, LOG_ERR, "Unexpected structure for a HTTP file upload slot.");
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR, "Unexpected structure for a HTTP file upload slot.");
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to obtain a slot for HTTP file upload.");
+ }
+ rexmpp_upload_task_finish(task);
+}
+
+void rexmpp_http_upload_feature_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)response;
+ struct rexmpp_http_upload_task *task = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "No HTTP file upload service found.");
+ rexmpp_upload_task_finish(task);
+ return;
+ }
+ rexmpp_xml_t *req =
+ rexmpp_xml_new_elem("request", "urn:xmpp:http:upload:0");
+ rexmpp_xml_add_attr(req, "filename", task->fname);
+ char buf[11];
+ snprintf(buf, 11, "%u", task->fsize);
+ rexmpp_xml_add_attr(req, "size", buf);
+ if (task->content_type) {
+ rexmpp_xml_add_attr(req, "content-type", task->content_type);
+ }
+ const char *to = rexmpp_xml_find_attr_val(request, "to");
+ rexmpp_iq_new(s, "get", to, req, rexmpp_http_upload_slot_cb, task);
+}
+
+rexmpp_err_t
+rexmpp_http_upload (rexmpp_t *s,
+ const char *jid,
+ const char *fname,
+ size_t fsize,
+ FILE *fh,
+ const char *content_type,
+ http_upload_cb cb,
+ void *cb_data)
+{
+ if (fname == NULL) {
+ rexmpp_log(s, LOG_ERR, "No file name is provided");
+ fclose(fh);
+ return REXMPP_E_PARAM;
+ }
+ struct rexmpp_http_upload_task *task =
+ malloc(sizeof(struct rexmpp_http_upload_task));
+ if (task == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to allocate memory for an upload task");
+ fclose(fh);
+ return REXMPP_E_MALLOC;
+ }
+ task->fname = strdup(fname);
+ task->fsize = fsize;
+ task->fh = fh;
+ task->content_type = content_type ? strdup(content_type) : NULL;
+ task->get_url = NULL;
+ task->http_headers = NULL;
+ task->cb = cb;
+ task->cb_data = cb_data;
+ task->s = s;
+ return rexmpp_disco_find_feature(s, jid, "urn:xmpp:http:upload:0",
+ rexmpp_http_upload_feature_cb, task, 0, 20);
+}
+#else
+rexmpp_err_t
+rexmpp_http_upload (rexmpp_t *s,
+ const char *jid,
+ const char *fname,
+ size_t fsize,
+ FILE *fh,
+ const char *content_type,
+ http_upload_cb cb,
+ void *cb_data)
+{
+ (void)jid;
+ (void)fname;
+ (void)fsize;
+ (void)content_type;
+ rexmpp_log(s, LOG_ERR, "rexmpp is built without curl support");
+ fclose(fh);
+ cb(s, cb_data, NULL);
+ return REXMPP_E_OTHER;
+}
+#endif
+
+rexmpp_err_t
+rexmpp_http_upload_path (rexmpp_t *s,
+ const char *jid,
+ char *fpath,
+ const char *content_type,
+ http_upload_cb cb,
+ void *cb_data)
+{
+ FILE *fh = fopen(fpath, "rb");
+ if (fh == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to open %s for reading: %s.\n",
+ fpath, strerror(errno));
+ cb(s, cb_data, NULL);
+ return REXMPP_E_OTHER;
+ }
+
+ char *fname = basename(fpath);
+ fseek(fh, 0, SEEK_END);
+ long fsize = ftell(fh);
+ fseek(fh, 0, SEEK_SET);
+
+ return
+ rexmpp_http_upload(s, jid, fname, fsize, fh, content_type, cb, cb_data);
+}
diff --git a/src/rexmpp_http_upload.h b/src/rexmpp_http_upload.h
new file mode 100644
index 0000000..898579b
--- /dev/null
+++ b/src/rexmpp_http_upload.h
@@ -0,0 +1,52 @@
+/**
+ @file rexmpp_http_upload.h
+ @brief XEP-0363: HTTP file upload
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+*/
+
+#ifndef REXMPP_HTTP_UPLOAD_H
+#define REXMPP_HTTP_UPLOAD_H
+
+#include "config.h"
+#include "rexmpp.h"
+
+
+typedef void (*http_upload_cb) (rexmpp_t *s, void *cb_data, const char *url);
+
+#ifdef HAVE_CURL
+struct rexmpp_http_upload_task {
+ char *fname;
+ uint32_t fsize;
+ FILE *fh;
+ char *content_type;
+ char *get_url;
+ http_upload_cb cb;
+ void *cb_data;
+ struct curl_slist *http_headers;
+ rexmpp_t *s;
+};
+
+void rexmpp_upload_task_finish (struct rexmpp_http_upload_task *task);
+#endif
+
+rexmpp_err_t
+rexmpp_http_upload (rexmpp_t *s,
+ const char *jid,
+ const char *fname,
+ size_t fsize,
+ FILE *fh,
+ const char *content_type,
+ http_upload_cb cb,
+ void *cb_data);
+
+rexmpp_err_t
+rexmpp_http_upload_path (rexmpp_t *s,
+ const char *jid,
+ char *fpath,
+ const char *content_type,
+ http_upload_cb cb,
+ void *cb_data);
+
+#endif
diff --git a/src/rexmpp_jid.c b/src/rexmpp_jid.c
new file mode 100644
index 0000000..f1c371c
--- /dev/null
+++ b/src/rexmpp_jid.c
@@ -0,0 +1,185 @@
+/**
+ @file rexmpp_jid.c
+ @brief JID parsing and manipulation
+ @author defanor <defanor@uberspace.net>
+ @date 2020
+ @copyright MIT license.
+*/
+
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#ifdef HAVE_ICU
+#include <unicode/ustring.h>
+#include <unicode/uset.h>
+#include <unicode/uspoof.h>
+#endif
+#include "rexmpp_jid.h"
+
+int rexmpp_jid_parse (const char *str, struct rexmpp_jid *jid) {
+ const char *resource = NULL, *domain = str;
+ size_t i;
+ size_t resource_len = 0, local_len = 0;
+ size_t domain_len, bare_len, full_len = strlen(str);
+ domain_len = full_len;
+ bare_len = full_len;
+
+ /* Find the separators. */
+ for (i = 0; i < full_len; i++) {
+ if (local_len == 0 && str[i] == '@') {
+ if (i == 0) {
+ /* '@' is in the very beginning, an error. */
+ return -1;
+ }
+ local_len = i;
+ domain_len -= local_len + 1;
+ domain = str + i + 1;
+ }
+ if (str[i] == '/') {
+ if (i == full_len - 1) {
+ /* '/' is in the end, that's an error. */
+ return -1;
+ }
+ resource_len = full_len - i - 1;
+ domain_len -= resource_len + 1;
+ bare_len -= resource_len + 1;
+ resource = str + i + 1;
+ break;
+ }
+ }
+
+ /* Check all the lengths. */
+ if (full_len > 3071 || bare_len > 2047 ||
+ local_len > 1023 || resource_len > 1023 ||
+ domain_len > 1023 || domain_len < 1) {
+ return -1;
+ }
+
+ /* Copy all the parts. */
+ strncpy(jid->full, str, 3072);
+ jid->full[full_len] = '\0';
+ strncpy(jid->bare, str, bare_len);
+ jid->bare[bare_len] = '\0';
+ strncpy(jid->local, str, local_len);
+ jid->local[local_len] = '\0';
+ strncpy(jid->domain, domain, domain_len);
+ jid->domain[domain_len] = '\0';
+ if (resource != NULL) {
+ strncpy(jid->resource, resource, resource_len);
+ jid->resource[resource_len] = '\0';
+ }
+
+ return 0;
+}
+
+/* <https://tools.ietf.org/html/rfc7622#section-3>,
+ <https://tools.ietf.org/html/rfc8265#section-3.3> */
+int rexmpp_jid_check (struct rexmpp_jid *jid) {
+#ifdef HAVE_ICU
+ UErrorCode err = U_ZERO_ERROR;
+ UChar local[1023], domain[1023], resource[1023];
+ int32_t local_len = 0, domain_len = 0, resource_len = 0;
+
+ /* Initial length checks are performed on parsing. */
+
+ /* Ensure that it's all valid UTF-8. */
+
+ u_strFromUTF8(local, 1023, &local_len, jid->local, -1, &err);
+ if (U_FAILURE(err)) {
+ return 0;
+ }
+ /* TODO: IDNA2008 on domain part. */
+ u_strFromUTF8(domain, 1023, &domain_len, jid->domain, -1, &err);
+ if (U_FAILURE(err)) {
+ return 0;
+ }
+ u_strFromUTF8(resource, 1023, &resource_len, jid->resource, -1, &err);
+ if (U_FAILURE(err)) {
+ return 0;
+ }
+
+ /* TODO: width mapping. */
+
+
+ /* Check character classes */
+ USpoofChecker *sc;
+ int32_t spoof;
+
+ /* IdentifierClass: {Ll, Lu, Lo, Nd, Lm, Mn, Mc} + 0x21 to 0x7e */
+ USet *identifier_chars = uset_openEmpty();
+ uset_applyIntPropertyValue(identifier_chars, UCHAR_GENERAL_CATEGORY_MASK,
+ U_GC_LL_MASK | U_GC_LU_MASK | U_GC_LO_MASK |
+ U_GC_ND_MASK | U_GC_LM_MASK | U_GC_MN_MASK |
+ U_GC_MC_MASK,
+ &err);
+ if (U_FAILURE(err)) {
+ return 0;
+ }
+ uset_addRange(identifier_chars, 0x21, 0x7e);
+
+ sc = uspoof_open(&err);
+ if (U_FAILURE(err)) {
+ uset_close(identifier_chars);
+ return 0;
+ }
+ uspoof_setChecks(sc, USPOOF_CHAR_LIMIT, &err);
+ if (U_FAILURE(err)) {
+ uset_close(identifier_chars);
+ uspoof_close(sc);
+ return 0;
+ }
+ uspoof_setAllowedChars(sc, identifier_chars, &err);
+ if (U_FAILURE(err)) {
+ uset_close(identifier_chars);
+ uspoof_close(sc);
+ return 0;
+ }
+ spoof = uspoof_check(sc, local, local_len, NULL, &err);
+ if (U_FAILURE(err) || spoof) {
+ uset_close(identifier_chars);
+ uspoof_close(sc);
+ return 0;
+ }
+
+ /* FreeformClass: Zs, {Sm, Sc, Sk, So}, {Pc, Pd, Ps, Pe, Pi, Pf,
+ Po}, toNFKC(cp) != cp ones, {Lt, Nl, No, Me}, and IdentifierClass
+ ones. */
+
+ /* TODO: https://tools.ietf.org/html/rfc8264#section-9.17 */
+ USet *freeform_chars = uset_openEmpty();
+ uset_applyIntPropertyValue(freeform_chars, UCHAR_GENERAL_CATEGORY_MASK,
+ U_GC_ZS_MASK | U_GC_SM_MASK | U_GC_SC_MASK |
+ U_GC_SK_MASK | U_GC_SO_MASK | U_GC_PC_MASK |
+ U_GC_PD_MASK | U_GC_PS_MASK | U_GC_PE_MASK |
+ U_GC_PI_MASK | U_GC_PF_MASK | U_GC_PO_MASK |
+ U_GC_LT_MASK | U_GC_NL_MASK | U_GC_NO_MASK |
+ U_GC_ME_MASK,
+ &err);
+ if (U_FAILURE(err)) {
+ uset_close(freeform_chars);
+ uset_close(identifier_chars);
+ uspoof_close(sc);
+ return 0;
+ }
+ uset_addAll(freeform_chars, identifier_chars);
+ uset_close(identifier_chars);
+
+ spoof = uspoof_check(sc, resource, resource_len, NULL, &err);
+ if (U_FAILURE(err) || spoof) {
+ uset_close(freeform_chars);
+ uspoof_close(sc);
+ return 0;
+ }
+ uset_close(freeform_chars);
+ uspoof_close(sc);
+
+ /* TODO: case mapping, u_strToLower */
+
+ /* TODO: normalization, unorm2_normalize */
+
+ /* TODO: directionality */
+#else
+ (void)jid;
+#endif
+ return 1;
+}
diff --git a/src/rexmpp_jid.h b/src/rexmpp_jid.h
new file mode 100644
index 0000000..bfeedcb
--- /dev/null
+++ b/src/rexmpp_jid.h
@@ -0,0 +1,25 @@
+/**
+ @file rexmpp_jid.h
+ @brief JID parsing and manipulation
+ @author defanor <defanor@uberspace.net>
+ @date 2020
+ @copyright MIT license.
+*/
+
+#ifndef REXMPP_JID_H
+#define REXMPP_JID_H
+
+/** @brief A redundant structure for easy access to JID parts and
+ avoidance of dynamic allocations. */
+struct rexmpp_jid {
+ char local[1024];
+ char domain[1024];
+ char resource[1024];
+ char bare[2048];
+ char full[3072];
+};
+
+int rexmpp_jid_parse (const char *str, struct rexmpp_jid *jid);
+int rexmpp_jid_check (struct rexmpp_jid *jid);
+
+#endif
diff --git a/src/rexmpp_jid.rs b/src/rexmpp_jid.rs
new file mode 100644
index 0000000..7730b2f
--- /dev/null
+++ b/src/rexmpp_jid.rs
@@ -0,0 +1,17 @@
+use std::os::raw::{c_char};
+
+#[repr(C)]
+pub struct RexmppJID {
+ local: [c_char; 1024],
+ domain: [c_char; 1024],
+ resource: [c_char; 1024],
+ bare: [c_char; 2048],
+ full: [c_char; 3072]
+}
+
+// #[no_mangle]
+// extern "C"
+// fn rexmpp_jid_parse (str: *const c_char, jid : &mut RexmppJID) -> c_int
+// {
+
+// }
diff --git a/src/rexmpp_jingle.c b/src/rexmpp_jingle.c
new file mode 100644
index 0000000..2b84445
--- /dev/null
+++ b/src/rexmpp_jingle.c
@@ -0,0 +1,2472 @@
+/**
+ @file rexmpp_jingle.c
+ @brief Jingle routines
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+
+The following XEPs are handled here so far:
+
+- XEP-0166: Jingle
+
+File transfer over IBB:
+
+- XEP-0234: Jingle File Transfer
+- XEP-0261: Jingle In-Band Bytestreams Transport Method
+
+A/V calls over ICE-UDP + DTLS-SRTP:
+
+- XEP-0167: Jingle RTP Sessions
+- XEP-0176: Jingle ICE-UDP Transport Method
+- XEP-0320: Use of DTLS-SRTP in Jingle Sessions
+- XEP-0215: External Service Discovery
+
+*/
+
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <libgen.h>
+
+#include "config.h"
+
+#ifdef ENABLE_CALLS
+#include <inttypes.h>
+#include <glib.h>
+#include <gio/gnetworking.h>
+#include <nice.h>
+#include <agent.h>
+#include <srtp2/srtp.h>
+#include <math.h>
+#include "portaudio.h"
+#ifdef HAVE_OPUS
+#include <opus.h>
+#endif
+#endif
+
+#include "rexmpp.h"
+#include "rexmpp_xml.h"
+#include "rexmpp_jingle.h"
+#include "rexmpp_base64.h"
+#include "rexmpp_random.h"
+#include "rexmpp_tls.h"
+#include "rexmpp_digest.h"
+
+
+/* https://en.wikipedia.org/wiki/G.711 */
+uint8_t rexmpp_pcma_encode (int16_t x) {
+ uint8_t sign = 0x80;
+ uint8_t pos;
+ uint8_t val = 0;
+ if (x < 0) {
+ x = -x;
+ sign = 0;
+ }
+ x >>= 3;
+ for (pos = 11; pos > 5 && ! (x & (1 << pos)); pos--);
+ val = (x >> (pos - 4)) & 0xf;
+ return (sign | ((pos - 4) << 4) | val) ^ 0x55;
+}
+
+int16_t rexmpp_pcma_decode (uint8_t x) {
+ x ^= 0x55;
+ int16_t sign = -1;
+ uint8_t shift = (x >> 4) & 7;
+ int16_t val = x & 0xf;
+ if (x & 0x80) {
+ x ^= 0x80;
+ sign = 1;
+ }
+ val = (val << 1) | 1;
+ if (shift > 0) {
+ val = (val | 0x20) << (shift - 1);
+ }
+ val <<= 3;
+ return sign * val;
+}
+
+uint8_t rexmpp_pcmu_encode (int16_t x) {
+ uint8_t sign = 0;
+ uint8_t pos;
+ uint8_t val = 0;
+ if (x < 0) {
+ x = -x;
+ sign = 0x80;
+ }
+ x >>= 2;
+ for (pos = 12; pos > 5 && ! (x & (1 << pos)); pos--);
+ val = (x >> (pos - 4)) & 0xf;
+ return (sign | ((pos - 4) << 4) | val) ^ 0xff;
+}
+
+int16_t rexmpp_pcmu_decode (uint8_t x) {
+ x ^= 0xff;
+ int16_t sign = 1;
+ uint8_t shift = (x >> 4) & 7;
+ int16_t val = x & 0xf;
+ if (x & 0x80) {
+ x ^= 0x80;
+ sign = -1;
+ }
+ val = (val << 1) | 1;
+ if (shift > 0) {
+ val = (val | 0x20) << (shift - 1);
+ }
+ val <<= 2;
+ return sign * val;
+}
+
+rexmpp_xml_t *
+rexmpp_jingle_session_payload_by_id (rexmpp_jingle_session_t *sess,
+ int payload_type_id)
+{
+ if (sess->accept == NULL) {
+ return NULL;
+ }
+ rexmpp_xml_t *descr_child =
+ rexmpp_xml_first_elem_child
+ (rexmpp_xml_find_child
+ (rexmpp_xml_find_child
+ (sess->accept, "urn:xmpp:jingle:1", "content"),
+ "urn:xmpp:jingle:apps:rtp:1", "description"));
+ while (descr_child != NULL) {
+ if (rexmpp_xml_match(descr_child, "urn:xmpp:jingle:apps:rtp:1",
+ "payload-type"))
+ {
+ const char *pl_id = rexmpp_xml_find_attr_val(descr_child, "id");
+ if (pl_id != NULL && atoi(pl_id) == payload_type_id) {
+ return descr_child;
+ }
+ }
+ descr_child = rexmpp_xml_next_elem_sibling(descr_child);
+ }
+ return NULL;
+}
+
+#ifdef ENABLE_CALLS
+int rexmpp_jingle_session_configure_audio (rexmpp_jingle_session_t *sess) {
+ if (sess->accept == NULL) {
+ return 0;
+ }
+ rexmpp_xml_t *descr_child =
+ rexmpp_xml_first_elem_child
+ (rexmpp_xml_find_child
+ (rexmpp_xml_find_child(sess->accept, "urn:xmpp:jingle:1", "content"),
+ "urn:xmpp:jingle:apps:rtp:1", "description"));
+ while (descr_child != NULL) {
+ if (rexmpp_xml_match(descr_child, "urn:xmpp:jingle:apps:rtp:1",
+ "payload-type"))
+ {
+ const char *pl_id = rexmpp_xml_find_attr_val(descr_child, "id");
+ if (pl_id != NULL) {
+ if (atoi(pl_id) == 0) {
+ rexmpp_log(sess->s, LOG_INFO,
+ "Setting the codec to PCMU (0) for Jingle session %s",
+ sess->sid);
+ sess->codec = REXMPP_CODEC_PCMU;
+ sess->payload_type = 0;
+ return sess->payload_type;
+ }
+ if (atoi(pl_id) == 8) {
+ rexmpp_log(sess->s, LOG_INFO,
+ "Setting the codec to PCMA (8) for Jingle session %s",
+ sess->sid);
+ sess->codec = REXMPP_CODEC_PCMA;
+ sess->payload_type = 8;
+ return sess->payload_type;
+ }
+#ifdef HAVE_OPUS
+ const char *pl_name = rexmpp_xml_find_attr_val(descr_child, "name");
+ if ((pl_name != NULL) && (strcmp(pl_name, "opus") == 0)) {
+ sess->codec = REXMPP_CODEC_OPUS;
+ sess->payload_type = atoi(pl_id);
+ rexmpp_log(sess->s, LOG_INFO,
+ "Setting the codec to Opus (%u) for Jingle session %s",
+ sess->payload_type, sess->sid);
+ return sess->payload_type;
+ }
+#endif /* HAVE_OPUS */
+ }
+ }
+ descr_child = rexmpp_xml_next_elem_sibling(descr_child);
+ }
+ return 0;
+}
+
+
+static int rexmpp_pa_callback (const void *input,
+ void *output,
+ unsigned long frameCount,
+ const PaStreamCallbackTimeInfo* timeInfo,
+ PaStreamCallbackFlags statusFlags,
+ void *userData)
+{
+ (void)timeInfo;
+ (void)statusFlags;
+ struct pa_buffers *data = (struct pa_buffers*)userData;
+ int16_t *out = (int16_t*)output;
+ int16_t *in = (int16_t*)input;
+ unsigned int i;
+ for (i = 0; i < frameCount; i++) {
+ if (in != NULL) {
+ data->capture.buf[data->capture.write_pos] = in[i];
+ data->capture.write_pos++;
+ data->capture.write_pos %= PA_BUF_SIZE;
+ }
+
+ if (data->playback.read_pos != data->playback.write_pos) {
+ out[i] = data->playback.buf[data->playback.read_pos];
+ data->playback.read_pos++;
+ data->playback.read_pos %= PA_BUF_SIZE;
+ } else {
+ out[i] = 0;
+ }
+ }
+ return 0;
+}
+
+void
+rexmpp_jingle_run_audio (rexmpp_jingle_session_t *sess) {
+ rexmpp_random_buf(&(sess->rtp_seq_num), sizeof(uint16_t));
+ sess->rtp_last_seq_num = 0xffff;
+ rexmpp_random_buf(&(sess->rtp_timestamp), sizeof(uint32_t));
+ rexmpp_random_buf(&(sess->rtp_ssrc), sizeof(uint32_t));
+
+ int rate = 8000;
+ int channels = 1;
+#ifdef HAVE_OPUS
+ if (sess->codec == REXMPP_CODEC_OPUS) {
+ rate = 48000;
+ channels = 2;
+ int opus_error;
+ sess->opus_enc =
+ opus_encoder_create(rate, channels, OPUS_APPLICATION_VOIP, &opus_error);
+ sess->opus_dec =
+ opus_decoder_create(rate, channels, &opus_error);
+ }
+#endif /* HAVE_OPUS */
+
+ rexmpp_log(sess->s, LOG_DEBUG,
+ "Setting up an audio stream: SSRC %" PRIx32
+ ", %d Hz, %d channels",
+ sess->rtp_ssrc, rate, channels);
+
+ PaError err = Pa_OpenDefaultStream (&(sess->pa_stream),
+ channels,
+ channels,
+ paInt16,
+ /* paFloat32, */
+ rate,
+ /* 480, */
+ paFramesPerBufferUnspecified,
+ rexmpp_pa_callback,
+ &(sess->ring_buffers));
+ if (err != paNoError) {
+ rexmpp_log(sess->s, LOG_ERR, "Failed to open a PA stream: %s",
+ Pa_GetErrorText(err));
+ }
+ err = Pa_StartStream(sess->pa_stream);
+ if (err != paNoError) {
+ rexmpp_log(sess->s, LOG_ERR, "Failed to start a PA stream: %s",
+ Pa_GetErrorText(err));
+ }
+}
+#endif /* ENABLE_CALLS */
+
+
+rexmpp_jingle_session_t *
+rexmpp_jingle_session_by_id (rexmpp_t *s, const char *sid) {
+ if (sid == NULL) {
+ return NULL;
+ }
+ rexmpp_jingle_session_t *cur = s->jingle->sessions;
+ while (cur != NULL) {
+ if (strcmp(cur->sid, sid) == 0) {
+ return cur;
+ }
+ cur = cur->next;
+ }
+ rexmpp_log(s, LOG_WARNING, "No Jingle session with sid %s found", sid);
+ return NULL;
+}
+
+void rexmpp_jingle_session_destroy (rexmpp_jingle_session_t *session) {
+ if (session->jid != NULL) {
+ free(session->jid);
+ }
+ if (session->sid != NULL) {
+ free(session->sid);
+ }
+ if (session->initiate != NULL) {
+ rexmpp_xml_free_list(session->initiate);
+ }
+ if (session->accept != NULL) {
+ rexmpp_xml_free_list(session->accept);
+ }
+ if (session->ibb_fh != NULL) {
+ fclose(session->ibb_fh);
+ }
+#ifdef ENABLE_CALLS
+ if (session->type == REXMPP_JINGLE_SESSION_MEDIA) {
+ int i;
+ if (session->pa_stream != NULL) {
+ PaError err = Pa_StopStream(session->pa_stream);
+ if (err != paNoError) {
+ rexmpp_log(session->s, LOG_ERR,
+ "Failed to close a PortAudio stream: %s",
+ Pa_GetErrorText(err));
+ }
+ session->pa_stream = NULL;
+ }
+#ifdef HAVE_OPUS
+ if (session->opus_enc != NULL) {
+ opus_encoder_destroy(session->opus_enc);
+ session->opus_enc = NULL;
+ }
+ if (session->opus_dec != NULL) {
+ opus_decoder_destroy(session->opus_dec);
+ session->opus_dec = NULL;
+ }
+#endif /* HAVE_OPUS */
+ for (i = 0; i < 2; i++) {
+ rexmpp_jingle_component_t *comp = &session->component[i];
+ if (comp->dtls_state == REXMPP_TLS_ACTIVE ||
+ comp->dtls_state == REXMPP_TLS_CLOSING ||
+ comp->dtls_state == REXMPP_TLS_CLOSED) {
+ /* SRTP structures are allocated upon a TLS connection, so
+ using the TLS state to find when they should be
+ deallocated. */
+ if (comp->srtp_in != NULL) {
+ srtp_dealloc(comp->srtp_in);
+ }
+ if (comp->srtp_out != NULL) {
+ srtp_dealloc(comp->srtp_out);
+ }
+ }
+ if (comp->dtls_state == REXMPP_TLS_HANDSHAKE ||
+ comp->dtls_state == REXMPP_TLS_ACTIVE ||
+ comp->dtls_state == REXMPP_TLS_CLOSING ||
+ comp->dtls_state == REXMPP_TLS_CLOSED) {
+ if (comp->dtls_state == REXMPP_TLS_HANDSHAKE ||
+ comp->dtls_state == REXMPP_TLS_ACTIVE) {
+ rexmpp_tls_disconnect(comp->s, comp->dtls);
+ }
+ rexmpp_tls_session_free(comp->dtls);
+ rexmpp_tls_ctx_free(comp->dtls);
+ comp->dtls = NULL;
+ comp->dtls_state = REXMPP_TLS_INACTIVE;
+ }
+ }
+ if (session->ice_agent != NULL) {
+ nice_agent_close_async(session->ice_agent, NULL, NULL);
+ g_object_unref(session->ice_agent);
+ session->ice_agent = NULL;
+ }
+ if (session->stun_host != NULL) {
+ free(session->stun_host);
+ session->stun_host = NULL;
+ }
+ if (session->turn_host != NULL) {
+ free(session->turn_host);
+ session->turn_host = NULL;
+ }
+ if (session->turn_username != NULL) {
+ free(session->turn_username);
+ session->turn_username = NULL;
+ }
+ if (session->turn_password != NULL) {
+ free(session->turn_password);
+ session->turn_password = NULL;
+ }
+ }
+#endif /* ENABLE_CALLS */
+ free(session);
+}
+
+void rexmpp_jingle_session_delete (rexmpp_t *s, rexmpp_jingle_session_t *sess) {
+ if (sess == NULL) {
+ return;
+ }
+ rexmpp_log(s, LOG_DEBUG, "Removing Jingle session %s", sess->sid);
+ rexmpp_jingle_session_t *cur = s->jingle->sessions, *prev = NULL;
+ while (cur != NULL) {
+ if (sess == cur) {
+ if (prev == NULL) {
+ s->jingle->sessions = cur->next;
+ } else {
+ prev->next = cur->next;
+ }
+ rexmpp_jingle_session_destroy(sess);
+ return;
+ }
+ prev = cur;
+ cur = cur->next;
+ }
+}
+
+void rexmpp_jingle_session_delete_by_id (rexmpp_t *s, const char *sid) {
+ rexmpp_jingle_session_delete(s, rexmpp_jingle_session_by_id(s, sid));
+}
+
+int rexmpp_jingle_session_add (rexmpp_t *s, rexmpp_jingle_session_t *sess) {
+ uint32_t sessions_num = 0;
+ rexmpp_jingle_session_t *cur = s->jingle->sessions;
+ while (cur != NULL) {
+ sessions_num++;
+ cur = cur->next;
+ }
+ if (sessions_num >= s->max_jingle_sessions) {
+ rexmpp_log(s, LOG_ERR, "Too many Jingle sessions, discaring a new one");
+ rexmpp_jingle_session_destroy(sess);
+ return 0;
+ }
+ rexmpp_log(s, LOG_DEBUG, "Adding Jingle session %s", sess->sid);
+ sess->next = s->jingle->sessions;
+ s->jingle->sessions = sess;
+ return 1;
+}
+
+int rexmpp_jingle_ice_agent_init (rexmpp_jingle_session_t *sess);
+
+rexmpp_jingle_session_t *
+rexmpp_jingle_session_create (rexmpp_t *s,
+ char *jid,
+ char *sid,
+ enum rexmpp_jingle_session_type type,
+ int initiator)
+{
+ rexmpp_jingle_session_t *sess = malloc(sizeof(rexmpp_jingle_session_t));
+ if (sess != NULL) {
+ sess->jid = jid;
+ sess->sid = sid;
+ sess->initiate = NULL;
+ sess->accept = NULL;
+ sess->s = s;
+ sess->initiator = initiator;
+ sess->type = type;
+ sess->ibb_fh = NULL;
+ sess->ibb_sid = NULL;
+ sess->ibb_seq = 0;
+#ifdef ENABLE_CALLS
+ sess->stun_host = NULL;
+ sess->stun_port = 0;
+ sess->turn_host = NULL;
+ sess->turn_port = 0;
+ sess->turn_username = NULL;
+ sess->turn_password = NULL;
+
+ int i;
+ for (i = 0; i < 2; i++) {
+ sess->component[i].component_id = i + 1;
+ sess->component[i].session = sess;
+ sess->component[i].s = s;
+ sess->component[i].dtls_state = REXMPP_TLS_INACTIVE;
+ sess->component[i].dtls = rexmpp_tls_ctx_new(s, 1);
+ sess->component[i].srtp_out = NULL;
+ sess->component[i].srtp_in = NULL;
+ }
+
+ sess->rtcp_mux = s->jingle_prefer_rtcp_mux;
+ sess->ice_agent = NULL;
+
+ sess->pa_stream = NULL;
+
+ sess->ring_buffers.capture.read_pos = 0;
+ sess->ring_buffers.capture.write_pos = 0;
+ sess->ring_buffers.playback.read_pos = 0;
+ sess->ring_buffers.playback.write_pos = 0;
+ sess->codec = REXMPP_CODEC_UNDEFINED;
+ sess->payload_type = 0;
+
+#ifdef HAVE_OPUS
+ sess->opus_enc = NULL;
+ sess->opus_dec = NULL;
+#endif /* HAVE_POUS */
+#endif /* ENABLE_CALLS */
+ if (! rexmpp_jingle_session_add(s, sess)) {
+ /* The session is destroyed by rexmpp_jingle_session_add */
+ return NULL;
+ }
+ return sess;
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to allocate memory for a Jingle session");
+ return NULL;
+ }
+}
+
+rexmpp_jingle_session_t *
+rexmpp_jingle_session_by_ibb_sid (rexmpp_t *s, const char *ibb_sid) {
+ if (ibb_sid == NULL) {
+ return NULL;
+ }
+ rexmpp_jingle_session_t *cur = s->jingle->sessions;
+ while (cur != NULL) {
+ if (cur->type == REXMPP_JINGLE_SESSION_FILE &&
+ strcmp(cur->ibb_sid, ibb_sid) == 0) {
+ return cur;
+ }
+ cur = cur->next;
+ }
+ rexmpp_log(s, LOG_WARNING,
+ "No Jingle session with ibb_sid %s found", ibb_sid);
+ return NULL;
+}
+
+int rexmpp_jingle_init (rexmpp_t *s) {
+ s->jingle = malloc(sizeof(struct rexmpp_jingle_ctx));
+ s->jingle->sessions = NULL;
+#ifdef ENABLE_CALLS
+ g_networking_init();
+ srtp_init();
+ s->jingle->gloop = g_main_loop_new(NULL, FALSE);
+ Pa_Initialize();
+ nice_debug_enable(1);
+#endif /* ENABLE_CALLS */
+ return 0;
+}
+
+void rexmpp_jingle_stop (rexmpp_t *s) {
+ while (s->jingle->sessions != NULL) {
+ rexmpp_jingle_session_delete(s, s->jingle->sessions);
+ }
+#ifdef ENABLE_CALLS
+ g_main_loop_quit(s->jingle->gloop);
+ s->jingle->gloop = NULL;
+ srtp_shutdown();
+ Pa_Terminate();
+#endif /* ENABLE_CALLS */
+ free(s->jingle);
+ s->jingle = NULL;
+}
+
+
+void rexmpp_jingle_accept_file_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "Failed to accept a Jingle file transfer");
+ rexmpp_jingle_session_delete_by_id(s, sid);
+ }
+ free(sid);
+}
+
+rexmpp_err_t
+rexmpp_jingle_accept_file (rexmpp_t *s,
+ rexmpp_jingle_session_t *session,
+ const char *path)
+{
+ session->ibb_fh = fopen(path, "wb");
+ if (session->ibb_fh == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to open %s for writing: %s",
+ path, strerror(errno));
+ return REXMPP_E_OTHER;
+ }
+ rexmpp_xml_t *jingle = session->initiate;
+ rexmpp_xml_t *content = rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content");
+
+ rexmpp_xml_t *new_jingle =
+ rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(new_jingle, "action", "session-accept");
+ rexmpp_xml_add_attr(new_jingle, "responder", s->assigned_jid.full);
+ rexmpp_xml_add_attr(new_jingle, "sid", session->sid);
+ rexmpp_xml_add_child(new_jingle, rexmpp_xml_clone(content));
+ session->accept = rexmpp_xml_clone(new_jingle);
+ return rexmpp_iq_new(s, "set", session->jid, new_jingle,
+ rexmpp_jingle_accept_file_cb, strdup(session->sid));
+}
+
+rexmpp_err_t
+rexmpp_jingle_accept_file_by_id (rexmpp_t *s,
+ const char *sid,
+ const char *path)
+{
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid);
+ if (session == NULL) {
+ return REXMPP_E_OTHER;
+ }
+ return rexmpp_jingle_accept_file(s, session, path);
+}
+
+void rexmpp_jingle_session_terminate_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "Failed to terminate session %s, removing anyway",
+ sid);
+ }
+ free(sid);
+}
+
+rexmpp_err_t
+rexmpp_jingle_session_terminate (rexmpp_t *s,
+ const char *sid,
+ rexmpp_xml_t *reason_node,
+ const char *reason_text)
+{
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid);
+ if (session == NULL) {
+ return REXMPP_E_OTHER;
+ }
+ rexmpp_xml_t *jingle =
+ rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(jingle, "action", "session-terminate");
+ rexmpp_xml_add_attr(jingle, "sid", sid);
+ rexmpp_xml_t *reason =
+ rexmpp_xml_new_elem("reason", "urn:xmpp:jingle:1");
+ if (reason_text != NULL) {
+ rexmpp_xml_t *text =
+ rexmpp_xml_new_elem("text", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_text(text, reason_text);
+ rexmpp_xml_add_child(reason, text);
+ }
+ rexmpp_xml_add_child(reason, reason_node);
+ rexmpp_xml_add_child(jingle, reason);
+ rexmpp_err_t ret = rexmpp_iq_new(s, "set", session->jid, jingle,
+ rexmpp_jingle_session_terminate_cb,
+ strdup(sid));
+ rexmpp_jingle_session_delete_by_id(s, sid);
+ return ret;
+}
+
+void rexmpp_jingle_send_file_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "Failed to initiate file sending for sid %s", sid);
+ rexmpp_jingle_session_delete_by_id(s, sid);
+ }
+ free(sid);
+}
+
+rexmpp_err_t
+rexmpp_jingle_send_file (rexmpp_t *s,
+ const char *jid,
+ char *path)
+{
+ /* Open the file and calculate its hash before allocating the other
+ things, so we can easily return on failure. */
+ FILE *fh = fopen(path, "rb");
+ if (fh == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to open %s for reading", path);
+ return REXMPP_E_OTHER;
+ }
+
+ char buf[4096];
+ rexmpp_digest_t sha256, sha3_256;
+ if (rexmpp_digest_init(&sha256, REXMPP_DIGEST_SHA256)) {
+ rexmpp_log(s, LOG_ERR, "Failed to initialize a SHA-256 digest object");
+ fclose(fh);
+ return REXMPP_E_OTHER;
+ }
+ if (rexmpp_digest_init(&sha3_256, REXMPP_DIGEST_SHA3_256)) {
+ rexmpp_log(s, LOG_ERR, "Failed to initialize a SHA-3-256 digest object");
+ rexmpp_digest_finish(&sha256, NULL, 0);
+ fclose(fh);
+ return REXMPP_E_OTHER;
+ }
+ size_t len = fread(buf, 1, 4096, fh);
+ while (len > 0) {
+ rexmpp_digest_update(&sha256, buf, len);
+ rexmpp_digest_update(&sha3_256, buf, len);
+ len = fread(buf, 1, 4096, fh);
+ }
+
+ char *sid = rexmpp_random_id();
+ char *ibb_sid = rexmpp_random_id();
+
+ rexmpp_xml_t *jingle =
+ rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(jingle, "action", "session-initiate");
+ rexmpp_xml_add_attr(jingle, "sid", sid);
+ rexmpp_xml_add_attr(jingle, "initiator", s->assigned_jid.full);
+
+ rexmpp_xml_t *content =
+ rexmpp_xml_new_elem("content", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(content, "creator", "initiator");
+ rexmpp_xml_add_attr(content, "name", "IBB file");
+ rexmpp_xml_add_child(jingle, content);
+
+ rexmpp_xml_t *transport =
+ rexmpp_xml_new_elem("transport", "urn:xmpp:jingle:transports:ibb:1");
+ rexmpp_xml_add_attr(transport, "block-size", "4096");
+ rexmpp_xml_add_attr(transport, "sid", ibb_sid);
+ rexmpp_xml_add_child(content, transport);
+ rexmpp_xml_t *description =
+ rexmpp_xml_new_elem("description", "urn:xmpp:jingle:apps:file-transfer:5");
+ rexmpp_xml_add_child(content, description);
+ rexmpp_xml_t *file =
+ rexmpp_xml_new_elem("file", "urn:xmpp:jingle:apps:file-transfer:5");
+ rexmpp_xml_add_child(description, file);
+ rexmpp_xml_t *file_name =
+ rexmpp_xml_new_elem("name", "urn:xmpp:jingle:apps:file-transfer:5");
+ rexmpp_xml_add_text(file_name, basename(path));
+ rexmpp_xml_add_child(file, file_name);
+
+ char hash[32];
+ char *hash_base64 = NULL;
+ size_t hash_base64_len = 0;
+
+ rexmpp_digest_finish(&sha256, hash,
+ rexmpp_digest_len(REXMPP_DIGEST_SHA256));
+ rexmpp_base64_to(hash,
+ rexmpp_digest_len(REXMPP_DIGEST_SHA256),
+ &hash_base64,
+ &hash_base64_len);
+ rexmpp_xml_t *file_hash =
+ rexmpp_xml_new_elem("hash", "urn:xmpp:hashes:2");
+ rexmpp_xml_add_attr(file_hash, "algo", "sha-256");
+ rexmpp_xml_add_text(file_hash, hash_base64);
+ free(hash_base64);
+ rexmpp_xml_add_child(file, file_hash);
+
+ hash_base64 = NULL;
+ hash_base64_len = 0;
+ rexmpp_digest_finish(&sha3_256, hash,
+ rexmpp_digest_len(REXMPP_DIGEST_SHA3_256));
+ rexmpp_base64_to(hash,
+ rexmpp_digest_len(REXMPP_DIGEST_SHA3_256),
+ &hash_base64,
+ &hash_base64_len);
+ file_hash = rexmpp_xml_new_elem("hash", "urn:xmpp:hashes:2");
+ rexmpp_xml_add_attr(file_hash, "algo", "sha3-256");
+ rexmpp_xml_add_text(file_hash, hash_base64);
+ free(hash_base64);
+ rexmpp_xml_add_child(file, file_hash);
+
+ long fsize = ftell(fh);
+ fseek(fh, 0, SEEK_SET);
+ snprintf(buf, 11, "%ld", fsize);
+ rexmpp_xml_t *file_size =
+ rexmpp_xml_new_elem("size", "urn:xmpp:jingle:apps:file-transfer:5");
+ rexmpp_xml_add_text(file_size, buf);
+ rexmpp_xml_add_child(file, file_size);
+
+ rexmpp_jingle_session_t *sess =
+ rexmpp_jingle_session_create(s, strdup(jid), sid, REXMPP_JINGLE_SESSION_FILE, 1);
+ if (sess != NULL) {
+ sess->initiate = rexmpp_xml_clone(jingle);
+ sess->ibb_sid = ibb_sid;
+ sess->ibb_fh = fh;
+ return rexmpp_iq_new(s, "set", sess->jid, jingle,
+ rexmpp_jingle_send_file_cb, strdup(sess->sid));
+ } else {
+ return REXMPP_E_OTHER;
+ }
+}
+
+
+void rexmpp_jingle_ibb_close_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (success) {
+ rexmpp_log(s, LOG_DEBUG, "Closed IBB stream for Jingle stream %s", sid);
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to close IBB stream for Jingle stream %s", sid);
+ }
+ free(sid);
+}
+
+void rexmpp_jingle_ibb_send_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "An IBB stream error for Jingle sid %s", sid);
+ rexmpp_jingle_session_delete_by_id(s, sid);
+ free(sid);
+ return;
+ }
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid);
+ if (session == NULL) {
+ rexmpp_log(s, LOG_ERR, "Jingle session %s doesn't exist", sid);
+ free(sid);
+ return;
+ }
+ if (feof(session->ibb_fh)) {
+ rexmpp_xml_t *close =
+ rexmpp_xml_new_elem("close", "http://jabber.org/protocol/ibb");
+ rexmpp_xml_add_attr(close, "sid", session->ibb_sid);
+ rexmpp_iq_new(s, "set", session->jid, close,
+ rexmpp_jingle_ibb_close_cb, sid);
+ return;
+ } else {
+ char buf[4096];
+ size_t len = fread(buf, 1, 4096, session->ibb_fh);
+ if (len > 0) {
+ rexmpp_xml_t *data =
+ rexmpp_xml_new_elem("data", "http://jabber.org/protocol/ibb");
+ rexmpp_xml_add_attr(data, "sid", session->ibb_sid);
+ char *out = NULL;
+ size_t out_len = 0;
+ rexmpp_base64_to(buf, len, &out, &out_len);
+ rexmpp_xml_add_text(data, out);
+ free(out);
+ snprintf(buf, 11, "%u", session->ibb_seq);
+ rexmpp_xml_add_attr(data, "seq", buf);
+ session->ibb_seq++;
+ rexmpp_iq_new(s, "set", session->jid, data,
+ rexmpp_jingle_ibb_send_cb, sid);
+ return;
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to read from a file: %s ", strerror(errno));
+ rexmpp_jingle_session_terminate(s, sid,
+ rexmpp_xml_new_elem("media-error",
+ "urn:xmpp:jingle:1"),
+ "File reading error");
+ }
+ }
+ free(sid);
+}
+
+#ifdef ENABLE_CALLS
+void rexmpp_jingle_call_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "Failed to initiate a call for sid %s", sid);
+ rexmpp_jingle_session_delete_by_id(s, sid);
+ }
+ free(sid);
+}
+
+void
+rexmpp_jingle_ice_udp_add_remote (rexmpp_jingle_session_t *sess,
+ rexmpp_xml_t *transport)
+{
+ if (sess->ice_agent == NULL) {
+ /* Must be an incoming call; just add candidates to
+ session-initiate's transport. */
+ rexmpp_xml_t *old_transport =
+ rexmpp_xml_find_child(rexmpp_xml_find_child(sess->initiate,
+ "urn:xmpp:jingle:1",
+ "content"),
+ "urn:xmpp:jingle:transports:ice-udp:1",
+ "transport");
+ rexmpp_xml_t *candidate = rexmpp_xml_first_elem_child(transport);
+ while (rexmpp_xml_match(candidate, "urn:xmpp:jingle:transports:ice-udp:1",
+ "candidate")) {
+ rexmpp_xml_add_child(old_transport, rexmpp_xml_clone(candidate));
+ candidate = rexmpp_xml_next_elem_sibling(candidate);
+ }
+ return;
+ }
+ const char *ufrag = rexmpp_xml_find_attr_val(transport, "ufrag");
+ const char *password = rexmpp_xml_find_attr_val(transport, "pwd");
+ nice_agent_set_remote_credentials(sess->ice_agent, sess->ice_stream_id,
+ ufrag, password);
+
+ int component_id;
+
+ for (component_id = 1; component_id <= (sess->rtcp_mux ? 1 : 2); component_id++) {
+ GSList *remote_candidates =
+ nice_agent_get_remote_candidates(sess->ice_agent,
+ sess->ice_stream_id,
+ component_id);
+ rexmpp_xml_t *candidate = rexmpp_xml_first_elem_child(transport);
+ while (candidate != NULL) {
+ if (rexmpp_xml_match(candidate, "urn:xmpp:jingle:transports:ice-udp:1",
+ "candidate")) {
+ const char *component = rexmpp_xml_find_attr_val(candidate, "component");
+ if (component[0] == component_id + '0') {
+ const char *type_str = rexmpp_xml_find_attr_val(candidate, "type");
+ int type_n = NICE_CANDIDATE_TYPE_HOST;
+ if (strcmp(type_str, "host") == 0) {
+ type_n = NICE_CANDIDATE_TYPE_HOST;
+ } else if (strcmp(type_str, "srflx") == 0) {
+ type_n = NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE;
+ } else if (strcmp(type_str, "prflx") == 0) {
+ type_n = NICE_CANDIDATE_TYPE_PEER_REFLEXIVE;
+ } else if (strcmp(type_str, "relay") == 0) {
+ type_n = NICE_CANDIDATE_TYPE_RELAYED;
+ }
+ NiceCandidate *c = nice_candidate_new(type_n);
+ c->component_id = component_id;
+ c->stream_id = sess->ice_stream_id;
+
+ const char *foundation = rexmpp_xml_find_attr_val(candidate, "foundation");
+ strncpy(c->foundation, foundation, NICE_CANDIDATE_MAX_FOUNDATION - 1);
+ c->foundation[NICE_CANDIDATE_MAX_FOUNDATION - 1] = 0;
+
+ c->transport = NICE_CANDIDATE_TRANSPORT_UDP;
+
+ const char *priority = rexmpp_xml_find_attr_val(candidate, "priority");
+ c->priority = atoi(priority);
+
+ const char *ip = rexmpp_xml_find_attr_val(candidate, "ip");
+ if (! nice_address_set_from_string(&c->addr, ip)) {
+ rexmpp_log(sess->s, LOG_ERR,
+ "Failed to parse an ICE-UDP candidate's address: %s",
+ ip);
+ }
+
+ const char *port = rexmpp_xml_find_attr_val(candidate, "port");
+ nice_address_set_port(&c->addr, atoi(port));
+
+ remote_candidates = g_slist_prepend(remote_candidates, c);
+ }
+ }
+ candidate = rexmpp_xml_next_elem_sibling(candidate);
+ }
+ if (remote_candidates != NULL) {
+ rexmpp_log(sess->s, LOG_DEBUG,
+ "Setting %d remote candidates for component %d",
+ g_slist_length(remote_candidates),
+ component_id);
+ nice_agent_set_remote_candidates(sess->ice_agent, sess->ice_stream_id,
+ component_id, remote_candidates);
+ g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free);
+ } else {
+ rexmpp_log(sess->s, LOG_WARNING,
+ "No remote candidates for component %d",
+ component_id);
+ }
+ }
+}
+
+/* Checks whether we are in the active (client) role for DTLS, based
+ on either "session-initiate" or "session-accept" message. */
+int rexmpp_jingle_dtls_is_active (rexmpp_jingle_session_t *sess, int in_initiate) {
+ rexmpp_xml_t *fingerprint =
+ rexmpp_xml_find_child
+ (rexmpp_xml_find_child
+ (rexmpp_xml_find_child
+ (in_initiate ? sess->initiate : sess->accept,
+ "urn:xmpp:jingle:1", "content"),
+ "urn:xmpp:jingle:transports:ice-udp:1", "transport"),
+ "urn:xmpp:jingle:apps:dtls:0", "fingerprint");
+ if (fingerprint == NULL) {
+ rexmpp_log(sess->s, LOG_ERR, "No fingerprint in the 'session-%s' Jingle element",
+ in_initiate ? "initiate" : "accept");
+ return 0;
+ }
+ const char *fingerprint_setup = rexmpp_xml_find_attr_val(fingerprint, "setup");
+ if (fingerprint_setup == NULL) {
+ rexmpp_log(sess->s, LOG_ERR, "No 'setup' attribute for a fingerprint element");
+ return 0;
+ }
+ int active = 0;
+ if (sess->initiator) {
+ if (in_initiate) {
+ active = (strcmp(fingerprint_setup, "active") == 0);
+ } else {
+ active = (strcmp(fingerprint_setup, "active") != 0);
+ }
+ } else {
+ if (in_initiate) {
+ active = (strcmp(fingerprint_setup, "active") != 0);
+ } else {
+ active = (strcmp(fingerprint_setup, "active") == 0);
+ }
+ }
+ return active;
+}
+
+
+void rexmpp_transport_info_call_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to send additional candidate for Jingle session %s",
+ sid);
+ }
+ free(ptr);
+}
+
+void
+rexmpp_jingle_candidate_gathering_done_cb (NiceAgent *agent,
+ guint stream_id,
+ gpointer data)
+{
+ rexmpp_jingle_session_t *sess = data;
+
+ rexmpp_log(sess->s, LOG_DEBUG, "ICE agent candidate gathering is done");
+
+ /* We'll need a fingerprint a bit later, but checking it before
+ allocating other things. */
+ char fp[32], fp_str[32 * 3 + 1];
+ size_t fp_size = 32;
+ if (rexmpp_tls_my_fp(sess->s, fp, fp_str, &fp_size)) {
+ return;
+ }
+
+ rexmpp_xml_t *jingle = rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(jingle, "sid", sess->sid);
+
+ rexmpp_xml_t *content = rexmpp_xml_new_elem("content", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(content, "creator", "initiator");
+ rexmpp_xml_add_attr(content, "senders", "both");
+ rexmpp_xml_t *description;
+ if (sess->initiator) {
+ /* We are the intiator: send the predefined options. */
+ rexmpp_xml_add_attr(jingle, "action", "session-initiate");
+ rexmpp_xml_add_attr(jingle, "initiator", sess->s->assigned_jid.full);
+ rexmpp_xml_add_attr(content, "name", "call");
+
+ /* https://datatracker.ietf.org/doc/html/rfc4568 */
+ rexmpp_xml_t *encryption =
+ rexmpp_xml_new_elem("encryption", "urn:xmpp:jingle:apps:rtp:1");
+ rexmpp_xml_add_attr(encryption, "required", "true");
+ rexmpp_xml_add_child(content, encryption);
+ rexmpp_xml_t *crypto =
+ rexmpp_xml_new_elem("crypto", "urn:xmpp:jingle:apps:rtp:1");
+ rexmpp_xml_add_attr(crypto, "crypto-suite", "AES_CM_128_HMAC_SHA1_80");
+ rexmpp_xml_add_attr(crypto, "tag", "1");
+ rexmpp_xml_add_child(encryption, crypto);
+
+ description = rexmpp_xml_clone(sess->s->jingle_rtp_description);
+ } else {
+ /* Accepting the call, will have to compare payload type options
+ to supported and preferred ones, and pick some from those. */
+ rexmpp_xml_t *init_jingle = sess->initiate;
+ rexmpp_xml_t *init_content =
+ rexmpp_xml_find_child(init_jingle, "urn:xmpp:jingle:1", "content");
+ const char *init_content_name = rexmpp_xml_find_attr_val(init_content, "name");
+ if (init_content_name != NULL) {
+ rexmpp_xml_add_attr(content, "name", init_content_name);
+ } else {
+ rexmpp_log(sess->s, LOG_ERR,
+ "Empty content name for Jingle session %s with %s",
+ sess->sid, sess->jid);
+ }
+ rexmpp_xml_add_attr(jingle, "action", "session-accept");
+ rexmpp_xml_add_attr(jingle, "initiator", sess->jid);
+ rexmpp_xml_add_attr(jingle, "responder", sess->s->assigned_jid.full);
+
+ description = rexmpp_xml_clone(sess->s->jingle_rtp_description);
+ /* Find the first matching payload-type and add that */
+ rexmpp_xml_t *pl_type =
+ rexmpp_xml_first_elem_child(sess->s->jingle_rtp_description);
+ rexmpp_xml_t *selected_pl = NULL;
+ while (pl_type != NULL) {
+ if (rexmpp_xml_match(pl_type, "urn:xmpp:jingle:apps:rtp:1", "payload-type")) {
+ const char *pl_id = rexmpp_xml_find_attr_val(pl_type, "id");
+ if (pl_id != NULL) {
+ int pl_id_num = atoi(pl_id);
+ rexmpp_xml_t *proposed_pl_type =
+ rexmpp_xml_first_elem_child
+ (rexmpp_xml_find_child
+ (rexmpp_xml_find_child(sess->initiate,
+ "urn:xmpp:jingle:1", "content"),
+ "urn:xmpp:jingle:apps:rtp:1", "description"));
+ while (proposed_pl_type != NULL && selected_pl == NULL) {
+ if (rexmpp_xml_match(proposed_pl_type, "urn:xmpp:jingle:apps:rtp:1", "payload-type")) {
+ const char *proposed_pl_id = rexmpp_xml_find_attr_val(proposed_pl_type, "id");
+ if (proposed_pl_id != NULL) {
+ int proposed_pl_id_num = atoi(proposed_pl_id);
+ if (pl_id_num < 96 && pl_id_num == proposed_pl_id_num) {
+ selected_pl = pl_type;
+ } else {
+ const char *pl_name =
+ rexmpp_xml_find_attr_val(pl_type, "name");
+ if (pl_name != NULL) {
+ const char *proposed_pl_name =
+ rexmpp_xml_find_attr_val(proposed_pl_type, "name");
+ if (proposed_pl_name != NULL) {
+ if (strcmp(pl_name, proposed_pl_name) == 0) {
+ /* todo: compare clock rates, numbers of
+ channels, parameters */
+ selected_pl = pl_type;
+ }
+ }
+ }
+ }
+ }
+ }
+ proposed_pl_type = rexmpp_xml_next_elem_sibling(proposed_pl_type);
+ }
+ } else {
+ rexmpp_log(sess->s, LOG_ERR,
+ "No 'id' specified for a payload-type element.");
+ }
+ }
+ pl_type = pl_type->next;
+ }
+ if (selected_pl != NULL) {
+ rexmpp_xml_add_child(description, rexmpp_xml_clone(selected_pl));
+ } else {
+ rexmpp_log(sess->s, LOG_ERR, "No suitable payload type found");
+ /* todo: fail if it's NULL, though it shouldn't happen, since
+ PCMU and PCMA are mandatory */
+ }
+ }
+
+ rexmpp_xml_add_child(jingle, content);
+ rexmpp_xml_add_child(content, description);
+
+ if (sess->rtcp_mux) {
+ rexmpp_xml_t *rtcp_mux =
+ rexmpp_xml_new_elem("rtcp-mux", "urn:xmpp:jingle:apps:rtp:1");
+ rexmpp_xml_add_child(description, rtcp_mux);
+ }
+
+ rexmpp_xml_t *transport =
+ rexmpp_xml_new_elem("transport", "urn:xmpp:jingle:transports:ice-udp:1");
+ gchar *ufrag = NULL;
+ gchar *password = NULL;
+ nice_agent_get_local_credentials(agent, stream_id, &ufrag, &password);
+ rexmpp_xml_add_attr(transport, "ufrag", ufrag);
+ rexmpp_xml_add_attr(transport, "pwd", password);
+ g_free(ufrag);
+ g_free(password);
+ rexmpp_xml_add_child(content, transport);
+ int component_id;
+ rexmpp_xml_t *postponed_candidates = NULL;
+ for (component_id = 1; component_id <= (sess->rtcp_mux ? 1 : 2); component_id++) {
+ GSList *candidates = nice_agent_get_local_candidates(agent, stream_id, component_id);
+ GSList *cand_cur = candidates;
+ int cand_num = 0;
+ while (cand_cur != NULL) {
+ rexmpp_xml_t *candidate =
+ rexmpp_xml_new_elem("candidate", "urn:xmpp:jingle:transports:ice-udp:1");
+ char buf[INET6_ADDRSTRLEN];
+ NiceCandidate *c = (NiceCandidate *)cand_cur->data;
+ snprintf(buf, 11, "%u", component_id);
+ rexmpp_xml_add_attr(candidate, "component", buf);
+ rexmpp_xml_add_attr(candidate, "foundation", c->foundation);
+ rexmpp_xml_add_attr(candidate, "generation", "0");
+ char *cid = rexmpp_random_id();
+ rexmpp_xml_add_attr(candidate, "id", cid);
+ free(cid);
+ nice_address_to_string(&c->addr, buf);
+ rexmpp_xml_add_attr(candidate, "ip", buf);
+ snprintf(buf, 11, "%u", nice_address_get_port(&c->addr));
+ rexmpp_xml_add_attr(candidate, "port", buf);
+ rexmpp_xml_add_attr(candidate, "network", "0");
+ rexmpp_xml_add_attr(candidate, "protocol", "udp");
+ snprintf(buf, 11, "%u", c->priority);
+ rexmpp_xml_add_attr(candidate, "priority", buf);
+ char *nice_type[] = {"host", "srflx", "prflx", "relay"};
+ if (c->type < 4) {
+ rexmpp_xml_add_attr(candidate, "type", nice_type[c->type]);
+ }
+ /* Can't send too many candidates, since stanza sizes are usually
+ limited, and then it breaks the stream. Limiting to 10 per
+ component, sending the rest later, via transport-info. */
+ if (cand_num < 10) {
+ rexmpp_xml_add_child(transport, candidate);
+ } else {
+ rexmpp_xml_t *jingle_ti =
+ rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(jingle_ti, "sid", sess->sid);
+ rexmpp_xml_add_attr(jingle_ti, "action", "transport-info");
+ rexmpp_xml_t *content_copy = rexmpp_xml_clone(content);
+ rexmpp_xml_t *transport_copy = rexmpp_xml_clone(transport);
+ rexmpp_xml_add_child(jingle_ti, content_copy);
+ rexmpp_xml_add_child(content_copy, transport_copy);
+ rexmpp_xml_add_child(transport_copy, candidate);
+ jingle_ti->next = postponed_candidates;
+ postponed_candidates = jingle_ti;
+ }
+ cand_cur = cand_cur->next;
+ cand_num++;
+ }
+ if (candidates != NULL) {
+ g_slist_free_full(candidates, (GDestroyNotify)&nice_candidate_free);
+ }
+ }
+
+ rexmpp_xml_t *fingerprint =
+ rexmpp_xml_new_elem("fingerprint", "urn:xmpp:jingle:apps:dtls:0");
+ rexmpp_xml_add_attr(fingerprint, "hash", "sha-256");
+ if (sess->initiator) {
+ rexmpp_xml_add_attr(fingerprint, "setup", "actpass");
+ } else if (rexmpp_jingle_dtls_is_active(sess, 1)) {
+ rexmpp_xml_add_attr(fingerprint, "setup", "active");
+ } else {
+ rexmpp_xml_add_attr(fingerprint, "setup", "passive");
+ }
+
+ rexmpp_xml_add_text(fingerprint, fp_str);
+ rexmpp_xml_add_child(transport, fingerprint);
+
+ if (sess->initiator) {
+ sess->initiate = rexmpp_xml_clone(jingle);
+ } else {
+ sess->accept = rexmpp_xml_clone(jingle);
+ rexmpp_jingle_session_configure_audio(sess);
+ rexmpp_jingle_run_audio(sess);
+ }
+
+ rexmpp_iq_new(sess->s, "set", sess->jid, jingle,
+ rexmpp_jingle_call_cb, strdup(sess->sid));
+
+ /* Now send transport-info messages with candidates that didn't fit
+ initially. */
+ while (postponed_candidates != NULL) {
+ rexmpp_xml_t *pc_next = postponed_candidates->next;
+ postponed_candidates->next = NULL;
+ rexmpp_iq_new(sess->s, "set", sess->jid, postponed_candidates,
+ rexmpp_transport_info_call_cb, strdup(sess->sid));
+ postponed_candidates = pc_next;
+ }
+}
+
+ssize_t
+rexmpp_jingle_dtls_push_func (void *p, const void *data, size_t size)
+{
+ rexmpp_jingle_component_t *comp = p;
+ rexmpp_jingle_session_t *sess = comp->session;
+ return nice_agent_send(sess->ice_agent, sess->ice_stream_id,
+ comp->component_id, size, data);
+}
+
+rexmpp_tls_t *rexmpp_jingle_component_dtls(void *p) {
+ rexmpp_jingle_component_t *comp = p;
+ return comp->dtls;
+}
+
+/* The timeout is always zero for DTLS. */
+int rexmpp_jingle_dtls_pull_timeout_func (void *p,
+ unsigned int ms)
+{
+ rexmpp_jingle_component_t *comp = p;
+ rexmpp_jingle_session_t *sess = comp->session;
+ /* if (comp->dtls->dtls_buf_len > 0) { */
+ /* return comp->dtls->dtls_buf_len; */
+ /* } */
+
+ fd_set rfds;
+ struct timeval tv;
+
+ struct sockaddr_in cli_addr;
+ socklen_t cli_addr_size;
+ int ret;
+ char c;
+
+ FD_ZERO(&rfds);
+ int fd, nfds = -1;
+
+ GPtrArray *sockets =
+ nice_agent_get_sockets(sess->ice_agent,
+ sess->ice_stream_id, comp->component_id);
+ guint i;
+ for (i = 0; i < sockets->len; i++) {
+ fd = g_socket_get_fd(sockets->pdata[i]);
+ FD_SET(fd, &rfds);
+ if (fd > nfds) {
+ nfds = fd;
+ }
+ }
+ g_ptr_array_unref(sockets);
+
+ tv.tv_sec = ms / 1000;
+ tv.tv_usec = (ms % 1000) * 1000;
+
+ ret = select(nfds + 1, &rfds, NULL, NULL, &tv);
+ if (ret < 0) {
+ rexmpp_log(sess->s, LOG_WARNING,
+ "DTLS pull function: select failed: %s",
+ strerror(errno));
+ return ret;
+ }
+
+ cli_addr_size = sizeof(cli_addr);
+ ret = 0;
+ sockets =
+ nice_agent_get_sockets(sess->ice_agent,
+ sess->ice_stream_id, comp->component_id);
+ for (i = 0; i < sockets->len; i++) {
+ int err =
+ recvfrom(g_socket_get_fd(sockets->pdata[i]), &c, 1, MSG_PEEK,
+ (struct sockaddr *) &cli_addr, &cli_addr_size);
+ if (err == -1) {
+ /* ENOTCONN and EAGAIN are common here, but report other
+ errors. */
+ if (errno != ENOTCONN && errno != EAGAIN) {
+ rexmpp_log(sess->s, LOG_WARNING,
+ "DTLS pull function: failed to peek a socket: %s",
+ strerror(errno));
+ }
+ } else {
+ ret += err;
+ }
+ }
+ g_ptr_array_unref(sockets);
+
+ if (ret > 0) {
+ return ret;
+ }
+ return 0;
+}
+
+const char *rexmpp_ice_component_state_text(int state) {
+ switch (state) {
+ case NICE_COMPONENT_STATE_DISCONNECTED: return "disconnected";
+ case NICE_COMPONENT_STATE_GATHERING: return "gathering";
+ case NICE_COMPONENT_STATE_CONNECTING: return "connecting";
+ case NICE_COMPONENT_STATE_CONNECTED: return "connected";
+ case NICE_COMPONENT_STATE_READY: return "ready";
+ case NICE_COMPONENT_STATE_FAILED: return "failed";
+ case NICE_COMPONENT_STATE_LAST: return "last";
+ default: return "unknown";
+ }
+}
+
+void
+rexmpp_jingle_component_state_changed_cb (NiceAgent *agent,
+ guint stream_id,
+ guint component_id,
+ guint state,
+ gpointer data)
+{
+ rexmpp_jingle_session_t *sess = data;
+ (void)agent;
+ rexmpp_log(sess->s, LOG_DEBUG,
+ "ICE agent component %d state for stream %d changed to %s",
+ component_id, stream_id, rexmpp_ice_component_state_text(state));
+ if (component_id < 1 || component_id > 2) {
+ rexmpp_log(sess->s, LOG_CRIT, "Unexpected ICE component_id: %d",
+ component_id);
+ return;
+ }
+ if (state == NICE_COMPONENT_STATE_READY) {
+ rexmpp_log(sess->s, LOG_INFO,
+ "ICE connection established for Jingle session %s, "
+ "ICE stream %d, component %d",
+ sess->sid, stream_id, component_id);
+ if (sess->component[component_id - 1].dtls_state != REXMPP_TLS_INACTIVE) {
+ rexmpp_log(sess->s, LOG_WARNING,
+ "The connection for Jingle session %s and component %d"
+ " was established previously",
+ sess->sid, component_id);
+ return;
+ }
+
+ rexmpp_jingle_component_t *comp = &(sess->component[component_id - 1]);
+ rexmpp_dtls_connect(sess->s,
+ comp->dtls,
+ comp,
+ rexmpp_jingle_dtls_is_active(sess, 0));
+ sess->component[component_id - 1].dtls_state = REXMPP_TLS_HANDSHAKE;
+ rexmpp_tls_handshake(sess->s, comp->dtls);
+ } else if (state == NICE_COMPONENT_STATE_FAILED) {
+ rexmpp_log(sess->s, LOG_ERR,
+ "ICE connection failed for Jingle session %s, ICE stream %d, component %d",
+ sess->sid, stream_id, component_id);
+ /* todo: maybe destroy the session if it failed for all the
+ components */
+ }
+}
+
+void
+rexmpp_jingle_ice_recv_cb (NiceAgent *agent, guint stream_id, guint component_id,
+ guint len, gchar *gbuf, gpointer data)
+{
+ /* Demultiplexing here for DTLS and SRTP:
+ https://datatracker.ietf.org/doc/html/rfc5764#section-5.1.2 */
+ (void)agent;
+ (void)stream_id;
+ (void)component_id;
+ uint8_t *buf = (uint8_t *)gbuf;
+ rexmpp_jingle_component_t *comp = data;
+ if (len == 0) {
+ rexmpp_log(comp->s, LOG_WARNING, "Received an empty ICE message");
+ return;
+ }
+ if (127 < buf[0] && buf[0] < 192) {
+ int err;
+ srtp_ctx_t *srtp_in;
+ if (comp->dtls_state == REXMPP_TLS_ACTIVE) {
+ srtp_in = comp->srtp_in;
+ } else if (comp->session->component[0].dtls_state == REXMPP_TLS_ACTIVE) {
+ /* Allow to reuse the first component's DTLS handshake/SRTP
+ session. */
+ srtp_in = comp->session->component[0].srtp_in;
+ } else {
+ rexmpp_log(comp->s, LOG_WARNING,
+ "Received an SRTP packet while DTLS is inactive");
+ return;
+ }
+ if (srtp_in == NULL) {
+ rexmpp_log(comp->s, LOG_WARNING,
+ "Received an SRTP packet while SRTP is not set up");
+ return;
+ }
+ if (component_id == 1) {
+ err = srtp_unprotect(srtp_in, buf, (int*)&len);
+ if (err == srtp_err_status_auth_fail && comp->session->rtcp_mux) {
+ /* Try to demultiplex. Maybe there's a better way to do it,
+ but this will do for now. */
+ err = srtp_unprotect_rtcp(srtp_in, buf, (int*)&len);
+ }
+ } else {
+ err = srtp_unprotect_rtcp(srtp_in, buf, (int*)&len);
+ }
+ if (err) {
+ rexmpp_log(comp->s, LOG_ERR, "SRT(C)P unprotect error %d on component %d",
+ err, component_id);
+ } else {
+ /* TODO: apparently sometimes there is more than one RTCP packet
+ in the decrypted data; parse them all. */
+ uint8_t version = (buf[0] & 0xc0) >> 6;
+ if (version == 2 && len >= 12) {
+ size_t i;
+ uint8_t payload_type = buf[1] & 0x7f;
+ uint8_t csrc_count = buf[0] & 0xF;
+ uint16_t seq_num = ((uint16_t)buf[2] << 8) | buf[3];
+ int data_start_pos = 12 + csrc_count * 4;
+
+ /* Exclude possible RTCP packets here, RFC 5761 */
+ if (component_id == 1
+ && (payload_type < 64 || payload_type > 80)) {
+ if ((seq_num > comp->session->rtp_last_seq_num)
+ || (comp->session->rtp_last_seq_num > 65500)) {
+ if ((comp->session->rtp_last_seq_num <= 65500)
+ && (seq_num - comp->session->rtp_last_seq_num > 1)) {
+ rexmpp_log(comp->s, LOG_NOTICE,
+ "%d RTP packets lost",
+ seq_num - comp->session->rtp_last_seq_num - 1);
+ }
+ struct ring_buf *playback = &(comp->session->ring_buffers.playback);
+
+ /* Skip the RTP header. */
+ if (payload_type == 0) {
+ /* PCMU */
+ for (i = data_start_pos; i < len; i++) {
+ playback->buf[playback->write_pos] = rexmpp_pcmu_decode(buf[i]);
+ playback->write_pos++;
+ playback->write_pos %= PA_BUF_SIZE;
+ }
+ } else if (payload_type == 8) {
+ /* PCMA */
+ for (i = data_start_pos; i < len; i++) {
+ playback->buf[playback->write_pos] = rexmpp_pcma_decode(buf[i]);
+ playback->write_pos++;
+ playback->write_pos %= PA_BUF_SIZE;
+ }
+ }
+#ifdef HAVE_OPUS
+ else if (payload_type == comp->session->payload_type
+ && comp->session->codec == REXMPP_CODEC_OPUS) {
+ /* The same payload type as for output, which is Opus */
+ opus_int16 decoded[5760 * 2];
+ int decoded_len;
+ decoded_len =
+ opus_decode(comp->session->opus_dec,
+ (const unsigned char *)buf + data_start_pos,
+ len - data_start_pos,
+ decoded,
+ 5760,
+ 0);
+ int j;
+ for (j = 0; j < decoded_len; j++) {
+ playback->buf[playback->write_pos] = decoded[j];
+ playback->write_pos++;
+ playback->write_pos %= PA_BUF_SIZE;
+ }
+ }
+#endif /* HAVE_OPUS */
+ else {
+ /* Some other payload type, possibly with a dynamic ID */
+ rexmpp_xml_t *payload =
+ rexmpp_jingle_session_payload_by_id(comp->session,
+ payload_type);
+ const char *pl_name = rexmpp_xml_find_attr_val(payload, "name");
+ rexmpp_log(comp->s, LOG_WARNING,
+ "Unhandled payload type %d, '%s'",
+ payload_type, pl_name);
+ }
+ comp->session->rtp_last_seq_num = seq_num;
+ } else {
+ rexmpp_log(comp->s, LOG_NOTICE,
+ "Out of order RTP packets received: %d after %d",
+ seq_num, comp->session->rtp_last_seq_num);
+ }
+ } else {
+ uint8_t packet_type = buf[1];
+ unsigned int pos = 0;
+ unsigned int rtcp_len = (buf[2] << 8) | buf[3];
+ if (packet_type == 200 && len >= 28) {
+ uint32_t rtcp_ssrc =
+ (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
+ uint64_t rtcp_ntp_timestamp = 0;
+ for (i = 8; i < 16; i++) {
+ rtcp_ntp_timestamp <<= 8;
+ rtcp_ntp_timestamp |= buf[i];
+ }
+ uint32_t rtcp_rtp_timestamp =
+ (buf[16] << 24) | (buf[17] << 16) | (buf[18] << 8) | buf[19];
+ uint32_t rtcp_packet_count =
+ (buf[20] << 24) | (buf[21] << 16) | (buf[22] << 8) | buf[23];
+ uint32_t rtcp_octet_count =
+ (buf[24] << 24) | (buf[25] << 16) | (buf[26] << 8) | buf[27];
+ rexmpp_log(comp->s, LOG_DEBUG,
+ "RTCP SR received: SSRC % " PRIx32 ", NTP TS %" PRIu64
+ ", RTP TS %" PRIu32 ", PC %" PRIu32 ", OC %" PRIu32,
+ rtcp_ssrc, rtcp_ntp_timestamp, rtcp_rtp_timestamp,
+ rtcp_packet_count, rtcp_octet_count);
+ pos = 28;
+ } else if (packet_type == 201 && len >= 8) {
+ uint32_t rtcp_ssrc =
+ (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
+ rexmpp_log(comp->s, LOG_DEBUG,
+ "RTCP RR received: SSRC %x",
+ rtcp_ssrc);
+ pos = 8;
+ } else {
+ rexmpp_log(comp->s, LOG_DEBUG, "RTCP packet received, PT %d",
+ packet_type);
+ }
+
+ while ((pos > 0)
+ && ((pos + 24) <= len)
+ && ((pos + 24) <= (rtcp_len + 1) * 4)) {
+ uint32_t rtcp_ssrc = (buf[pos] << 24) | (buf[pos + 1] << 16)
+ | (buf[pos + 2] << 8) | buf[pos + 3];
+ uint8_t fraction_lost = buf[pos + 4];
+ uint32_t packets_lost =
+ (buf[pos + 5] << 24) | (buf[pos + 6] << 16) | buf[pos + 7];
+ uint32_t rtcp_ehsn = (buf[pos + 8] << 24) | (buf[pos + 9] << 16)
+ | (buf[pos + 10] << 8) | buf[pos + 11];
+ uint32_t rtcp_jitter = (buf[pos + 12] << 24) | (buf[pos + 13] << 16)
+ | (buf[pos + 14] << 8) | buf[pos + 15];
+ uint32_t rtcp_lsr = (buf[pos + 16] << 24) | (buf[pos + 17] << 16)
+ | (buf[pos + 18] << 8) | buf[pos + 19];
+ uint32_t rtcp_dlsr = (buf[pos + 20] << 24) | (buf[pos + 21] << 16)
+ | (buf[pos + 22] << 8) | buf[pos + 23];
+ rexmpp_log(comp->s, LOG_DEBUG,
+ "RTCP report block: SSRC % " PRIx32 ", lost %" PRIu32
+ "%, %" PRIu8 " packets, "
+ "highest seq num %" PRIu32
+ ", jitter %" PRIu32 ", LSR %" PRIu32 ", DLSR %" PRIu32,
+ rtcp_ssrc, fraction_lost, packets_lost,
+ rtcp_ehsn, rtcp_jitter, rtcp_lsr, rtcp_dlsr);
+ pos += 24;
+ }
+ }
+ } else {
+ rexmpp_log(comp->s, LOG_WARNING,
+ "Unhandled RT(C)P packet: version %d, length %d",
+ version, len);
+ }
+ }
+ } else {
+ rexmpp_dtls_feed(comp->s, comp->dtls, buf, len);
+ }
+}
+
+int
+rexmpp_jingle_ice_agent_init (rexmpp_jingle_session_t *sess)
+{
+ sess->ice_agent = nice_agent_new(g_main_loop_get_context (sess->s->jingle->gloop),
+ NICE_COMPATIBILITY_RFC5245);
+ if (sess->s->local_address != NULL) {
+ NiceAddress *address = nice_address_new();
+ nice_address_set_from_string(address, sess->s->local_address);
+ nice_agent_add_local_address(sess->ice_agent, address);
+ nice_address_free(address);
+ }
+ g_object_set(sess->ice_agent, "controlling-mode", sess->initiator, NULL);
+ g_signal_connect(sess->ice_agent, "candidate-gathering-done",
+ G_CALLBACK(rexmpp_jingle_candidate_gathering_done_cb), sess);
+ g_signal_connect(sess->ice_agent, "component-state-changed",
+ G_CALLBACK(rexmpp_jingle_component_state_changed_cb), sess);
+
+ sess->ice_stream_id = nice_agent_add_stream(sess->ice_agent, sess->rtcp_mux ? 1 : 2);
+ if (sess->ice_stream_id == 0) {
+ rexmpp_log(sess->s, LOG_ERR, "Failed to add an ICE agent stream");
+ g_object_unref(sess->ice_agent);
+ sess->ice_agent = NULL;
+ return 0;
+ }
+
+ int i;
+ for (i = 0; i < (sess->rtcp_mux ? 1 : 2); i++) {
+ nice_agent_attach_recv(sess->ice_agent, sess->ice_stream_id, i + 1,
+ g_main_loop_get_context (sess->s->jingle->gloop),
+ rexmpp_jingle_ice_recv_cb,
+ &sess->component[i]);
+ }
+
+ return 1;
+}
+
+void rexmpp_jingle_turn_dns_cb (rexmpp_t *s, void *ptr, rexmpp_dns_result_t *result) {
+ rexmpp_jingle_session_t *sess = ptr;
+ if (result != NULL && result->data != NULL) {
+ /* Only using the first address. */
+ struct in_addr addr;
+ memcpy(&addr,
+ result->data[0],
+ result->len[0]);
+ char *ip = inet_ntoa(addr);
+ rexmpp_log(s, LOG_DEBUG, "Resolved TURN server's hostname to %s (%ssecure)",
+ ip, result->secure ? "" : "in");
+ /* Adding it just for the first component for now. */
+ nice_agent_set_relay_info(sess->ice_agent, sess->ice_stream_id, 1,
+ ip, sess->turn_port,
+ sess->turn_username, sess->turn_password,
+ NICE_RELAY_TYPE_TURN_UDP);
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Failed to resolve TURN server's address");
+ }
+ nice_agent_gather_candidates(sess->ice_agent, sess->ice_stream_id);
+ rexmpp_dns_result_free(result);
+}
+
+void rexmpp_jingle_stun_dns_cb (rexmpp_t *s, void *ptr, rexmpp_dns_result_t *result) {
+ rexmpp_jingle_session_t *sess = ptr;
+ if (result != NULL && result->data != NULL) {
+ /* Only using the first address. */
+ struct in_addr addr;
+ memcpy(&addr,
+ result->data[0],
+ result->len[0]);
+ char *ip = inet_ntoa(addr);
+ rexmpp_log(s, LOG_DEBUG, "Resolved STUN server's hostname to %s (%ssecure)",
+ ip, result->secure ? "" : "in");
+ g_object_set(sess->ice_agent, "stun-server", ip, NULL);
+ g_object_set(sess->ice_agent, "stun-server-port", sess->stun_port, NULL);
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Failed to resolve STUN server's address");
+ }
+
+ /* Proceed to TURN resolution if there's a TURN host, or just start
+ connecting. */
+ if (sess->turn_host != NULL) {
+ rexmpp_dns_resolve(s, sess->turn_host, 1, 1, sess, rexmpp_jingle_turn_dns_cb);
+ } else {
+ nice_agent_gather_candidates(sess->ice_agent, sess->ice_stream_id);
+ }
+ rexmpp_dns_result_free(result);
+}
+
+void rexmpp_jingle_turn_cb (rexmpp_t *s,
+ void *sess_ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)req;
+ rexmpp_jingle_session_t *sess = sess_ptr;
+ if (success) {
+ /* use credentials */
+ rexmpp_xml_t *services = rexmpp_xml_first_elem_child(response);
+ if (rexmpp_xml_match(services, "urn:xmpp:extdisco:2", "services")) {
+ rexmpp_xml_t *service = rexmpp_xml_first_elem_child(services);
+ while (service != NULL) {
+ if (rexmpp_xml_match(service, "urn:xmpp:extdisco:2", "service")) {
+ const char *type = rexmpp_xml_find_attr_val(service, "type");
+ const char *transport = rexmpp_xml_find_attr_val(service, "transport");
+ const char *host = rexmpp_xml_find_attr_val(service, "host");
+ const char *port = rexmpp_xml_find_attr_val(service, "port");
+ const char *username = rexmpp_xml_find_attr_val(service, "username");
+ const char *password = rexmpp_xml_find_attr_val(service, "password");
+
+ if (sess->stun_host == NULL &&
+ type != NULL && transport != NULL && host != NULL && port != NULL &&
+ strcmp(type, "stun") == 0 && strcmp(transport, "udp") == 0) {
+ sess->stun_host = strdup(host);
+ sess->stun_port = atoi(port);
+ rexmpp_log(s, LOG_DEBUG, "Setting STUN server to %s:%s", host, port);
+ }
+
+ if (sess->turn_host == NULL &&
+ type != NULL && transport != NULL && host != NULL && port != NULL &&
+ username != NULL && password != NULL &&
+ strcmp(type, "turn") == 0 && strcmp(transport, "udp") == 0) {
+ sess->turn_host = strdup(host);
+ sess->turn_port = atoi(port);
+ sess->turn_username = strdup(username);
+ sess->turn_password = strdup(password);
+ rexmpp_log(s, LOG_DEBUG, "Setting TURN server to %s:%s", host, port);
+ }
+ }
+ service = rexmpp_xml_next_elem_sibling(service);
+ }
+ if (sess->stun_host != NULL) {
+ /* Resolve, then resolve STUN host, then connect. */
+ rexmpp_dns_resolve(s, sess->turn_host, 1, 1, sess, rexmpp_jingle_stun_dns_cb);
+ return;
+ } else if (sess->stun_host != NULL) {
+ /* Resolve, then connect. */
+ /* todo: handle IPv6 too, but that's awkward enough for now to
+ deal with resolution before calling the library. And
+ hopefully IPv6 will make all this NAT traversal business
+ unnecessary anyway. */
+ rexmpp_dns_resolve(s, sess->stun_host, 1, 1, sess, rexmpp_jingle_turn_dns_cb);
+ return;
+ } else {
+ rexmpp_log(s, LOG_DEBUG, "No STUN or TURN servers found");
+ }
+ }
+ } else {
+ rexmpp_log(s, LOG_DEBUG,
+ "Failed to request TURN credentials, "
+ "trying to connect without STUN/TURN");
+ }
+ nice_agent_gather_candidates(sess->ice_agent, sess->ice_stream_id);
+}
+
+void rexmpp_jingle_discover_turn_cb (rexmpp_t *s,
+ void *sess_ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)req;
+ const char *response_from = rexmpp_xml_find_attr_val(response, "from");
+ rexmpp_jingle_session_t *sess = sess_ptr;
+ if (success) {
+ rexmpp_xml_t *services =
+ rexmpp_xml_new_elem("services", "urn:xmpp:extdisco:2");
+ rexmpp_xml_add_attr(services, "type", "turn");
+ rexmpp_iq_new(s, "get", response_from, services,
+ rexmpp_jingle_turn_cb, sess_ptr);
+ } else {
+ rexmpp_log(s, LOG_DEBUG,
+ "No external service discovery, trying to connect without STUN/TURN");
+ nice_agent_gather_candidates(sess->ice_agent, sess->ice_stream_id);
+ }
+}
+
+void rexmpp_jingle_discover_turn (rexmpp_t *s, rexmpp_jingle_session_t *sess) {
+ rexmpp_disco_find_feature(s, s->initial_jid.domain, "urn:xmpp:extdisco:2",
+ rexmpp_jingle_discover_turn_cb, sess, 0, 1);
+}
+
+rexmpp_err_t
+rexmpp_jingle_call (rexmpp_t *s,
+ const char *jid)
+{
+ rexmpp_jingle_session_t *sess =
+ rexmpp_jingle_session_create(s, strdup(jid), rexmpp_random_id(),
+ REXMPP_JINGLE_SESSION_MEDIA, 1);
+ if (sess != NULL) {
+ rexmpp_jingle_ice_agent_init(sess);
+ rexmpp_jingle_discover_turn(s, sess);
+ return REXMPP_SUCCESS;
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to create a Jingle session for a call");
+ return REXMPP_E_OTHER;
+ }
+}
+
+rexmpp_err_t
+rexmpp_jingle_call_accept (rexmpp_t *s,
+ const char *sid)
+{
+ rexmpp_jingle_session_t *sess = rexmpp_jingle_session_by_id(s, sid);
+ if (sess == NULL) {
+ return REXMPP_E_OTHER;
+ }
+ rexmpp_jingle_ice_agent_init(sess);
+
+ rexmpp_xml_t *content =
+ rexmpp_xml_find_child(sess->initiate,
+ "urn:xmpp:jingle:1",
+ "content");
+ rexmpp_xml_t * ice_udp_transport =
+ rexmpp_xml_find_child(content,
+ "urn:xmpp:jingle:transports:ice-udp:1",
+ "transport");
+ if (ice_udp_transport == NULL) {
+ rexmpp_log(s, LOG_ERR, "No ICE-UDP transport defined for session %s", sid);
+ rexmpp_jingle_session_terminate
+ (s, sid,
+ rexmpp_xml_new_elem("unsupported-transports", "urn:xmpp:jingle:1"),
+ "No ICE-UDP transport defined");
+ return REXMPP_E_OTHER;
+ }
+ rexmpp_jingle_ice_udp_add_remote(sess, ice_udp_transport);
+ rexmpp_jingle_discover_turn(s, sess);
+ return REXMPP_SUCCESS;
+}
+#else /* ENABLE_CALLS */
+
+ssize_t
+rexmpp_jingle_dtls_push_func (void *p, const void *data, size_t size)
+{
+ (void)p;
+ (void)data;
+ (void)size;
+ return -1;
+}
+
+rexmpp_err_t
+rexmpp_jingle_call (rexmpp_t *s,
+ const char *jid)
+{
+ (void)jid;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without support for media calls");
+ return REXMPP_E_OTHER;
+}
+
+rexmpp_err_t
+rexmpp_jingle_call_accept (rexmpp_t *s,
+ const char *sid)
+{
+ (void)sid;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without support for media calls");
+ return REXMPP_E_OTHER;
+}
+#endif /* ENABLE_CALLS */
+
+int rexmpp_jingle_iq (rexmpp_t *s, rexmpp_xml_t *elem) {
+ int handled = 0;
+ if (! s->enable_jingle) {
+ return handled;
+ }
+ rexmpp_xml_t *jingle =
+ rexmpp_xml_find_child(elem, "urn:xmpp:jingle:1", "jingle");
+ if (jingle != NULL) {
+ handled = 1;
+ const char *action = rexmpp_xml_find_attr_val(jingle, "action");
+ const char *sid = rexmpp_xml_find_attr_val(jingle, "sid");
+ const char *from_jid = rexmpp_xml_find_attr_val(elem, "from");
+ if (action != NULL && sid != NULL && from_jid != NULL) {
+ if (strcmp(action, "session-initiate") == 0) {
+ /* todo: could be more than one content element, handle that */
+ rexmpp_xml_t *content =
+ rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content");
+ if (content == NULL) {
+ rexmpp_iq_reply(s, elem, "error",
+ rexmpp_xml_error("cancel", "bad-request"));
+ } else {
+ rexmpp_iq_reply(s, elem, "result", NULL);
+
+ rexmpp_xml_t *file_description =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:apps:file-transfer:5",
+ "description");
+ rexmpp_xml_t *ibb_transport =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ibb:1",
+ "transport");
+ rexmpp_xml_t *ice_udp_transport =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ice-udp:1",
+ "transport");
+ rexmpp_xml_t *rtp_description =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:apps:rtp:1",
+ "description");
+
+ if (file_description != NULL && ibb_transport != NULL) {
+ const char *ibb_sid = rexmpp_xml_find_attr_val(ibb_transport, "sid");
+ if (ibb_sid != NULL) {
+ rexmpp_log(s, LOG_DEBUG,
+ "Jingle session-initiate from %s, sid %s, ibb sid %s",
+ from_jid, sid, ibb_sid);
+ rexmpp_jingle_session_t *sess =
+ rexmpp_jingle_session_create(s, strdup(from_jid), strdup(sid),
+ REXMPP_JINGLE_SESSION_FILE, 0);
+ if (sess != NULL) {
+ sess->initiate = rexmpp_xml_clone(jingle);
+ sess->ibb_sid = strdup(ibb_sid);
+ } else {
+ rexmpp_jingle_session_terminate
+ (s, sid,
+ rexmpp_xml_new_elem("failed-transport",
+ "urn:xmpp:jingle:1"),
+ NULL);
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR,
+ "Jingle IBB transport doesn't have a sid attribute");
+ rexmpp_jingle_session_terminate
+ (s, sid,
+ rexmpp_xml_new_elem("unsupported-transports",
+ "urn:xmpp:jingle:1"),
+ NULL);
+ }
+#ifdef ENABLE_CALLS
+ } else if (ice_udp_transport != NULL && rtp_description != NULL) {
+ rexmpp_log(s, LOG_DEBUG, "Jingle session-initiate from %s, sid %s",
+ from_jid, sid);
+ rexmpp_jingle_session_t *sess =
+ rexmpp_jingle_session_create(s, strdup(from_jid), strdup(sid),
+ REXMPP_JINGLE_SESSION_MEDIA, 0);
+ if (sess != NULL) {
+ sess->rtcp_mux =
+ (rexmpp_xml_find_child(rtp_description,
+ "urn:xmpp:jingle:apps:rtp:1",
+ "rtcp-mux") != NULL);
+ sess->initiate = rexmpp_xml_clone(jingle);
+ }
+#endif /* ENABLE_CALLS */
+ } else if (file_description == NULL &&
+ rtp_description == NULL) {
+ rexmpp_jingle_session_terminate
+ (s, sid,
+ rexmpp_xml_new_elem("unsupported-applications",
+ "urn:xmpp:jingle:1"),
+ NULL);
+ } else if (ibb_transport == NULL &&
+ ice_udp_transport == NULL) {
+ rexmpp_jingle_session_terminate
+ (s, sid,
+ rexmpp_xml_new_elem("unsupported-transports",
+ "urn:xmpp:jingle:1"),
+ NULL);
+ } else {
+ /* todo: some other error */
+ }
+ }
+ } else if (strcmp(action, "session-terminate") == 0) {
+ /* todo: check/log the reason */
+ rexmpp_jingle_session_delete_by_id(s, sid);
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ } else if (strcmp(action, "session-accept") == 0) {
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid);
+ if (session != NULL) {
+ session->accept = rexmpp_xml_clone(jingle);
+ rexmpp_xml_t *content =
+ rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content");
+ rexmpp_xml_t *file_description =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:apps:file-transfer:5",
+ "description");
+ rexmpp_xml_t *ibb_transport =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ibb:1",
+ "transport");
+ if (ibb_transport != NULL && file_description != NULL) {
+ rexmpp_xml_t *open =
+ rexmpp_xml_new_elem("open", "http://jabber.org/protocol/ibb");
+ rexmpp_xml_add_attr(open, "sid", session->ibb_sid);
+ rexmpp_xml_add_attr(open, "block-size", "4096");
+ rexmpp_xml_add_attr(open, "stanza", "iq");
+ rexmpp_iq_new(s, "set", session->jid, open,
+ rexmpp_jingle_ibb_send_cb, strdup(sid));
+ } else {
+#ifdef ENABLE_CALLS
+ rexmpp_jingle_session_configure_audio(session);
+ rexmpp_jingle_run_audio(session);
+ rexmpp_xml_t *ice_udp_transport =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ice-udp:1",
+ "transport");
+ if (ice_udp_transport != NULL) {
+ rexmpp_jingle_ice_udp_add_remote(session, ice_udp_transport);
+ } else {
+ rexmpp_log(s, LOG_WARNING,
+ "ICE-UDP transport is unset in session-accept");
+ }
+#endif /* ENABLE_CALLS */
+ }
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Jingle session %s is not found", sid);
+ }
+ } else if (strcmp(action, "transport-info") == 0) {
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid);
+ if (session != NULL) {
+#ifdef ENABLE_CALLS
+ rexmpp_xml_t *content =
+ rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content");
+ rexmpp_xml_t *ice_udp_transport =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ice-udp:1",
+ "transport");
+ if (ice_udp_transport != NULL) {
+ rexmpp_jingle_ice_udp_add_remote(session, ice_udp_transport);
+ }
+#endif /* ENABLE_CALLS */
+ }
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Unknown Jingle action: %s", action);
+ rexmpp_iq_reply(s, elem, "error",
+ rexmpp_xml_error("cancel", "bad-request"));
+ }
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Received a malformed Jingle element");
+ rexmpp_iq_reply(s, elem, "error",
+ rexmpp_xml_error("cancel", "bad-request"));
+ }
+ }
+
+ /* XEP-0261: Jingle In-Band Bytestreams Transport Method */
+ rexmpp_xml_t *ibb_open =
+ rexmpp_xml_find_child(elem, "http://jabber.org/protocol/ibb", "open");
+ if (ibb_open != NULL) {
+ handled = 1;
+ /* no-op, though could check sid here. */
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ }
+ rexmpp_xml_t *ibb_close =
+ rexmpp_xml_find_child(elem, "http://jabber.org/protocol/ibb", "close");
+ if (ibb_close != NULL) {
+ handled = 1;
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ const char *sid = rexmpp_xml_find_attr_val(ibb_close, "sid");
+
+ if (sid != NULL) {
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_ibb_sid(s, sid);
+ if (session != NULL) {
+ rexmpp_jingle_session_terminate
+ (s, session->sid,
+ rexmpp_xml_new_elem("success", "urn:xmpp:jingle:1"), NULL);
+ }
+ }
+ }
+ rexmpp_xml_t *ibb_data =
+ rexmpp_xml_find_child(elem, "http://jabber.org/protocol/ibb", "data");
+ if (ibb_data != NULL) {
+ handled = 1;
+ const char *sid = rexmpp_xml_find_attr_val(ibb_data, "sid");
+ if (sid != NULL) {
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_ibb_sid(s, sid);
+ if (session != NULL && session->ibb_fh != NULL) {
+ char *data = NULL;
+ const char *data_base64 = rexmpp_xml_text_child(ibb_data);
+ if (data_base64 != NULL) {
+ size_t data_len = 0;
+ int base64_err = rexmpp_base64_from(data_base64, strlen(data_base64),
+ &data, &data_len);
+ if (base64_err != 0) {
+ rexmpp_log(s, LOG_ERR, "Base-64 decoding failure");
+ } else {
+ size_t written = fwrite(data, 1, data_len, session->ibb_fh);
+ if (written != data_len) {
+ rexmpp_log(s, LOG_ERR, "Wrote %d bytes, expected %d",
+ written, data_len);
+ /* todo: maybe introduce buffering, or make it an error */
+ }
+ }
+ }
+ }
+ }
+ /* todo: report errors */
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ }
+ return handled;
+}
+
+int rexmpp_jingle_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
+ int nfds = -1;
+#ifdef ENABLE_CALLS
+ gint poll_timeout;
+ GPollFD poll_fds[10];
+ GMainContext* gctx = g_main_loop_get_context(s->jingle->gloop);
+ if (g_main_context_acquire(gctx)) {
+ gint poll_fds_n = g_main_context_query(gctx,
+ G_PRIORITY_HIGH,
+ &poll_timeout,
+ (GPollFD *)&poll_fds,
+ 10);
+ g_main_context_release(gctx);
+ int i;
+ for (i = 0; i < poll_fds_n; i++) {
+ if (poll_fds[i].events & (G_IO_IN | G_IO_HUP | G_IO_ERR)) {
+ FD_SET(poll_fds[i].fd, read_fds);
+ }
+ if (poll_fds[i].events & (G_IO_OUT | G_IO_ERR)) {
+ FD_SET(poll_fds[i].fd, write_fds);
+ }
+ if (poll_fds[i].fd > nfds) {
+ nfds = poll_fds[i].fd;
+ }
+ }
+
+ rexmpp_jingle_session_t *sess;
+ for (sess = s->jingle->sessions; sess != NULL; sess = sess->next) {
+ for (i = 0; i < 2; i++) {
+ if (sess->component[i].dtls_state != REXMPP_TLS_INACTIVE &&
+ sess->component[i].dtls_state != REXMPP_TLS_CLOSED &&
+ sess->component[i].dtls_state != REXMPP_TLS_ERROR) {
+ GPtrArray *sockets =
+ nice_agent_get_sockets(sess->ice_agent,
+ sess->ice_stream_id, i + 1);
+ guint i;
+ for (i = 0; i < sockets->len; i++) {
+ int fd = g_socket_get_fd(sockets->pdata[i]);
+ FD_SET(fd, read_fds);
+ if (fd > nfds) {
+ nfds = fd;
+ }
+ }
+ g_ptr_array_unref(sockets);
+ }
+ }
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to acquire GMainContext in rexmpp_jingle_fds");
+ }
+#else /* ENABLE_CALLS */
+ (void)s;
+ (void)read_fds;
+ (void)write_fds;
+#endif /* ENABLE_CALLS */
+ return (nfds + 1);
+}
+
+struct timespec * rexmpp_jingle_timeout (rexmpp_t *s,
+ struct timespec *max_tv,
+ struct timespec *tv) {
+#ifdef ENABLE_CALLS
+ gint poll_timeout;
+ GPollFD poll_fds[10];
+ GMainContext* gctx = g_main_loop_get_context(s->jingle->gloop);
+ if (g_main_context_acquire(gctx)) {
+ g_main_context_query(gctx,
+ G_PRIORITY_HIGH,
+ &poll_timeout,
+ (GPollFD *)&poll_fds,
+ 10);
+ g_main_context_release(gctx);
+
+ rexmpp_jingle_session_t *sess;
+ for (sess = s->jingle->sessions; sess != NULL; sess = sess->next) {
+ int i;
+ for (i = 0; i < 2; i++) {
+ if (sess->component[i].dtls_state != REXMPP_TLS_INACTIVE &&
+ sess->component[i].dtls_state != REXMPP_TLS_CLOSED &&
+ sess->component[i].dtls_state != REXMPP_TLS_ERROR) {
+ int tms = rexmpp_dtls_timeout(sess->s, sess->component[i].dtls);
+ if (tms > 0 && (poll_timeout < 0 || tms < poll_timeout)) {
+ poll_timeout = tms;
+ }
+ /* Set poll timeout to at most 5 ms if there are connected
+ components, for timely transmission of Jingle data. */
+ if (sess->component[i].dtls_state == REXMPP_TLS_ACTIVE &&
+ (poll_timeout < 0 || poll_timeout > 5)) {
+ poll_timeout = 5;
+ }
+ }
+ }
+ }
+
+ if (poll_timeout >= 0) {
+ int sec = poll_timeout / 1000;
+ int nsec = (poll_timeout % 1000) * 1000000;
+ if (max_tv == NULL ||
+ (max_tv->tv_sec > sec ||
+ (max_tv->tv_sec == sec && max_tv->tv_nsec > nsec))) {
+ tv->tv_sec = sec;
+ tv->tv_nsec = nsec;
+ max_tv = tv;
+ }
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to acquire GMainContext in rexmpp_jingle_timeout");
+ }
+#else /* ENABLE_CALLS */
+ (void)s;
+ (void)tv;
+#endif /* ENABLE_CALLS */
+ return max_tv;
+}
+
+rexmpp_err_t
+rexmpp_jingle_run (rexmpp_t *s,
+ fd_set *read_fds,
+ fd_set *write_fds)
+{
+ (void)write_fds;
+ (void)read_fds;
+#ifdef ENABLE_CALLS
+ rexmpp_jingle_session_t *sess;
+ int err;
+ unsigned char key_mat[2 * (SRTP_AES_ICM_128_KEY_LEN_WSALT)],
+ client_sess_key[SRTP_AES_ICM_128_KEY_LEN_WSALT],
+ server_sess_key[SRTP_AES_ICM_128_KEY_LEN_WSALT];
+ for (sess = s->jingle->sessions; sess != NULL; sess = sess->next) {
+ char input[4096 + SRTP_MAX_TRAILER_LEN];
+ int input_len;
+ int comp_id;
+
+ for (comp_id = 0; comp_id < 2; comp_id++) {
+ rexmpp_jingle_component_t *comp = &sess->component[comp_id];
+
+ if (comp->dtls_state == REXMPP_TLS_HANDSHAKE) {
+ int ret = rexmpp_tls_handshake(s, comp->dtls);
+ if (ret == REXMPP_TLS_SUCCESS) {
+ rexmpp_log(s, LOG_DEBUG,
+ "DTLS connected for Jingle session %s, component %d",
+ sess->sid, comp->component_id);
+ comp->dtls_state = REXMPP_TLS_ACTIVE;
+
+ rexmpp_xml_t *jingle = comp->session->initiator
+ ? comp->session->accept
+ : comp->session->initiate;
+ rexmpp_xml_t *fingerprint =
+ rexmpp_xml_find_child
+ (rexmpp_xml_find_child
+ (rexmpp_xml_find_child
+ (jingle, "urn:xmpp:jingle:1", "content"),
+ "urn:xmpp:jingle:transports:ice-udp:1", "transport"),
+ "urn:xmpp:jingle:apps:dtls:0", "fingerprint");
+ if (fingerprint == NULL) {
+ /* todo: might be neater to check it upon receiving the
+ stanzas, instead of checking it here */
+ rexmpp_log(comp->s, LOG_ERR,
+ "No fingerprint in the peer's Jingle element");
+ rexmpp_jingle_session_terminate
+ (s, sess->sid,
+ rexmpp_xml_new_elem("connectivity-error", "urn:xmpp:jingle:1"),
+ "No fingerprint element");
+ return REXMPP_E_TLS;
+ } else {
+ const char *hash_str = rexmpp_xml_find_attr_val(fingerprint, "hash");
+ if (hash_str == NULL) {
+ rexmpp_log(comp->s, LOG_ERR,
+ "No hash attribute in the peer's fingerprint element");
+ rexmpp_jingle_session_terminate
+ (s, sess->sid,
+ rexmpp_xml_new_elem("connectivity-error", "urn:xmpp:jingle:1"),
+ "No hash attribute in the fingerprint element");
+ return REXMPP_E_TLS;
+ } else {
+ char fp[64], fp_str[64 * 3];
+ size_t fp_size = 64;
+ if (rexmpp_tls_peer_fp(comp->s, comp->dtls, hash_str,
+ fp, fp_str, &fp_size))
+ {
+ rexmpp_jingle_session_terminate
+ (s, sess->sid,
+ rexmpp_xml_new_elem("connectivity-error",
+ "urn:xmpp:jingle:1"),
+ "Failed to obtain the DTLS certificate fingerprint");
+ return REXMPP_E_TLS;
+ } else {
+ const char *fingerprint_cont =
+ rexmpp_xml_text_child(fingerprint);
+ /* Fingerprint string should be uppercase, but
+ allowing any case for now, while Dino uses
+ lowercase. */
+ int fingerprint_mismatch = strcasecmp(fingerprint_cont, fp_str);
+ if (fingerprint_mismatch) {
+ rexmpp_log(comp->s, LOG_ERR,
+ "Peer's fingerprint mismatch: expected %s,"
+ " calculated %s",
+ fingerprint_cont, fp_str);
+ rexmpp_jingle_session_terminate
+ (s, sess->sid,
+ rexmpp_xml_new_elem("security-error", "urn:xmpp:jingle:1"),
+ "DTLS certificate fingerprint mismatch");
+ return REXMPP_E_TLS;
+ } else {
+ /* The fingerprint is fine, proceed to SRTP. */
+ rexmpp_log(comp->s, LOG_DEBUG,
+ "Peer's fingerprint: %s", fp_str);
+
+ rexmpp_tls_srtp_get_keys(s, comp->dtls,
+ SRTP_AES_128_KEY_LEN, SRTP_SALT_LEN,
+ key_mat);
+ /* client key */
+ memcpy(client_sess_key, key_mat, SRTP_AES_128_KEY_LEN);
+ /* server key */
+ memcpy(server_sess_key, key_mat + SRTP_AES_128_KEY_LEN,
+ SRTP_AES_128_KEY_LEN);
+ /* client salt */
+ memcpy(client_sess_key + SRTP_AES_128_KEY_LEN,
+ key_mat + SRTP_AES_128_KEY_LEN * 2,
+ SRTP_SALT_LEN);
+ /* server salt */
+ memcpy(server_sess_key + SRTP_AES_128_KEY_LEN,
+ key_mat + SRTP_AES_128_KEY_LEN * 2 + SRTP_SALT_LEN,
+ SRTP_SALT_LEN);
+
+ int active_role = rexmpp_jingle_dtls_is_active(sess, 0);
+
+ srtp_policy_t inbound;
+ memset(&inbound, 0x0, sizeof(srtp_policy_t));
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtp);
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtcp);
+ inbound.ssrc.type = ssrc_any_inbound;
+ inbound.key = active_role ? server_sess_key : client_sess_key;
+ inbound.window_size = 1024;
+ inbound.allow_repeat_tx = 1;
+ inbound.next = NULL;
+ err = srtp_create(&(comp->srtp_in), &inbound);
+ if (err) {
+ rexmpp_log(s, LOG_ERR, "Failed to create srtp_in");
+ }
+
+ srtp_policy_t outbound;
+ memset(&outbound, 0x0, sizeof(srtp_policy_t));
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtp);
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtcp);
+ outbound.ssrc.type = ssrc_any_outbound;
+ outbound.key = active_role ? client_sess_key : server_sess_key;
+ outbound.window_size = 1024;
+ outbound.allow_repeat_tx = 1;
+ outbound.next = NULL;
+ err = srtp_create(&(comp->srtp_out), &outbound);
+ if (err) {
+ rexmpp_log(s, LOG_ERR, "Failed to create srtp_out");
+ }
+ }
+ }
+ }
+ }
+ } else if (ret != REXMPP_TLS_E_AGAIN) {
+ rexmpp_log(s, LOG_ERR, "DTLS error for session %s, component %d",
+ sess->sid, comp->component_id);
+ comp->dtls_state = REXMPP_TLS_ERROR;
+ if (comp->component_id == 1) {
+ rexmpp_jingle_session_terminate
+ (s, sess->sid,
+ rexmpp_xml_new_elem("connectivity-error", "urn:xmpp:jingle:1"),
+ "DTLS connection error");
+ return REXMPP_E_TLS;
+ }
+ }
+ }
+
+ /* Check on the DTLS session, too. */
+ if (comp->dtls_state == REXMPP_TLS_ACTIVE) {
+ ssize_t received;
+ rexmpp_tls_err_t err =
+ rexmpp_tls_recv(s, comp->dtls, input, 4096, &received);
+ if (err != REXMPP_TLS_SUCCESS && err != REXMPP_TLS_E_AGAIN) {
+ rexmpp_log(s, LOG_ERR,
+ "Error on rexmpp_tls_recv (component id %d), "
+ "terminating Jingle session %s",
+ comp->component_id, sess->sid);
+ rexmpp_jingle_session_terminate
+ (s, sess->sid,
+ rexmpp_xml_new_elem("connectivity-error", "urn:xmpp:jingle:1"),
+ "TLS reading error");
+ return REXMPP_E_TLS;
+ }
+ }
+ }
+
+ /* Send captured audio frames for established sessions. */
+ if ((sess->component[0].dtls_state == REXMPP_TLS_ACTIVE)
+ && (sess->component[0].srtp_out != NULL)
+ && (sess->codec != REXMPP_CODEC_UNDEFINED)) {
+ struct ring_buf *capture = &(sess->ring_buffers.capture);
+ while (capture->write_pos != capture->read_pos) {
+ if (sess->codec == REXMPP_CODEC_PCMU) {
+ for (input_len = 12;
+ input_len < 4096 && capture->write_pos != capture->read_pos;
+ input_len++)
+ {
+ input[input_len] =
+ rexmpp_pcmu_encode(capture->buf[capture->read_pos]);
+ capture->read_pos++;
+ capture->read_pos %= PA_BUF_SIZE;
+ sess->rtp_timestamp++;
+ }
+ } else if (sess->codec == REXMPP_CODEC_PCMA) {
+ for (input_len = 12;
+ input_len < 4096 && capture->write_pos != capture->read_pos;
+ input_len++)
+ {
+ input[input_len] =
+ rexmpp_pcma_encode(capture->buf[capture->read_pos]);
+ capture->read_pos++;
+ capture->read_pos %= PA_BUF_SIZE;
+ sess->rtp_timestamp++;
+ }
+ }
+#ifdef HAVE_OPUS
+ else if (sess->codec == REXMPP_CODEC_OPUS) {
+ unsigned int samples_available;
+ if (capture->write_pos > capture->read_pos) {
+ samples_available = capture->write_pos - capture->read_pos;
+ } else {
+ samples_available =
+ (PA_BUF_SIZE - capture->read_pos) + capture->write_pos;
+ }
+ unsigned int frame_size = 480;
+ if (samples_available >= frame_size * 2) {
+ opus_int16 pcm[4096];
+ /* Prepare a regular buffer */
+ unsigned int i;
+ for (i = 0;
+ (i < frame_size * 2) &&
+ (capture->write_pos != capture->read_pos);
+ i++)
+ {
+ pcm[i] = capture->buf[capture->read_pos];
+ capture->read_pos++;
+ capture->read_pos %= PA_BUF_SIZE;
+ sess->rtp_timestamp++;
+ }
+ /* Encode it */
+ int encoded_len;
+ encoded_len = opus_encode(sess->opus_enc,
+ pcm,
+ frame_size,
+ (unsigned char*)input + 12,
+ 4096);
+ if (encoded_len < 0) {
+ rexmpp_log(s, LOG_ERR, "Failed to encode an Opus frame");
+ break;
+ }
+ input_len = 12 + encoded_len;
+ } else {
+ break;
+ }
+ }
+#endif /* HAVE_OPUS */
+
+ /* Setup an RTP header */
+ uint32_t hl, nl;
+ hl = (2 << 30) /* version */
+ | (0 << 29) /* padding */
+ | (0 << 28) /* extension */
+ | (0 << 24) /* CSRC count */
+ | (0 << 23) /* marker */
+ | (sess->payload_type << 16) /* paylaod type, RFC 3551 */
+ | sess->rtp_seq_num;
+ sess->rtp_seq_num++;
+ nl = htonl(hl);
+ memcpy(input, &nl, sizeof(uint32_t));
+ nl = htonl(sess->rtp_timestamp);
+ memcpy(input + 4, &nl, sizeof(uint32_t));
+ nl = htonl(sess->rtp_ssrc);
+ memcpy(input + 8, &nl, sizeof(uint32_t));
+ /* The RTP header is ready */
+
+ srtp_ctx_t *srtp_out = sess->component[0].srtp_out;
+ err = srtp_protect(srtp_out, input, &input_len);
+ if (err) {
+ rexmpp_log(s, LOG_ERR, "SRTP protect error %d", err);
+ } else {
+ nice_agent_send(sess->ice_agent, sess->ice_stream_id,
+ /* sess->rtcp_mux ? 1 : comp->component_id, */
+ 1,
+ input_len, input);
+ }
+ }
+ }
+ }
+ g_main_context_iteration(g_main_loop_get_context(s->jingle->gloop), 0);
+#else /* ENABLE_CALLS */
+ (void)s;
+ (void)read_fds;
+#endif /* ENABLE_CALLS */
+ return REXMPP_SUCCESS;
+}
diff --git a/src/rexmpp_jingle.h b/src/rexmpp_jingle.h
new file mode 100644
index 0000000..189614a
--- /dev/null
+++ b/src/rexmpp_jingle.h
@@ -0,0 +1,168 @@
+/**
+ @file rexmpp_jingle.h
+ @brief Jingle routines
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+
+*/
+
+
+#ifndef REXMPP_JINGLE_H
+#define REXMPP_JINGLE_H
+
+#include "config.h"
+
+#ifdef ENABLE_CALLS
+#include <glib.h>
+#include <agent.h>
+#include <srtp2/srtp.h>
+#include "portaudio.h"
+#ifdef HAVE_OPUS
+#include <opus/opus.h>
+#endif
+#define PA_BUF_SIZE 0x4000
+#endif
+
+#include "rexmpp.h"
+#include "rexmpp_tls.h"
+
+
+/** @brief Processes incoming Jingle IQs. */
+int rexmpp_jingle_iq (rexmpp_t *s, rexmpp_xml_t *elem);
+
+/** @brief Destroys Jingle sessions. */
+void rexmpp_jingle_stop (rexmpp_t *s);
+
+/** @brief Accepts a file, given a sid and a path to save it to. */
+rexmpp_err_t
+rexmpp_jingle_accept_file_by_id (rexmpp_t *s,
+ const char *sid,
+ const char *path);
+
+/** @brief Sends a file to a given full JID. */
+rexmpp_err_t
+rexmpp_jingle_send_file (rexmpp_t *s,
+ const char *jid,
+ char *path);
+
+/** @brief Terminates a Jingle session. */
+rexmpp_err_t
+rexmpp_jingle_session_terminate (rexmpp_t *s,
+ const char *sid,
+ rexmpp_xml_t *reason_node,
+ const char *reason_text);
+
+typedef struct rexmpp_jingle_component rexmpp_jingle_component_t;
+typedef struct rexmpp_jingle_session rexmpp_jingle_session_t;
+typedef struct rexmpp_jingle_ctx rexmpp_jingle_ctx_t;
+
+enum rexmpp_jingle_session_type {
+ REXMPP_JINGLE_SESSION_FILE,
+ REXMPP_JINGLE_SESSION_MEDIA
+};
+
+enum rexmpp_codec {
+ REXMPP_CODEC_UNDEFINED,
+ REXMPP_CODEC_PCMU,
+ REXMPP_CODEC_PCMA,
+ REXMPP_CODEC_OPUS
+};
+
+#ifdef ENABLE_CALLS
+/* A structure used for callbacks, to pass rexmpp_t,
+ rexmpp_jingle_session_t, and the component ID. */
+struct rexmpp_jingle_component {
+ rexmpp_t *s;
+ rexmpp_jingle_session_t *session;
+ int component_id;
+ rexmpp_tls_t *dtls;
+ enum tls_st dtls_state;
+ srtp_t srtp_in;
+ srtp_t srtp_out;
+};
+
+struct ring_buf
+{
+ int16_t buf[PA_BUF_SIZE];
+ unsigned int write_pos;
+ unsigned int read_pos;
+};
+
+struct pa_buffers
+{
+ struct ring_buf capture;
+ struct ring_buf playback;
+};
+#endif
+
+struct rexmpp_jingle_session {
+ char *jid;
+ char *sid;
+ rexmpp_xml_t *initiate;
+ rexmpp_xml_t *accept;
+ rexmpp_jingle_session_t *next;
+ /* Sessions are commonly passed to callbacks by external libraries,
+ so it's convenient to have the corresponding rexmpp_t accessible
+ through those. */
+ rexmpp_t *s;
+ int initiator;
+ enum rexmpp_jingle_session_type type;
+
+ /* IBB file transfers */
+ FILE *ibb_fh;
+ char *ibb_sid;
+ uint16_t ibb_seq;
+
+ /* ICE-UDP + DTLS-SRTP A/V calls */
+#ifdef ENABLE_CALLS
+ char *stun_host;
+ uint16_t stun_port;
+ char *turn_host;
+ uint16_t turn_port;
+ char *turn_username;
+ char *turn_password;
+ /* two component structures for callbacks: for SRTP and SRTCP */
+ rexmpp_jingle_component_t component[2];
+ int rtcp_mux;
+ NiceAgent *ice_agent;
+ int ice_stream_id;
+ PaStream *pa_stream;
+ /* The default codec and payload type for this stream. */
+ enum rexmpp_codec codec;
+ uint8_t payload_type;
+ struct pa_buffers ring_buffers;
+ uint16_t rtp_seq_num;
+ uint16_t rtp_last_seq_num;
+ uint32_t rtp_timestamp;
+ uint32_t rtp_ssrc;
+#ifdef HAVE_OPUS
+ OpusEncoder *opus_enc;
+ OpusDecoder *opus_dec;
+#endif /* HAVE_POUS */
+#endif /* ENABLE_CALLS */
+};
+
+struct rexmpp_jingle_ctx {
+#ifdef ENABLE_CALLS
+ GMainLoop* gloop;
+#endif
+ rexmpp_jingle_session_t *sessions;
+};
+
+
+int rexmpp_jingle_init (rexmpp_t *s);
+rexmpp_err_t rexmpp_jingle_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds);
+struct timespec * rexmpp_jingle_timeout (rexmpp_t *s,
+ struct timespec *max_tv,
+ struct timespec *tv);
+int rexmpp_jingle_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds);
+
+rexmpp_err_t
+rexmpp_jingle_call (rexmpp_t *s,
+ const char *jid);
+rexmpp_err_t
+rexmpp_jingle_call_accept (rexmpp_t *s,
+ const char *sid);
+
+#endif
diff --git a/src/rexmpp_openpgp.c b/src/rexmpp_openpgp.c
new file mode 100644
index 0000000..14e2d35
--- /dev/null
+++ b/src/rexmpp_openpgp.c
@@ -0,0 +1,887 @@
+/**
+ @file rexmpp_openpgp.c
+ @brief XEP-0373 routines
+ @author defanor <defanor@uberspace.net>
+ @date 2020--2021
+ @copyright MIT license.
+
+
+Implementation notes
+====================
+
+XEP-0373 v0.6 is implemented here.
+
+Intentionally omitted functionality:
+
+- Not including a `to` element for self, since it is redundant for
+ signed messages, and only useful for signed ones.
+
+- Private key synchronisation is not implemented, since it is
+ unnecessary in the presence of asynchronous cryptography and support
+ for multiple keys, but can be dangerous if a passphrase used for key
+ encryption is weaker than the key itself.
+
+- XEP-0374 is not implemented here, since restricting its usage to
+ `<signcrypt/>` is likely to be undesirable in some cases (primarily
+ because it introduces non-repudiation).
+
+Possible future improvements:
+
+- A setting to generate the keys if they are missing, upload them
+ automatically, encrypt messages opportunistically (as the XEP
+ suggests).
+
+- Maybe use alternative key retrieval methods in order to decrease
+ dependency on PEP/pubsub, and possibly to incorporate existing
+ infrastructure: e.g., retrieval by PEP-provided fingerprint from key
+ servers, by vCard-provided email address from WKD or DANE.
+
+*/
+
+#include <syslog.h>
+#include <string.h>
+#include <time.h>
+
+#include "config.h"
+
+#ifdef HAVE_GPGME
+#include <gpgme.h>
+#endif
+#include <gcrypt.h>
+
+#include "rexmpp.h"
+#include "rexmpp_xml.h"
+#include "rexmpp_openpgp.h"
+#include "rexmpp_jid.h"
+#include "rexmpp_pubsub.h"
+#include "rexmpp_base64.h"
+#include "rexmpp_random.h"
+
+
+#ifdef HAVE_GPGME
+
+void rexmpp_pgp_fp_reply (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)req; /* Not of interest. */
+ if (! success) {
+ rexmpp_log(s, LOG_WARNING, "Failed to retrieve an OpenpPGP key");
+ return;
+ }
+ rexmpp_xml_t *pubsub =
+ rexmpp_xml_find_child(response, "http://jabber.org/protocol/pubsub",
+ "pubsub");
+ if (pubsub == NULL) {
+ rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: not a pubsub response");
+ return;
+ }
+ rexmpp_xml_t *items =
+ rexmpp_xml_find_child(pubsub, "http://jabber.org/protocol/pubsub",
+ "items");
+ if (items == NULL) {
+ rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: no items in pubsub element");
+ return;
+ }
+ rexmpp_xml_t *item =
+ rexmpp_xml_find_child(items, "http://jabber.org/protocol/pubsub", "item");
+ if (item == NULL) {
+ rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: no item in items");
+ return;
+ }
+ rexmpp_xml_t *pubkey =
+ rexmpp_xml_find_child(item, "urn:xmpp:openpgp:0", "pubkey");
+ if (pubkey == NULL) {
+ rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: no pubkey in item");
+ return;
+ }
+ rexmpp_xml_t *data =
+ rexmpp_xml_find_child(pubkey, "urn:xmpp:openpgp:0", "data");
+ if (data == NULL) {
+ rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: no data in pubkey");
+ return;
+ }
+
+ char *key_raw = NULL;
+ size_t key_raw_len = 0;
+ const char *key_base64 = rexmpp_xml_text_child(data);
+ int base64_err =
+ rexmpp_base64_from(key_base64, strlen(key_base64), &key_raw, &key_raw_len);
+ if (base64_err != 0) {
+ rexmpp_log(s, LOG_ERR, "Base-64 key decoding failure");
+ return;
+ }
+
+ gpgme_error_t err;
+ gpgme_data_t key_dh;
+
+ gpgme_data_new_from_mem(&key_dh, key_raw, key_raw_len, 0);
+ err = gpgme_op_import(s->pgp_ctx, key_dh);
+ /* Apparently reading GPGME results is not thread-safe. Fortunately
+ it's not critical. */
+ gpgme_import_result_t r = gpgme_op_import_result(s->pgp_ctx);
+
+ gpgme_data_release(key_dh);
+ if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_WARNING, "OpenPGP key import error: %s",
+ gpgme_strerror(err));
+ return;
+ }
+ if (r->imported == 1) {
+ rexmpp_log(s, LOG_DEBUG, "Imported a key");
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Key import failure");
+ }
+}
+
+rexmpp_err_t
+rexmpp_openpgp_check_keys (rexmpp_t *s,
+ const char *jid,
+ rexmpp_xml_t *items)
+{
+ rexmpp_xml_t *item =
+ rexmpp_xml_find_child(items, "http://jabber.org/protocol/pubsub#event",
+ "item");
+ rexmpp_xml_t *list =
+ rexmpp_xml_find_child(item, "urn:xmpp:openpgp:0", "public-keys-list");
+ rexmpp_xml_t *metadata;
+ for (metadata = rexmpp_xml_first_elem_child(list);
+ metadata != NULL;
+ metadata = rexmpp_xml_next_elem_sibling(metadata)) {
+ const char *fingerprint =
+ rexmpp_xml_find_attr_val(metadata, "v4-fingerprint");
+ gpgme_key_t key;
+ gpgme_error_t err;
+ err = gpgme_get_key(s->pgp_ctx, fingerprint, &key, 0);
+ if (key != NULL) {
+ gpgme_key_unref(key);
+ }
+ if (gpg_err_code(err) == GPG_ERR_EOF) {
+ rexmpp_log(s, LOG_DEBUG,
+ "Unknown OpenPGP key fingerprint for %s: %s",
+ jid, fingerprint);
+ rexmpp_xml_t *fp_req =
+ rexmpp_xml_new_elem("pubsub", "http://jabber.org/protocol/pubsub");
+ rexmpp_xml_t *fp_req_items =
+ rexmpp_xml_new_elem("items", NULL);
+ rexmpp_xml_add_attr(fp_req_items, "max_items", "1");
+ char key_node[72];
+ snprintf(key_node, 72, "urn:xmpp:openpgp:0:public-keys:%s", fingerprint);
+ rexmpp_xml_add_attr(fp_req_items, "node", key_node);
+ rexmpp_xml_add_child(fp_req, fp_req_items);
+ rexmpp_iq_new(s, "get", jid, fp_req, rexmpp_pgp_fp_reply, NULL);
+ } else if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_WARNING,
+ "OpenPGP error when looking for a key: %s",
+ gpgme_strerror(err));
+ }
+ }
+
+ return REXMPP_SUCCESS;
+}
+
+rexmpp_xml_t *rexmpp_published_fingerprints (rexmpp_t *s, const char *jid) {
+ rexmpp_xml_t *published =
+ rexmpp_find_event(s, jid, "urn:xmpp:openpgp:0:public-keys", NULL);
+ if (published == NULL) {
+ return NULL;
+ }
+ rexmpp_xml_t *event =
+ rexmpp_xml_find_child(published, "http://jabber.org/protocol/pubsub#event",
+ "event");
+ rexmpp_xml_t *items =
+ rexmpp_xml_find_child(event, "http://jabber.org/protocol/pubsub#event",
+ "items");
+ rexmpp_xml_t *item =
+ rexmpp_xml_find_child(items, "http://jabber.org/protocol/pubsub#event",
+ "item");
+ rexmpp_xml_t *list =
+ rexmpp_xml_find_child(item, "urn:xmpp:openpgp:0",
+ "public-keys-list");
+ rexmpp_xml_t *published_fps = list->alt.elem.children;
+ return published_fps;
+}
+
+int rexmpp_openpgp_key_is_published (rexmpp_t *s, const char *fp) {
+ rexmpp_xml_t *metadata;
+ for (metadata = rexmpp_published_fingerprints(s, s->assigned_jid.bare);
+ metadata != NULL;
+ metadata = metadata->next) {
+ if (! rexmpp_xml_match(metadata, "urn:xmpp:openpgp:0", "pubkey-metadata")) {
+ continue;
+ }
+ const char *fingerprint = rexmpp_xml_find_attr_val(metadata, "v4-fingerprint");
+ if (fingerprint == NULL) {
+ rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata");
+ continue;
+ }
+ if (strcmp(fingerprint, fp) == 0) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+rexmpp_xml_t *
+rexmpp_openpgp_remove_key_from_list (rexmpp_t *s,
+ const char *fp)
+{
+ rexmpp_xml_t *fps =
+ rexmpp_xml_clone_list(rexmpp_published_fingerprints(s, s->assigned_jid.bare));
+ rexmpp_xml_t *metadata, *prev = NULL;
+ for (metadata = fps;
+ metadata != NULL;
+ prev = metadata, metadata = rexmpp_xml_next_elem_sibling(metadata)) {
+ if (! rexmpp_xml_match(metadata, "urn:xmpp:openpgp:0", "pubkey-metadata")) {
+ continue;
+ }
+ const char *fingerprint =
+ rexmpp_xml_find_attr_val(metadata, "v4-fingerprint");
+ if (fingerprint == NULL) {
+ rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata");
+ continue;
+ }
+ int matches = (strcmp(fingerprint, fp) == 0);
+ if (matches) {
+ if (prev != NULL) {
+ prev->next = metadata->next;
+ } else {
+ fps = metadata->next;
+ }
+ rexmpp_xml_free(metadata);
+ return fps;
+ }
+ }
+ return fps;
+}
+
+void rexmpp_pgp_key_publish_list_iq (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)req;
+ (void)response;
+ if (! success) {
+ rexmpp_log(s, LOG_WARNING, "Failed to publish an OpenpPGP key list");
+ return;
+ }
+ rexmpp_log(s, LOG_INFO, "Published an OpenpPGP key list");
+}
+
+void rexmpp_pgp_key_fp_list_upload (rexmpp_t *s, rexmpp_xml_t *metadata) {
+ rexmpp_xml_t *keylist =
+ rexmpp_xml_new_elem("public-keys-list", "urn:xmpp:openpgp:0");
+ rexmpp_xml_add_child(keylist, metadata);
+ rexmpp_pubsub_item_publish(s, NULL, "urn:xmpp:openpgp:0:public-keys",
+ NULL, keylist, rexmpp_pgp_key_publish_list_iq, NULL);
+}
+
+void rexmpp_pgp_key_delete_iq (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)response;
+ if (! success) {
+ rexmpp_log(s, LOG_WARNING, "Failed to delete an OpenpPGP key");
+ return;
+ }
+ rexmpp_xml_t *pubsub = req->alt.elem.children;
+ rexmpp_xml_t *publish = pubsub->alt.elem.children;;
+ const char *node = rexmpp_xml_find_attr_val(publish, "node");
+ const char *fingerprint = node + 31;
+ rexmpp_log(s, LOG_INFO, "Removed OpenpPGP key %s", fingerprint);
+}
+
+void rexmpp_pgp_key_publish_iq (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)response;
+ if (! success) {
+ rexmpp_log(s, LOG_WARNING, "Failed to publish an OpenpPGP key");
+ return;
+ }
+ rexmpp_log(s, LOG_INFO, "Uploaded an OpenpPGP key");
+ rexmpp_xml_t *pubsub = req->alt.elem.children;
+ rexmpp_xml_t *publish = pubsub->alt.elem.children;;
+ const char *node = rexmpp_xml_find_attr_val(publish, "node");
+ const char *fingerprint = node + 31;
+
+ char time_str[42];
+ time_t t = time(NULL);
+ struct tm utc_time;
+ gmtime_r(&t, &utc_time);
+ strftime(time_str, 42, "%FT%TZ", &utc_time);
+
+ rexmpp_xml_t *metadata =
+ rexmpp_xml_new_elem("pubkey-metadata", "urn:xmpp:openpgp:0");
+ rexmpp_xml_add_attr(metadata, "date", time_str);
+ rexmpp_xml_add_attr(metadata, "v4-fingerprint", fingerprint);
+
+ rexmpp_xml_t *fps = rexmpp_openpgp_remove_key_from_list(s, fingerprint);
+ if (fps != NULL) {
+ metadata->next = fps;
+ }
+ rexmpp_pgp_key_fp_list_upload(s, metadata);
+}
+
+void rexmpp_openpgp_retract_key (rexmpp_t *s, const char *fp) {
+ rexmpp_xml_t *new_fp_list = rexmpp_openpgp_remove_key_from_list(s, fp);
+ if (new_fp_list != NULL) {
+ rexmpp_pgp_key_fp_list_upload(s, new_fp_list);
+ }
+ char node_str[72];
+ snprintf(node_str, 72, "urn:xmpp:openpgp:0:public-keys:%s", fp);
+ rexmpp_pubsub_node_delete(s, NULL, node_str, rexmpp_pgp_key_delete_iq, NULL);
+}
+
+rexmpp_err_t rexmpp_openpgp_publish_key (rexmpp_t *s, const char *fp) {
+ if (strlen(fp) != 40) {
+ rexmpp_log(s, LOG_ERR, "Wrong fingerprint length: %d", strlen(fp));
+ return REXMPP_E_PGP;
+ }
+
+ gpgme_data_t key_dh;
+ gpgme_error_t err;
+
+ err = gpgme_data_new(&key_dh);
+ if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_ERR, "Failed to create a gpgme data buffer: %s",
+ gpgme_strerror(err));
+ return REXMPP_E_PGP;
+ }
+ gpgme_data_set_encoding(key_dh, GPGME_DATA_ENCODING_BINARY);
+ err = gpgme_op_export(s->pgp_ctx, fp, 0, key_dh);
+ if (gpg_err_code(err) == GPG_ERR_EOF) {
+ rexmpp_log(s, LOG_ERR, "No such key found: %s", fp);
+ gpgme_data_release(key_dh);
+ return REXMPP_E_PGP;
+ } else if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_ERR, "Failed to read a key: %s", gpgme_strerror(err));
+ gpgme_data_release(key_dh);
+ return REXMPP_E_PGP;
+ }
+ char *key_raw, *key_base64 = NULL;
+ size_t key_raw_len, key_base64_len = 0;
+ key_raw = gpgme_data_release_and_get_mem(key_dh, &key_raw_len);
+ rexmpp_base64_to(key_raw, key_raw_len, &key_base64, &key_base64_len);
+ free(key_raw);
+ rexmpp_xml_t *data =
+ rexmpp_xml_new_elem("data", "urn:xmpp:openpgp:0");
+ rexmpp_xml_add_text(data, key_base64);
+ free(key_base64);
+
+ rexmpp_xml_t *pubkey =
+ rexmpp_xml_new_elem("pubkey", "urn:xmpp:openpgp:0");
+ rexmpp_xml_add_child(pubkey, data);
+
+ char time_str[42];
+ time_t t = time(NULL);
+ struct tm utc_time;
+ gmtime_r(&t, &utc_time);
+ strftime(time_str, 42, "%FT%TZ", &utc_time);
+ char node_str[72];
+ snprintf(node_str, 72, "urn:xmpp:openpgp:0:public-keys:%s", fp);
+ rexmpp_pubsub_item_publish(s, NULL, node_str, time_str,
+ pubkey, rexmpp_pgp_key_publish_iq, NULL);
+ return REXMPP_SUCCESS;
+}
+
+int rexmpp_openpgp_fingerprint_matches (const char *f1, const char *f2) {
+ int i = 0, j = 0;
+
+ while (f1[i] || f2[j]) {
+ /* skip spaces */
+ while (f1[i] == ' ') i++;
+ while (f2[j] == ' ') j++;
+ /* compare */
+ if (f1[i] != f2[j]) {
+ return 0;
+ }
+ /* advance */
+ i++;
+ j++;
+ }
+ return 1;
+}
+
+rexmpp_xml_t *
+rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s,
+ rexmpp_xml_t *message,
+ int *valid)
+{
+ gpgme_error_t err;
+ struct rexmpp_jid from, to;
+ *valid = 0;
+ if (! rexmpp_xml_match(message, "jabber:client", "message")) {
+ rexmpp_log(s, LOG_ERR, "Not a message element");
+ return NULL;
+ }
+ const char *from_str = rexmpp_xml_find_attr_val(message, "from");
+ if (from_str == NULL) {
+ rexmpp_log(s, LOG_ERR, "No 'from' attribute");
+ return NULL;
+ }
+ rexmpp_jid_parse(from_str, &from);
+ const char *to_str = rexmpp_xml_find_attr_val(message, "to");
+ if (to_str == NULL) {
+ if (strcmp(from.bare, s->assigned_jid.bare) != 0) {
+ rexmpp_log(s, LOG_ERR, "No 'to' attribute");
+ return NULL;
+ }
+ rexmpp_jid_parse(from.full, &to);
+ } else {
+ rexmpp_jid_parse(to_str, &to);
+ }
+ rexmpp_xml_t *openpgp =
+ rexmpp_xml_find_child(message, "urn:xmpp:openpgp:0", "openpgp");
+ if (openpgp == NULL) {
+ rexmpp_log(s, LOG_ERR, "No 'openpgp' child element");
+ return NULL;
+ }
+ const char *cipher_str = rexmpp_xml_text_child(openpgp);
+ rexmpp_xml_t *plain =
+ rexmpp_openpgp_decrypt_verify(s, cipher_str);
+ if (plain == NULL) {
+ return NULL;
+ }
+
+ if (rexmpp_xml_match(plain, "urn:xmpp:openpgp:0", "crypt")) {
+ *valid = 1;
+ return plain;
+ }
+
+ if (! (rexmpp_xml_match(plain, "urn:xmpp:openpgp:0", "signcrypt") ||
+ rexmpp_xml_match(plain, "urn:xmpp:openpgp:0", "sign"))) {
+ rexmpp_log(s, LOG_ERR, "An unexpected element inside <openpgp/>");
+ return plain;
+ }
+
+ rexmpp_xml_t *child;
+ int found = 0;
+ for (child = rexmpp_xml_first_elem_child(plain);
+ child != NULL && ! found;
+ child = rexmpp_xml_next_elem_sibling(child))
+ {
+ if (rexmpp_xml_match(child, "urn:xmpp:openpgp:0", "to")) {
+ const char *to_jid = rexmpp_xml_find_attr_val(child, "jid");
+ if (to_jid == NULL) {
+ rexmpp_log(s, LOG_WARNING,
+ "Found a 'to' element without a 'jid' attribute");
+ } else if (strcmp(to_jid, to.bare) == 0) {
+ found = 1;
+ }
+ }
+ }
+ if (! found) {
+ rexmpp_log(s, LOG_ERR,
+ "No recipient corresponds to outer message's recipient");
+ return plain;
+ }
+
+ gpgme_verify_result_t result = gpgme_op_verify_result(s->pgp_ctx);
+ if (result == NULL) {
+ rexmpp_log(s, LOG_ERR, "Signature verification failed");
+ return plain;
+ }
+
+ gpgme_signature_t sig = result->signatures;
+ if (sig->next != NULL) {
+ rexmpp_log(s, LOG_WARNING,
+ "Multiple signatures detected, verifying them all");
+ }
+ while (sig) {
+ if (! sig->validity) {
+ rexmpp_log(s, LOG_WARNING, "Invalid signature: %s",
+ gpgme_strerror(sig->validity_reason));
+ return plain;
+ }
+
+ found = 0;
+ rexmpp_xml_t *metadata;
+ for (metadata = rexmpp_published_fingerprints(s, from.bare);
+ metadata != NULL && ! found;
+ metadata = metadata->next) {
+ const char *fingerprint = rexmpp_xml_find_attr_val(metadata, "v4-fingerprint");
+ if (fingerprint == NULL) {
+ rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata");
+ continue;
+ }
+ if (rexmpp_openpgp_fingerprint_matches(fingerprint, sig->fpr)) {
+ found = 1;
+ }
+ }
+ if (! found) {
+ rexmpp_log(s, LOG_ERR, "No %s's known key matches that of the signature",
+ from.bare);
+ return plain;
+ }
+ gpgme_key_t key;
+ err = gpgme_get_key(s->pgp_ctx, sig->fpr, &key, 0);
+ if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_ERR, "Key reading failure: %s",
+ gpgme_strerror(err));
+ return plain;
+ }
+ gpgme_user_id_t uid;
+ found = 0;
+ for (uid = key->uids; uid != NULL; uid = uid->next) {
+ if (strlen(uid->uid) < 6) {
+ continue;
+ }
+ if (strncmp(uid->uid, "xmpp:", 5) == 0 &&
+ strcmp(uid->uid + 5, from.bare) == 0) {
+ found = 1;
+ }
+ }
+ if (! found) {
+ rexmpp_log(s, LOG_ERR,
+ "No 'xmpp:%s' user ID found in the key used for signature",
+ from.bare);
+ return plain;
+ }
+ sig = sig->next;
+ }
+
+ *valid = 1;
+ return plain;
+}
+
+rexmpp_xml_t *
+rexmpp_openpgp_decrypt_verify (rexmpp_t *s,
+ const char *cipher_base64)
+{
+ gpgme_error_t err;
+ gpgme_data_t cipher_dh, plain_dh;
+ char *cipher_raw = NULL, *plain;
+ size_t cipher_raw_len = 0, plain_len;
+ int base64_err = rexmpp_base64_from(cipher_base64, strlen(cipher_base64),
+ &cipher_raw, &cipher_raw_len);
+ if (base64_err != 0) {
+ rexmpp_log(s, LOG_ERR, "Base-64 cipher decoding failure");
+ return NULL;
+ }
+ gpgme_data_new_from_mem(&cipher_dh, cipher_raw, cipher_raw_len, 0);
+ gpgme_data_new(&plain_dh);
+ err = gpgme_op_decrypt_verify (s->pgp_ctx, cipher_dh, plain_dh);
+ gpgme_data_release(cipher_dh);
+
+ if (! (gpg_err_code(err) == GPG_ERR_NO_ERROR ||
+ gpg_err_code(err) == GPG_ERR_NO_DATA)) {
+ rexmpp_log(s, LOG_ERR, "Failed to decrypt/verify: %s", gpgme_strerror(err));
+ gpgme_data_release(plain_dh);
+ return NULL;
+ }
+ plain = gpgme_data_release_and_get_mem(plain_dh, &plain_len);
+ if (plain == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to release and get memory");
+ return NULL;
+ }
+ rexmpp_xml_t *elem = rexmpp_xml_parse(plain, plain_len);
+ if(elem == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to parse an XML document");
+ }
+ free(plain);
+ return elem;
+}
+
+void rexmpp_openpgp_add_keys (rexmpp_t *s,
+ const char *jid,
+ gpgme_key_t **keys,
+ int *nkeys,
+ int *allocated)
+{
+ gpgme_error_t err;
+ rexmpp_xml_t *metadata;
+ for (metadata = rexmpp_published_fingerprints(s, jid);
+ metadata != NULL;
+ metadata = metadata->next) {
+ const char *fingerprint = rexmpp_xml_find_attr_val(metadata, "v4-fingerprint");
+ if (fingerprint == NULL) {
+ rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata");
+ continue;
+ }
+ err = gpgme_get_key(s->pgp_ctx, fingerprint, &(*keys)[*nkeys], 0);
+ if (gpg_err_code(err) == GPG_ERR_NO_ERROR) {
+ if ((*keys)[*nkeys]->can_encrypt) {
+ *nkeys = *nkeys + 1;
+ if (*nkeys == *allocated) {
+ *allocated = *allocated * 2;
+ gpgme_key_t *new_keys =
+ realloc(*keys, sizeof(gpgme_key_t *) * *allocated);
+ if (new_keys == NULL) {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to reallocate the OpenPGP keys array: %s",
+ strerror(errno));
+ continue;
+ }
+ *keys = new_keys;
+ }
+ } else {
+ gpgme_key_unref((*keys)[*nkeys]);
+ }
+ (*keys)[*nkeys] = NULL;
+ } else if (gpg_err_code(err) == GPG_ERR_EOF) {
+ rexmpp_log(s, LOG_WARNING, "No key %s for %s found",
+ fingerprint, jid);
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to read key %s: %s",
+ fingerprint, gpgme_strerror(err));
+ }
+ }
+}
+
+void rexmpp_openpgp_set_signers (rexmpp_t *s) {
+ gpgme_error_t err;
+ rexmpp_xml_t *metadata;
+ gpgme_key_t sec_key;
+ gpgme_signers_clear(s->pgp_ctx);
+ for (metadata = rexmpp_published_fingerprints(s, s->initial_jid.bare);
+ metadata != NULL;
+ metadata = metadata->next) {
+ const char *fingerprint = rexmpp_xml_find_attr_val(metadata, "v4-fingerprint");
+ if (fingerprint == NULL) {
+ rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata");
+ continue;
+ }
+ err = gpgme_get_key(s->pgp_ctx, fingerprint, &sec_key, 1);
+ if (gpg_err_code(err) == GPG_ERR_NO_ERROR) {
+ if (sec_key->can_sign) {
+ gpgme_signers_add(s->pgp_ctx, sec_key);
+ }
+ gpgme_key_unref(sec_key);
+ } else if (gpg_err_code(err) != GPG_ERR_EOF) {
+ rexmpp_log(s, LOG_ERR, "Failed to read key %s: %s",
+ fingerprint, gpgme_strerror(err));
+ }
+ }
+}
+
+char *rexmpp_openpgp_payload (rexmpp_t *s,
+ rexmpp_xml_t *payload,
+ const char **recipients,
+ const char **signers,
+ enum rexmpp_ox_mode mode)
+{
+ gpgme_error_t err;
+ int i, nkeys = 0, allocated = 0;
+ gpgme_key_t *keys = NULL;
+
+ /* Prepare an element. */
+ char *elem_name = NULL;
+ if (mode == REXMPP_OX_SIGNCRYPT) {
+ elem_name = "signcrypt";
+ } else if (mode == REXMPP_OX_SIGN) {
+ elem_name = "sign";
+ } else if (mode == REXMPP_OX_CRYPT) {
+ elem_name = "crypt";
+ }
+ rexmpp_xml_t *elem =
+ rexmpp_xml_new_elem(elem_name, "urn:xmpp:openpgp:0");
+
+ if (mode == REXMPP_OX_SIGN || mode == REXMPP_OX_SIGNCRYPT) {
+ if (signers == NULL) {
+ rexmpp_openpgp_set_signers(s);
+ } else {
+ gpgme_signers_clear(s->pgp_ctx);
+ int signer;
+ gpgme_key_t sec_key;
+ for (signer = 0; signers[signer] != NULL; signer++) {
+ err = gpgme_get_key(s->pgp_ctx, signers[signer], &sec_key, 1);
+ if (gpg_err_code(err) == GPG_ERR_NO_ERROR) {
+ gpgme_signers_add(s->pgp_ctx, sec_key);
+ gpgme_key_unref(sec_key);
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to read key %s: %s",
+ signers[signer], gpgme_strerror(err));
+ }
+ }
+ }
+
+ /* Add all the recipients. */
+ for (i = 0; recipients[i] != NULL; i++) {
+ rexmpp_xml_t *to =
+ rexmpp_xml_new_elem("to", "urn:xmpp:openpgp:0");
+ rexmpp_xml_add_attr(to, "jid", recipients[i]);
+ rexmpp_xml_add_child(elem, to);
+ }
+ }
+
+ /* Add timestamp. */
+ char time_str[42];
+ time_t t = time(NULL);
+ struct tm utc_time;
+ gmtime_r(&t, &utc_time);
+ strftime(time_str, 42, "%FT%TZ", &utc_time);
+ rexmpp_xml_t *time =
+ rexmpp_xml_new_elem("time", "urn:xmpp:openpgp:0");
+ rexmpp_xml_add_attr(time, "stamp", time_str);
+ rexmpp_xml_add_child(elem, time);
+
+ /* Add the payload. */
+ rexmpp_xml_t *pl =
+ rexmpp_xml_new_elem("payload", "urn:xmpp:openpgp:0");
+ rexmpp_xml_add_child(pl, payload);
+ rexmpp_xml_add_child(elem, pl);
+
+ if (mode == REXMPP_OX_CRYPT || mode == REXMPP_OX_SIGNCRYPT) {
+ /* Add keys for encryption. */
+ allocated = 8;
+ keys = malloc(sizeof(gpgme_key_t *) * allocated);
+ if (keys == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to allocate memory for keys");
+ rexmpp_xml_free(elem);
+ return NULL;
+ }
+ keys[0] = NULL;
+ rexmpp_openpgp_add_keys(s, s->initial_jid.bare, &keys, &nkeys, &allocated);
+ for (i = 0; recipients[i] != NULL; i++) {
+ rexmpp_openpgp_add_keys(s, recipients[i], &keys, &nkeys, &allocated);
+ }
+
+ /* A random-length random-content padding. */
+ char *rand_str, rand[256];
+ rexmpp_random_buf(rand, 1);
+ size_t rand_str_len = 0, rand_len = (unsigned char)rand[0] % (255 - 16) + 16;
+ rexmpp_random_buf(rand, rand_len);
+ rexmpp_base64_to(rand, rand_len, &rand_str, &rand_str_len);
+
+ rexmpp_xml_t *rpad =
+ rexmpp_xml_new_elem("rpad", "urn:xmpp:openpgp:0");
+ rexmpp_xml_add_text(rpad, rand_str);
+ free(rand_str);
+ rexmpp_xml_add_child(elem, rpad);
+ }
+
+ /* Serialize the resulting XML. */
+ char *plaintext = rexmpp_xml_serialize(elem, 0);
+ rexmpp_xml_free(elem);
+
+ /* Encrypt, base64-encode. */
+ gpgme_data_t cipher_dh, plain_dh;
+ gpgme_data_new(&cipher_dh);
+ gpgme_data_new_from_mem(&plain_dh, plaintext, strlen(plaintext), 0);
+ if (mode == REXMPP_OX_SIGNCRYPT) {
+ err = gpgme_op_encrypt_sign(s->pgp_ctx, keys, GPGME_ENCRYPT_NO_ENCRYPT_TO,
+ plain_dh, cipher_dh);
+ } else if (mode == REXMPP_OX_CRYPT) {
+ err = gpgme_op_encrypt(s->pgp_ctx, keys, GPGME_ENCRYPT_NO_ENCRYPT_TO,
+ plain_dh, cipher_dh);
+ } else { /* if (mode == REXMPP_OX_SIGN) */
+ err = gpgme_op_sign(s->pgp_ctx, plain_dh, cipher_dh, GPGME_SIG_MODE_NORMAL);
+ }
+ if (keys != NULL) {
+ for (i = 0; i < nkeys; i++) {
+ gpgme_key_unref(keys[i]);
+ }
+ free(keys);
+ keys = NULL;
+ }
+ gpgme_data_release(plain_dh);
+ if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_ERR, "Failed to %s: %s", elem_name, gpgme_strerror(err));
+ gpgme_data_release(cipher_dh);
+ return NULL;
+ }
+ char *cipher_raw = NULL, *cipher_base64 = NULL;
+ size_t cipher_raw_len = 0, cipher_base64_len = 0;
+ cipher_raw = gpgme_data_release_and_get_mem(cipher_dh, &cipher_raw_len);
+ rexmpp_base64_to(cipher_raw, cipher_raw_len,
+ &cipher_base64, &cipher_base64_len);
+ free(cipher_raw);
+
+ return cipher_base64;
+}
+
+rexmpp_err_t rexmpp_openpgp_set_home_dir (rexmpp_t *s, const char *home_dir) {
+ gpgme_engine_info_t engine_info;
+ gpgme_error_t err;
+ engine_info = gpgme_ctx_get_engine_info(s->pgp_ctx);
+ err = gpgme_ctx_set_engine_info(s->pgp_ctx, engine_info->protocol,
+ engine_info->file_name,
+ home_dir);
+ if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_ERR, "Failed to set home directory: %s",
+ gpgme_strerror(err));
+ return REXMPP_E_PGP;
+ }
+ return REXMPP_SUCCESS;
+}
+
+#else
+
+/* Dummy functions for when it's built without GPGME. */
+
+rexmpp_err_t gpgme_not_supported(rexmpp_t *s) {
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without GPGME support");
+ return REXMPP_E_PGP;
+}
+
+rexmpp_err_t
+rexmpp_openpgp_check_keys (rexmpp_t *s,
+ const char *jid,
+ rexmpp_xml_t *items) {
+ (void)jid;
+ (void)items;
+ return gpgme_not_supported(s);
+}
+
+rexmpp_err_t rexmpp_openpgp_publish_key (rexmpp_t *s, const char *fp) {
+ (void)fp;
+ return gpgme_not_supported(s);
+}
+
+void rexmpp_openpgp_retract_key (rexmpp_t *s, const char *fp) {
+ (void)fp;
+ gpgme_not_supported(s);
+}
+
+rexmpp_xml_t *
+rexmpp_openpgp_decrypt_verify (rexmpp_t *s,
+ const char *cipher_base64) {
+ (void)cipher_base64;
+ gpgme_not_supported(s);
+ return NULL;
+}
+
+rexmpp_xml_t *
+rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s,
+ rexmpp_xml_t *message,
+ int *valid) {
+ (void)message;
+ (void)valid;
+ gpgme_not_supported(s);
+ return NULL;
+}
+
+char *rexmpp_openpgp_payload (rexmpp_t *s,
+ rexmpp_xml_t *payload,
+ const char **recipients,
+ const char **signers,
+ enum rexmpp_ox_mode mode) {
+ (void)recipients;
+ (void)signers;
+ (void)mode;
+ rexmpp_xml_free(payload);
+ gpgme_not_supported(s);
+ return NULL;
+}
+
+rexmpp_err_t rexmpp_openpgp_set_home_dir (rexmpp_t *s, const char *home_dir) {
+ (void)home_dir;
+ return gpgme_not_supported(s);
+}
+
+#endif
diff --git a/src/rexmpp_openpgp.h b/src/rexmpp_openpgp.h
new file mode 100644
index 0000000..2132930
--- /dev/null
+++ b/src/rexmpp_openpgp.h
@@ -0,0 +1,102 @@
+/**
+ @file rexmpp_openpgp.h
+ @brief XEP-0373 routines
+ @author defanor <defanor@uberspace.net>
+ @date 2020--2021
+ @copyright MIT license.
+*/
+#ifndef REXMPP_OPENPGP_H
+#define REXMPP_OPENPGP_H
+
+#include "rexmpp.h"
+
+/**
+ @brief A mode corresponding to XEP-0373's OpenPGP content element.
+ */
+enum rexmpp_ox_mode {
+ REXMPP_OX_SIGN,
+ REXMPP_OX_CRYPT,
+ REXMPP_OX_SIGNCRYPT
+};
+
+/**
+ @brief Checks whether we have all the keys from the list of known
+ keys for a given JID, requests missing ones.
+ @param[in] s ::rexmpp
+ @param[in] jid A JID.
+ @param[in] items An <items> element with <public-keys-list> in it.
+*/
+rexmpp_err_t
+rexmpp_openpgp_check_keys (rexmpp_t *s,
+ const char *jid,
+ rexmpp_xml_t *items);
+
+/**
+ @brief Publishes a key via PEP/pubsub.
+ @param[in] s ::rexmpp
+ @param[in] fp The fingerprint of a key that should be published.
+ @returns ::REXMPP_SUCCESS or an error code.
+*/
+rexmpp_err_t rexmpp_openpgp_publish_key (rexmpp_t *s, const char *fp);
+
+/**
+ @brief Retracts a key from PEP/pubsub.
+ @param[in] s ::rexmpp
+ @param[in] fp The fingerprint of a key that should be deleted.
+*/
+void rexmpp_openpgp_retract_key (rexmpp_t *s, const char *fp);
+
+/**
+ @brief Tries to decrypt and/or verify an OpenPGP message.
+ @param[in] s ::rexmpp
+ @param[in] cipher_base64 An OpenPGP ciphertext.
+ @returns A plaintext message body.
+*/
+rexmpp_xml_t *
+rexmpp_openpgp_decrypt_verify (rexmpp_t *s,
+ const char *cipher_base64);
+
+/**
+ @brief Tries to decrypt and/or verify an OpenPGP message from a
+ <message> element, taking into account its attributes.
+ @param[in] s ::rexmpp
+ @param[in] message A <message> element.
+ @param[out] valid Will be set to 1 if the message appears to be
+ valid.
+ @returns A decrypted message body.
+*/
+rexmpp_xml_t *
+rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s,
+ rexmpp_xml_t *message,
+ int *valid);
+
+/**
+ @brief Encodes a message, producing a signed and/or encrypted
+ payload.
+ @param[in] s ::rexmpp
+ @param[in] payload XML payload.
+ @param[in] recipients A NULL-terminated list of recipient JIDs.
+ @param[in] signers A NULL-terminated list of fingerprints of the
+ keys to sign with. Can be NULL to sign with all the available
+ published keys.
+ @param[in] mode ::rexmpp_ox_mode
+ @returns An encoded <openpgp> payload.
+*/
+char *rexmpp_openpgp_payload (rexmpp_t *s,
+ rexmpp_xml_t *payload,
+ const char **recipients,
+ const char **signers,
+ enum rexmpp_ox_mode mode);
+
+
+/**
+ @brief An utility function for setting GPG home directory. An
+ appropriate time to call it is right after rexmpp_init.
+ @param[in] s ::rexmpp
+ @param[in] home_dir Path to the home directory.
+ @returns ::REXMPP_E_PGP or ::REXMPP_SUCCESS
+*/
+rexmpp_err_t rexmpp_openpgp_set_home_dir (rexmpp_t *s, const char *home_dir);
+
+
+#endif
diff --git a/src/rexmpp_pubsub.c b/src/rexmpp_pubsub.c
new file mode 100644
index 0000000..58cb060
--- /dev/null
+++ b/src/rexmpp_pubsub.c
@@ -0,0 +1,88 @@
+/**
+ @file rexmpp_pubsub.c
+ @brief XEP-0060 helper functions
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+*/
+
+#include "rexmpp.h"
+#include "rexmpp_xml.h"
+
+void
+rexmpp_pubsub_iq (rexmpp_t *s,
+ const char *iq_type,
+ const char *pubsub_namespace,
+ const char *service_jid,
+ rexmpp_xml_t *payload,
+ rexmpp_iq_callback_t callback,
+ void *cb_data)
+{
+ if (pubsub_namespace == NULL) {
+ pubsub_namespace = "http://jabber.org/protocol/pubsub";
+ }
+ rexmpp_xml_t *pubsub = rexmpp_xml_new_elem("pubsub", pubsub_namespace);
+ rexmpp_xml_add_child(pubsub, payload);
+ rexmpp_iq_new(s, iq_type, service_jid, pubsub, callback, cb_data);
+}
+
+void
+rexmpp_pubsub_item_publish (rexmpp_t *s,
+ const char *service_jid,
+ const char *node,
+ const char *item_id,
+ rexmpp_xml_t *payload,
+ rexmpp_iq_callback_t callback,
+ void *cb_data)
+{
+ rexmpp_xml_t *item =
+ rexmpp_xml_new_elem("item", "http://jabber.org/protocol/pubsub");
+ if (item_id != NULL) {
+ rexmpp_xml_add_attr(item, "id", item_id);
+ }
+ rexmpp_xml_add_child(item, payload);
+
+ rexmpp_xml_t *publish =
+ rexmpp_xml_new_elem("publish", "http://jabber.org/protocol/pubsub");
+ rexmpp_xml_add_attr(publish, "node", node);
+ rexmpp_xml_add_child(publish, item);
+
+ rexmpp_pubsub_iq(s, "set", NULL, service_jid, publish, callback, cb_data);
+}
+
+void
+rexmpp_pubsub_item_retract (rexmpp_t *s,
+ const char *service_jid,
+ const char *node,
+ const char *item_id,
+ rexmpp_iq_callback_t callback,
+ void *cb_data)
+{
+ rexmpp_xml_t *item =
+ rexmpp_xml_new_elem("item", "http://jabber.org/protocol/pubsub");
+ if (item_id != NULL) {
+ rexmpp_xml_add_attr(item, "id", item_id);
+ }
+
+ rexmpp_xml_t *retract =
+ rexmpp_xml_new_elem("retract", "http://jabber.org/protocol/pubsub");
+ rexmpp_xml_add_attr(retract, "node", node);
+ rexmpp_xml_add_child(retract, item);
+
+ rexmpp_pubsub_iq(s, "set", NULL, service_jid, retract, callback, cb_data);
+}
+
+void
+rexmpp_pubsub_node_delete (rexmpp_t *s,
+ const char *service_jid,
+ const char *node,
+ rexmpp_iq_callback_t callback,
+ void *cb_data)
+{
+ rexmpp_xml_t *delete =
+ rexmpp_xml_new_elem("delete", "http://jabber.org/protocol/pubsub#owner");
+ rexmpp_xml_add_attr(delete, "node", node);
+
+ rexmpp_pubsub_iq(s, "set", "http://jabber.org/protocol/pubsub#owner",
+ service_jid, delete, callback, cb_data);
+}
diff --git a/src/rexmpp_pubsub.h b/src/rexmpp_pubsub.h
new file mode 100644
index 0000000..86675d3
--- /dev/null
+++ b/src/rexmpp_pubsub.h
@@ -0,0 +1,40 @@
+/**
+ @file rexmpp_pubsub.h
+ @brief XEP-0060 helper functions
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+*/
+
+void
+rexmpp_pubsub_iq (rexmpp_t *s,
+ const char *iq_type,
+ const char *pubsub_namespace,
+ const char *service_jid,
+ rexmpp_xml_t *payload,
+ rexmpp_iq_callback_t callback,
+ void *cb_data);
+
+void
+rexmpp_pubsub_item_publish (rexmpp_t *s,
+ const char *service_jid,
+ const char *node,
+ const char *item_id,
+ rexmpp_xml_t *payload,
+ rexmpp_iq_callback_t callback,
+ void *cb_data);
+
+void
+rexmpp_pubsub_item_retract (rexmpp_t *s,
+ const char *service_jid,
+ const char *node,
+ const char *item_id,
+ rexmpp_iq_callback_t callback,
+ void *cb_data);
+
+void
+rexmpp_pubsub_node_delete (rexmpp_t *s,
+ const char *service_jid,
+ const char *node,
+ rexmpp_iq_callback_t callback,
+ void *cb_data);
diff --git a/src/rexmpp_random.c b/src/rexmpp_random.c
new file mode 100644
index 0000000..11e2b73
--- /dev/null
+++ b/src/rexmpp_random.c
@@ -0,0 +1,34 @@
+/**
+ @file rexmpp_random.c
+ @brief Random generation
+ @author defanor <defanor@uberspace.net>
+ @date 2023
+ @copyright MIT license.
+*/
+
+#include "config.h"
+#include "rexmpp_base64.h"
+
+#ifdef HAVE_GCRYPT
+#include <gcrypt.h>
+#else
+#define _GNU_SOURCE
+#include <stdlib.h>
+#endif
+
+
+void rexmpp_random_buf (void *buf, size_t len) {
+#ifdef HAVE_GCRYPT
+ gcry_create_nonce(buf, len);
+#else
+ arc4random_buf(buf, len);
+#endif
+}
+
+char *rexmpp_random_id () {
+ char buf_raw[18], *buf_base64 = NULL;
+ size_t buf_base64_len = 0;
+ rexmpp_random_buf(buf_raw, 18);
+ rexmpp_base64_to(buf_raw, 18, &buf_base64, &buf_base64_len);
+ return buf_base64;
+}
diff --git a/src/rexmpp_random.h b/src/rexmpp_random.h
new file mode 100644
index 0000000..b0f1978
--- /dev/null
+++ b/src/rexmpp_random.h
@@ -0,0 +1,29 @@
+/**
+ @file rexmpp_random.h
+ @brief Random generation
+ @author defanor <defanor@uberspace.net>
+ @date 2023
+ @copyright MIT license.
+*/
+
+#ifndef REXMPP_RANDOM_H
+#define REXMPP_RANDOM_H
+
+/**
+ @brief Fills a buffer with cryptographically-secure random data.
+ @param[out] buf A buffer to write into.
+ @param[in] len The number of bytes to fill.
+
+ Uses arc4random_buf or gcry_create_nonce, depending on what is
+ available.
+*/
+void rexmpp_random_buf (void *buf, size_t len);
+
+/**
+ @brief Generates a random ASCII identifier.
+ @returns A null-terminated string, which must be freed by the
+ caller.
+*/
+char *rexmpp_random_id ();
+
+#endif
diff --git a/src/rexmpp_random.rs b/src/rexmpp_random.rs
new file mode 100644
index 0000000..dbabc71
--- /dev/null
+++ b/src/rexmpp_random.rs
@@ -0,0 +1,5 @@
+use std::os::raw::{c_char};
+
+extern {
+ pub fn rexmpp_random_id () -> *mut c_char;
+}
diff --git a/src/rexmpp_roster.c b/src/rexmpp_roster.c
index 3d62613..3deb5b7 100644
--- a/src/rexmpp_roster.c
+++ b/src/rexmpp_roster.c
@@ -7,24 +7,24 @@
*/
#include "rexmpp.h"
+#include "rexmpp_xml.h"
#include <syslog.h>
#include <string.h>
-#include <libxml/tree.h>
-#include <libxml/xmlsave.h>
+#include <stdlib.h>
-xmlNodePtr rexmpp_roster_find_item (rexmpp_t *s,
- const char *jid,
- xmlNodePtr *prev_item)
+rexmpp_xml_t *
+rexmpp_roster_find_item (rexmpp_t *s,
+ const char *jid,
+ rexmpp_xml_t **prev_item)
{
- xmlNodePtr prev = NULL, cur = s->roster_items;
+ rexmpp_xml_t *prev = NULL, *cur = s->roster_items;
while (cur != NULL) {
- char *cur_jid = xmlGetProp(cur, "jid");
+ const char *cur_jid = rexmpp_xml_find_attr_val(cur, "jid");
if (cur_jid == NULL) {
rexmpp_log(s, LOG_ALERT, "No jid found in a roster item.");
return NULL;
}
int match = (strcmp(cur_jid, jid) == 0);
- free(cur_jid);
if (match) {
if (prev_item != NULL) {
*prev_item = prev;
@@ -32,75 +32,75 @@ xmlNodePtr rexmpp_roster_find_item (rexmpp_t *s,
return cur;
}
prev = cur;
- cur = cur->next;
+ cur = rexmpp_xml_next_elem_sibling(cur);
}
return NULL;
}
-rexmpp_err_t rexmpp_modify_roster (rexmpp_t *s, xmlNodePtr item) {
+rexmpp_err_t rexmpp_modify_roster (rexmpp_t *s, rexmpp_xml_t *item) {
rexmpp_err_t ret = REXMPP_SUCCESS;
if (! rexmpp_xml_match(item, "jabber:iq:roster", "item")) {
rexmpp_log(s, LOG_ERR, "No roster item.");
return REXMPP_E_PARAM;
}
- char *subscription = xmlGetProp(item, "subscription");
- char *jid = xmlGetProp(item, "jid");
+ const char *subscription = rexmpp_xml_find_attr_val(item, "subscription");
+ const char *jid = rexmpp_xml_find_attr_val(item, "jid");
if (subscription != NULL && strcmp(subscription, "remove") == 0) {
/* Delete the item. */
- xmlNodePtr prev, cur;
+ rexmpp_xml_t *prev, *cur;
cur = rexmpp_roster_find_item(s, jid, &prev);
if (cur != NULL) {
if (prev != NULL) {
- prev->next = cur->next;
+ prev->next = rexmpp_xml_next_elem_sibling(cur);
} else {
- s->roster_items = cur->next;
+ s->roster_items = rexmpp_xml_next_elem_sibling(cur);
}
- xmlFreeNode(cur);
+ rexmpp_xml_free(cur);
} else {
ret = REXMPP_E_ROSTER_ITEM_NOT_FOUND;
}
} else {
/* Add or modify the item. */
- xmlNodePtr cur, prev;
+ rexmpp_xml_t *cur, *prev;
cur = rexmpp_roster_find_item(s, jid, &prev);
/* Remove the item if it was in the roster before. */
if (cur != NULL) {
if (prev != NULL) {
- prev->next = cur->next;
+ prev->next = rexmpp_xml_next_elem_sibling(cur);
} else {
- s->roster_items = cur->next;
+ s->roster_items = rexmpp_xml_next_elem_sibling(cur);
}
- xmlFreeNode(cur);
+ rexmpp_xml_free(cur);
}
/* Add the new item. */
- xmlNodePtr new_item = xmlCopyNode(item, 1);
+ rexmpp_xml_t *new_item = rexmpp_xml_clone(item);
new_item->next = s->roster_items;
s->roster_items = new_item;
}
- free(jid);
- if (subscription != NULL) {
- free(subscription);
- }
if (s->roster_modify_cb != NULL) {
s->roster_modify_cb(s, item);
}
return ret;
}
-void rexmpp_roster_set (rexmpp_t *s, xmlNodePtr query) {
+void rexmpp_roster_set (rexmpp_t *s, rexmpp_xml_t *query) {
if (s->roster_items != NULL) {
- xmlFreeNodeList(s->roster_items);
+ rexmpp_xml_free_list(s->roster_items);
}
if (s->roster_ver != NULL) {
free(s->roster_ver);
}
- s->roster_ver = xmlGetProp(query, "ver");
- s->roster_items = xmlCopyNodeList(xmlFirstElementChild(query));
+ const char *roster_ver = rexmpp_xml_find_attr_val(query, "ver");
+ s->roster_ver = NULL;
+ if (roster_ver != NULL) {
+ s->roster_ver = strdup(roster_ver);
+ }
+ s->roster_items = rexmpp_xml_clone_list(rexmpp_xml_first_elem_child(query));
if (s->roster_modify_cb != NULL) {
- xmlNodePtr item;
- for (item = xmlFirstElementChild(query);
+ rexmpp_xml_t *item;
+ for (item = rexmpp_xml_first_elem_child(query);
item != NULL;
- item = xmlNextElementSibling(item))
+ item = rexmpp_xml_next_elem_sibling(item))
{
s->roster_modify_cb(s, item);
}
@@ -112,10 +112,11 @@ void rexmpp_roster_cache_read (rexmpp_t *s) {
rexmpp_log(s, LOG_WARNING, "No roster cache file path is set.");
return;
}
- xmlDocPtr doc = xmlReadFile(s->roster_cache_file, "utf-8", XML_PARSE_NONET);
- xmlNodePtr query = xmlDocGetRootElement(doc);
- rexmpp_roster_set(s, query);
- xmlFreeDoc(doc);
+ rexmpp_xml_t *query = rexmpp_xml_read_file(s->roster_cache_file);
+ if (query != NULL) {
+ rexmpp_roster_set(s, query);
+ rexmpp_xml_free(query);
+ }
}
void rexmpp_roster_cache_write (rexmpp_t *s) {
@@ -123,31 +124,33 @@ void rexmpp_roster_cache_write (rexmpp_t *s) {
rexmpp_log(s, LOG_WARNING, "No roster cache file path is set.");
return;
}
- xmlDocPtr doc = xmlNewDoc("1.0");
- xmlNodePtr query = xmlNewDocNode(doc, NULL, "query", NULL);
- xmlDocSetRootElement(doc, query);
- xmlNewNs(query, "jabber:iq:roster", NULL);
+
+ rexmpp_xml_t *query = rexmpp_xml_new_elem("query", "jabber:iq:roster");
if (s->roster_ver != NULL) {
- xmlNewProp(query, "ver", s->roster_ver);
+ rexmpp_xml_add_attr(query, "ver", s->roster_ver);
}
if (s->roster_items != NULL) {
- xmlAddChild(query, xmlDocCopyNodeList(doc, s->roster_items));
+ rexmpp_xml_add_child(query, rexmpp_xml_clone_list(s->roster_items));
}
- xmlSaveFileEnc(s->roster_cache_file, doc, "utf-8");
- xmlFreeDoc(doc);
+ rexmpp_xml_write_file(s->roster_cache_file, query);
+ rexmpp_xml_free(query);
}
void rexmpp_iq_roster_get (rexmpp_t *s,
- xmlNodePtr req,
- xmlNodePtr response,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
int success)
{
+ (void)ptr;
+ (void)req; /* Nothing interesting in the request. */
if (! success) {
rexmpp_log(s, LOG_ERR, "Roster loading failed.");
return;
}
- xmlNodePtr query = xmlFirstElementChild(response);
- if (! rexmpp_xml_match(query, "jabber:iq:roster", "query")) {
+ rexmpp_xml_t *query =
+ rexmpp_xml_find_child(response, "jabber:iq:roster", "query");
+ if (query == NULL) {
rexmpp_log(s, LOG_DEBUG, "No roster query in reply.");
return;
}
diff --git a/src/rexmpp_roster.h b/src/rexmpp_roster.h
index d9e41fd..f613a7b 100644
--- a/src/rexmpp_roster.h
+++ b/src/rexmpp_roster.h
@@ -7,14 +7,15 @@
*/
-xmlNodePtr rexmpp_roster_find_item (rexmpp_t *s,
- const char *jid,
- xmlNodePtr *prev_item);
-rexmpp_err_t rexmpp_modify_roster (rexmpp_t *s, xmlNodePtr item);
-void rexmpp_roster_set (rexmpp_t *s, xmlNodePtr query);
+rexmpp_xml_t *rexmpp_roster_find_item (rexmpp_t *s,
+ const char *jid,
+ rexmpp_xml_t **prev_item);
+rexmpp_err_t rexmpp_modify_roster (rexmpp_t *s, rexmpp_xml_t *item);
+void rexmpp_roster_set (rexmpp_t *s, rexmpp_xml_t *query);
void rexmpp_roster_cache_read (rexmpp_t *s);
void rexmpp_roster_cache_write (rexmpp_t *s);
void rexmpp_iq_roster_get (rexmpp_t *s,
- xmlNodePtr req,
- xmlNodePtr response,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
int success);
diff --git a/src/rexmpp_rust.rs b/src/rexmpp_rust.rs
new file mode 100644
index 0000000..ba80598
--- /dev/null
+++ b/src/rexmpp_rust.rs
@@ -0,0 +1,8 @@
+mod rexmpp_jid;
+mod rexmpp_xml;
+mod rexmpp_xml_parser;
+mod rexmpp_dns;
+mod rexmpp_tcp;
+mod rexmpp_socks;
+mod rexmpp_random;
+mod rexmpp;
diff --git a/src/rexmpp_sasl.c b/src/rexmpp_sasl.c
new file mode 100644
index 0000000..20c4ba0
--- /dev/null
+++ b/src/rexmpp_sasl.c
@@ -0,0 +1,262 @@
+/**
+ @file rexmpp_sasl.c
+ @brief SASL
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+
+*/
+
+#include <syslog.h>
+#include <stdlib.h>
+
+#include "config.h"
+#include "rexmpp.h"
+#include "rexmpp_sasl.h"
+#include "rexmpp_base64.h"
+
+#ifdef HAVE_GSASL
+#include <gsasl.h>
+
+int rexmpp_sasl_cb (Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) {
+ (void)sctx; /* The session should already be in rexmpp_t. */
+ rexmpp_t *s = gsasl_callback_hook_get(ctx);
+ if (s == NULL || s->sasl_property_cb == NULL) {
+ return GSASL_NO_CALLBACK;
+ }
+ if (s->sasl_property_cb(s, (rexmpp_sasl_property)prop) == 0) {
+ return GSASL_OK;
+ } else {
+ return GSASL_NO_CALLBACK;
+ }
+}
+
+int rexmpp_sasl_ctx_init (rexmpp_t *s) {
+ s->sasl = malloc(sizeof(struct rexmpp_sasl_ctx));
+ int err = gsasl_init(&(s->sasl->ctx));
+ if (err != GSASL_OK) {
+ rexmpp_log(s, LOG_CRIT, "gsasl initialisation error: %s",
+ gsasl_strerror(err));
+ return -1;
+ }
+ gsasl_callback_hook_set(s->sasl->ctx, s);
+ gsasl_callback_set(s->sasl->ctx, rexmpp_sasl_cb);
+ return 0;
+}
+
+void rexmpp_sasl_ctx_deinit (rexmpp_t *s) {
+ gsasl_done(s->sasl->ctx);
+ if (s->sasl != NULL) {
+ free(s->sasl);
+ s->sasl = NULL;
+ }
+}
+
+void rexmpp_sasl_ctx_cleanup (rexmpp_t *s) {
+ gsasl_finish(s->sasl->session);
+ s->sasl->session = NULL;
+}
+
+int rexmpp_sasl_encode (rexmpp_t *s, const char *in, size_t in_len, char **out, size_t *out_len) {
+ int sasl_err = gsasl_encode (s->sasl->session, in, in_len, out, out_len);
+ if (sasl_err != GSASL_OK) {
+ rexmpp_log(s, LOG_ERR, "SASL encoding error: %s", gsasl_strerror(sasl_err));
+ return -1;
+ }
+ return 0;
+}
+
+int rexmpp_sasl_decode (rexmpp_t *s, const char *in, size_t in_len, char **out, size_t *out_len) {
+ int sasl_err = gsasl_decode(s->sasl->session, in, in_len, out, out_len);
+ if (sasl_err != GSASL_OK) {
+ rexmpp_log(s, LOG_ERR, "SASL decoding error: %s", gsasl_strerror(sasl_err));
+ return -1;
+ }
+ return 0;
+}
+
+const char *rexmpp_sasl_suggest_mechanism (rexmpp_t *s, const char *mech_list) {
+ return gsasl_client_suggest_mechanism(s->sasl->ctx, mech_list);
+}
+
+void rexmpp_sasl_property_set (rexmpp_t *s, rexmpp_sasl_property prop, const char *data) {
+ gsasl_property_set (s->sasl->session, (Gsasl_property)prop, data);
+}
+
+int rexmpp_sasl_start (rexmpp_t *s, const char *mech) {
+ int sasl_err = gsasl_client_start(s->sasl->ctx, mech, &(s->sasl->session));
+ if (sasl_err != GSASL_OK) {
+ rexmpp_log(s, LOG_CRIT, "Failed to initialise SASL session: %s",
+ gsasl_strerror(sasl_err));
+ return -1;
+ }
+ return 0;
+}
+
+int rexmpp_sasl_step64 (rexmpp_t *s, const char *b64_in, char **b64_out) {
+ int sasl_err = gsasl_step64 (s->sasl->session, b64_in, b64_out);
+ if (sasl_err != GSASL_OK) {
+ if (sasl_err == GSASL_NEEDS_MORE) {
+ rexmpp_log(s, LOG_DEBUG, "SASL needs more data");
+ } else {
+ rexmpp_log(s, LOG_ERR, "SASL error: %s", gsasl_strerror(sasl_err));
+ return -1;
+ }
+ }
+ return 0;
+}
+
+#else
+
+/* No GSASL. */
+#include <memory.h>
+
+int rexmpp_sasl_ctx_init (rexmpp_t *s) {
+ s->sasl = malloc(sizeof(struct rexmpp_sasl_ctx));
+ s->sasl->mech = REXMPP_SASL_MECH_UNKNOWN;
+ s->sasl->authid = NULL;
+ s->sasl->password = NULL;
+ return 0;
+}
+
+void rexmpp_sasl_ctx_cleanup (rexmpp_t *s) {
+ s->sasl->mech = REXMPP_SASL_MECH_UNKNOWN;
+ if (s->sasl->authid != NULL) {
+ free(s->sasl->authid);
+ s->sasl->authid = NULL;
+ }
+ if (s->sasl->password != NULL) {
+ free(s->sasl->password);
+ s->sasl->password = NULL;
+ }
+}
+
+void rexmpp_sasl_ctx_deinit (rexmpp_t *s) {
+ if (s->sasl != NULL) {
+ free(s->sasl);
+ s->sasl = NULL;
+ }
+}
+
+int rexmpp_sasl_encode (rexmpp_t *s, const char *in, size_t in_len, char **out, size_t *out_len) {
+ (void)s;
+ *out = malloc(in_len);
+ memcpy(*out, in, in_len);
+ *out_len = in_len;
+ return 0;
+}
+int rexmpp_sasl_decode (rexmpp_t *s, const char *in, size_t in_len, char **out, size_t *out_len) {
+ (void)s;
+ *out = malloc(in_len);
+ memcpy(*out, in, in_len);
+ *out_len = in_len;
+ return 0;
+}
+
+rexmpp_sasl_mechanism rexmpp_sasl_mech_read (const char *mech) {
+ if (mech == NULL) {
+ return REXMPP_SASL_MECH_UNKNOWN;
+ }
+ if (strcmp(mech, "EXTERNAL") == 0) {
+ return REXMPP_SASL_MECH_EXTERNAL;
+ } else if (strcmp(mech, "PLAIN") == 0) {
+ return REXMPP_SASL_MECH_PLAIN;
+ } else {
+ return REXMPP_SASL_MECH_UNKNOWN;
+ }
+}
+
+const char *rexmpp_sasl_mech_name (rexmpp_sasl_mechanism mech) {
+ if (mech == REXMPP_SASL_MECH_EXTERNAL) {
+ return "EXTERNAL";
+ } else if (mech == REXMPP_SASL_MECH_PLAIN) {
+ return "PLAIN";
+ } else {
+ return NULL;
+ }
+}
+
+const char *rexmpp_sasl_suggest_mechanism (rexmpp_t *s, const char *mech_list) {
+ (void)s;
+ char *mech, *save_ptr, *mlist = strdup(mech_list);
+ mech = strtok_r(mlist, " ", &save_ptr);
+ rexmpp_sasl_mechanism preferred = REXMPP_SASL_MECH_UNKNOWN;
+ while (mech != NULL) {
+ rexmpp_sasl_mechanism m = rexmpp_sasl_mech_read(mech);
+ if (m == REXMPP_SASL_MECH_EXTERNAL ||
+ (m == REXMPP_SASL_MECH_PLAIN && preferred == REXMPP_SASL_MECH_UNKNOWN)) {
+ preferred = m;
+ }
+ mech = strtok_r(NULL, " ", &save_ptr);
+ }
+ free(mlist);
+ return rexmpp_sasl_mech_name(preferred);
+}
+
+int rexmpp_sasl_start (rexmpp_t *s, const char *mech) {
+ rexmpp_sasl_mechanism m = rexmpp_sasl_mech_read(mech);
+ if (m != REXMPP_SASL_MECH_UNKNOWN) {
+ s->sasl->mech = m;
+ return 0;
+ }
+ return -1;
+}
+
+const char *rexmpp_sasl_get_prop (rexmpp_t *s, rexmpp_sasl_property prop) {
+ if (prop == REXMPP_SASL_PROP_AUTHID) {
+ if (s->sasl->authid == NULL) {
+ s->sasl_property_cb(s, prop);
+ }
+ return s->sasl->authid;
+ } else if (prop == REXMPP_SASL_PROP_PASSWORD) {
+ if (s->sasl->password == NULL) {
+ s->sasl_property_cb(s, prop);
+ }
+ return s->sasl->password;
+ }
+ return NULL;
+}
+
+int rexmpp_sasl_step64 (rexmpp_t *s, const char *b64_in, char **b64_out) {
+ (void)s;
+ (void)b64_in;
+ if (s->sasl->mech == REXMPP_SASL_MECH_PLAIN) {
+ /* RFC 4616 */
+ const char *authid = rexmpp_sasl_get_prop(s, REXMPP_SASL_PROP_AUTHID);
+ const char *password = rexmpp_sasl_get_prop(s, REXMPP_SASL_PROP_PASSWORD);
+ if (authid != NULL && password != NULL) {
+ size_t auth_len = strlen(authid) + strlen(password) + 2;
+ char *auth = malloc(auth_len);
+ auth[0] = 0;
+ memcpy(auth + 1, authid, strlen(authid));
+ auth[strlen(authid) + 1] = 0;
+ memcpy(auth + strlen(authid) + 2, password, strlen(password));
+ size_t out_len;
+ rexmpp_base64_to(auth, auth_len, b64_out, &out_len);
+ free(auth);
+ return 0;
+ }
+ } else if (s->sasl->mech == REXMPP_SASL_MECH_EXTERNAL) {
+ *b64_out = strdup("");
+ return 0;
+ }
+ return -1;
+}
+
+void rexmpp_sasl_property_set (rexmpp_t *s, rexmpp_sasl_property prop, const char *data) {
+ (void)s;
+ (void)data;
+ if (prop == REXMPP_SASL_PROP_AUTHID) {
+ if (s->sasl->authid != NULL) {
+ free(s->sasl->authid);
+ }
+ s->sasl->authid = strdup(data);
+ } else if (prop == REXMPP_SASL_PROP_PASSWORD) {
+ if (s->sasl->password != NULL) {
+ free(s->sasl->password);
+ }
+ s->sasl->password = strdup(data);
+ }
+}
+
+#endif
diff --git a/src/rexmpp_sasl.h b/src/rexmpp_sasl.h
new file mode 100644
index 0000000..a8460d4
--- /dev/null
+++ b/src/rexmpp_sasl.h
@@ -0,0 +1,110 @@
+/**
+ @file rexmpp_sasl.h
+ @brief SASL
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+
+*/
+
+
+#ifndef REXMPP_SASL_H
+#define REXMPP_SASL_H
+
+#include "config.h"
+
+#include "rexmpp.h"
+
+/** @brief These correspond to Gsasl_property values. */
+typedef enum {
+ /* Information properties, e.g., username. */
+ REXMPP_SASL_PROP_AUTHID = 1,
+ REXMPP_SASL_PROP_AUTHZID = 2,
+ REXMPP_SASL_PROP_PASSWORD = 3,
+ REXMPP_SASL_PROP_ANONYMOUS_TOKEN = 4,
+ REXMPP_SASL_PROP_SERVICE = 5,
+ REXMPP_SASL_PROP_HOSTNAME = 6,
+ REXMPP_SASL_PROP_GSSAPI_DISPLAY_NAME = 7,
+ REXMPP_SASL_PROP_PASSCODE = 8,
+ REXMPP_SASL_PROP_SUGGESTED_PIN = 9,
+ REXMPP_SASL_PROP_PIN = 10,
+ REXMPP_SASL_PROP_REALM = 11,
+ REXMPP_SASL_PROP_DIGEST_MD5_HASHED_PASSWORD = 12,
+ REXMPP_SASL_PROP_QOPS = 13,
+ REXMPP_SASL_PROP_QOP = 14,
+ REXMPP_SASL_PROP_SCRAM_ITER = 15,
+ REXMPP_SASL_PROP_SCRAM_SALT = 16,
+ REXMPP_SASL_PROP_SCRAM_SALTED_PASSWORD = 17,
+ REXMPP_SASL_PROP_SCRAM_SERVERKEY = 23,
+ REXMPP_SASL_PROP_SCRAM_STOREDKEY = 24,
+ REXMPP_SASL_PROP_CB_TLS_UNIQUE = 18,
+ REXMPP_SASL_PROP_SAML20_IDP_IDENTIFIER = 19,
+ REXMPP_SASL_PROP_SAML20_REDIRECT_URL = 20,
+ REXMPP_SASL_PROP_OPENID20_REDIRECT_URL = 21,
+ REXMPP_SASL_PROP_OPENID20_OUTCOME_DATA = 22,
+ /* Client callbacks. */
+ REXMPP_SASL_PROP_SAML20_AUTHENTICATE_IN_BROWSER = 250,
+ REXMPP_SASL_PROP_OPENID20_AUTHENTICATE_IN_BROWSER = 251,
+ /* Server validation callback properties. */
+ REXMPP_SASL_PROP_VALIDATE_SIMPLE = 500,
+ REXMPP_SASL_PROP_VALIDATE_EXTERNAL = 501,
+ REXMPP_SASL_PROP_VALIDATE_ANONYMOUS = 502,
+ REXMPP_SASL_PROP_VALIDATE_GSSAPI = 503,
+ REXMPP_SASL_PROP_VALIDATE_SECURID = 504,
+ REXMPP_SASL_PROP_VALIDATE_SAML20 = 505,
+ REXMPP_SASL_PROP_VALIDATE_OPENID20 = 506
+} rexmpp_sasl_property;
+
+/**
+ @brief SASL context.
+*/
+#ifdef HAVE_GSASL
+#include <gsasl.h>
+struct rexmpp_sasl_ctx {
+ Gsasl *ctx;
+ Gsasl_session *session;
+};
+#else
+typedef enum {
+ REXMPP_SASL_MECH_EXTERNAL,
+ REXMPP_SASL_MECH_PLAIN,
+ REXMPP_SASL_MECH_UNKNOWN
+} rexmpp_sasl_mechanism;
+
+struct rexmpp_sasl_ctx {
+ rexmpp_sasl_mechanism mech;
+ char *authid;
+ char *password;
+};
+#endif
+
+typedef struct rexmpp_sasl_ctx rexmpp_sasl_ctx_t;
+
+/**
+ @brief Initializes SASL context.
+*/
+int rexmpp_sasl_ctx_init (rexmpp_t *s);
+
+/**
+ @brief Cleans up the state that can be discarded between XMPP
+ connections, to be called from rexmpp_cleanup.
+*/
+void rexmpp_sasl_ctx_cleanup (rexmpp_t *s);
+
+/**
+ @brief Deinitializes a SASL context.
+*/
+void rexmpp_sasl_ctx_deinit (rexmpp_t *s);
+
+
+int rexmpp_sasl_encode (rexmpp_t *s, const char *in, size_t in_len, char **out, size_t *out_len);
+int rexmpp_sasl_decode (rexmpp_t *s, const char *in, size_t in_len, char **out, size_t *out_len);
+
+const char *rexmpp_sasl_suggest_mechanism (rexmpp_t *s, const char *mech_list);
+
+int rexmpp_sasl_start (rexmpp_t *s, const char *mech);
+int rexmpp_sasl_step64 (rexmpp_t *s, const char *b64_in, char **b64_out);
+
+void rexmpp_sasl_property_set (rexmpp_t *s, rexmpp_sasl_property prop, const char *data);
+
+#endif
diff --git a/src/rexmpp_socks.rs b/src/rexmpp_socks.rs
new file mode 100644
index 0000000..1828de1
--- /dev/null
+++ b/src/rexmpp_socks.rs
@@ -0,0 +1,180 @@
+// For rustc and libstd-rust version 1.48
+
+use std::os::raw::{c_int, c_char};
+use std::ffi::CStr;
+use std::net::TcpStream;
+use std::os::unix::io::{FromRawFd, IntoRawFd};
+use std::io::Write;
+use std::io::Read;
+use std::io::ErrorKind;
+use std::convert::TryFrom;
+
+const REXMPP_SOCKS_BUF_LEN: usize = 300;
+
+#[derive(PartialEq)]
+#[repr(C)]
+enum SocksIOState {
+ Writing,
+ Reading
+}
+
+#[derive(PartialEq)]
+#[repr(C)]
+enum SocksStage {
+ Auth,
+ Cmd,
+ Done
+}
+
+#[derive(PartialEq)]
+#[repr(C)]
+enum SocksErr {
+ Connected,
+ EAgain,
+ ETCP,
+ EReply,
+ EVersion,
+ ESocks,
+ EHost
+}
+
+#[repr(C)]
+pub struct RexmppSocks {
+ fd: c_int,
+ host: *const c_char,
+ port: u16,
+ stage: SocksStage,
+ io_state: SocksIOState,
+ socks_error: c_int,
+ buf: [u8; REXMPP_SOCKS_BUF_LEN],
+ buf_len: usize,
+ buf_sent: usize
+}
+
+#[no_mangle]
+extern "C" fn rexmpp_socks_proceed (s : &mut RexmppSocks) -> SocksErr {
+ if s.io_state == SocksIOState::Writing {
+ let mut stream : TcpStream = unsafe { TcpStream::from_raw_fd(s.fd) };
+ let ret = stream.write(&s.buf[s.buf_sent .. s.buf_len]);
+ // Make sure the connection is not closed by TcpStream.
+ TcpStream::into_raw_fd(stream);
+ match ret {
+ Ok(sent) => {
+ s.buf_sent += sent;
+ if s.buf_len == s.buf_sent {
+ s.buf_len = 0;
+ s.io_state = SocksIOState::Reading;
+ }
+ }
+ Err(error) => match error.kind() {
+ ErrorKind::WouldBlock => return SocksErr::EAgain,
+ _ => return SocksErr::ETCP
+ }
+ }
+ } else if s.io_state == SocksIOState::Reading {
+ let mut stream : TcpStream = unsafe { TcpStream::from_raw_fd(s.fd) };
+ let ret = stream.read(&mut s.buf[s.buf_len ..]);
+ // Make sure the connection is not closed by TcpStream.
+ TcpStream::into_raw_fd(stream);
+ match ret {
+ Ok(received) => {
+ s.buf_len += received;
+ if s.buf[0] != 5 {
+ return SocksErr::EVersion;
+ }
+ if s.buf_len >= 2 {
+ s.socks_error = s.buf[1].into();
+ }
+ if s.stage == SocksStage::Auth {
+ if s.buf_len > 2 {
+ return SocksErr::EReply;
+ }
+ if s.buf_len == 2 {
+ if s.socks_error != 0 {
+ return SocksErr::ESocks;
+ }
+ s.buf[0] = 5; // SOCKS version 5
+ s.buf[1] = 1; // Connect
+ s.buf[2] = 0; // Reserved
+ s.buf[3] = 3; // Domain name (todo: IP addresses)
+ let host_cstr : &CStr =
+ unsafe { CStr::from_ptr(s.host) };
+ let host_len = host_cstr.to_bytes().len();
+ match u8::try_from(host_len) {
+ Ok(u) => { s.buf[4] = u }
+ Err(_) => return SocksErr::EHost
+ }
+ s.buf[5 .. 5 + host_len].
+ copy_from_slice(&host_cstr.to_bytes());
+ s.buf[5 + host_len .. 7 + host_len].
+ copy_from_slice(&s.port.to_be_bytes());
+ s.buf_len = 7 + host_len;
+ s.buf_sent = 0;
+ s.stage = SocksStage::Cmd;
+ s.io_state = SocksIOState::Writing;
+ return rexmpp_socks_proceed(s);
+ }
+ } else if s.stage == SocksStage::Cmd {
+ if s.buf_len >= 5 {
+ let mut full_len : usize = 6;
+ match s.buf[3] {
+ // IPv4
+ 1 => full_len += 4,
+ // Domain name
+ 3 => full_len += usize::from(s.buf[4]) + 1,
+ // IPv6
+ 4 => full_len += 16,
+ _ => return SocksErr::EReply
+ }
+ if s.buf_len > full_len {
+ return SocksErr::EReply;
+ }
+ if s.buf_len == full_len {
+ if s.socks_error != 0 {
+ return SocksErr::ESocks;
+ }
+ // We are done
+ s.stage = SocksStage::Done;
+ return SocksErr::Connected;
+ }
+ }
+ }
+ }
+ Err(error) => match error.kind() {
+ ErrorKind::WouldBlock => return SocksErr::EAgain,
+ _ => return SocksErr::ETCP
+ }
+ }
+ }
+ return SocksErr::EAgain
+}
+
+#[no_mangle]
+extern "C" fn rexmpp_socks_init (
+ s : &mut RexmppSocks,
+ fd: c_int,
+ host: *const c_char,
+ port: u16
+)
+ -> SocksErr
+{
+ s.fd = fd;
+ s.host = host;
+ s.port = port;
+ s.socks_error = 0;
+
+ let host_cstr : &CStr = unsafe { CStr::from_ptr(host) };
+ if host_cstr.to_bytes().len() > 255 {
+ return SocksErr::EHost;
+ }
+
+ // Request authentication
+ s.stage = SocksStage::Auth;
+ s.io_state = SocksIOState::Writing;
+ s.buf[0] = 5; // SOCKS version 5
+ s.buf[1] = 1; // 1 supported method
+ s.buf[2] = 0; // No authentication required
+ s.buf_len = 3;
+ s.buf_sent = 0;
+ return rexmpp_socks_proceed(s);
+}
diff --git a/src/rexmpp_tcp.c b/src/rexmpp_tcp.c
index e57cfb8..c6a53a5 100644
--- a/src/rexmpp_tcp.c
+++ b/src/rexmpp_tcp.c
@@ -6,7 +6,6 @@
@copyright MIT license.
*/
-#include <ares.h>
#include <netdb.h>
#include <arpa/nameser.h>
#include <sys/socket.h>
@@ -19,44 +18,42 @@
#include <arpa/inet.h>
#include <fcntl.h>
+#include "rexmpp.h"
#include "rexmpp_tcp.h"
+#include "rexmpp_dns.h"
-void rexmpp_dns_aaaa_cb (void *ptr,
- int status,
- int timeouts,
- unsigned char *abuf,
- int alen)
+void rexmpp_tcp_dns_aaaa_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_dns_result_t *result)
{
+ (void)s;
rexmpp_tcp_conn_t *conn = ptr;
- conn->resolver_status_v6 = status;
- if (status == ARES_SUCCESS) {
+ conn->resolved_v6 = result;
+ if (result != NULL) {
conn->resolution_v6 = REXMPP_CONN_RESOLUTION_SUCCESS;
- ares_parse_aaaa_reply(abuf, alen, &(conn->addr_v6), NULL, NULL);
conn->addr_cur_v6 = -1;
} else {
conn->resolution_v6 = REXMPP_CONN_RESOLUTION_FAILURE;
}
}
-void rexmpp_dns_a_cb (void *ptr,
- int status,
- int timeouts,
- unsigned char *abuf,
- int alen)
+void rexmpp_tcp_dns_a_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_dns_result_t *result)
{
+ (void)s;
rexmpp_tcp_conn_t *conn = ptr;
- conn->resolver_status_v4 = status;
- if (status == ARES_SUCCESS) {
+ conn->resolved_v4 = result;
+ if (result != NULL) {
conn->resolution_v4 = REXMPP_CONN_RESOLUTION_SUCCESS;
- ares_parse_a_reply(abuf, alen, &(conn->addr_v4), NULL, NULL);
conn->addr_cur_v4 = -1;
if (conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING) {
/* Wait for 50 ms for IPv6. */
- gettimeofday(&(conn->next_connection_time), NULL);
- conn->next_connection_time.tv_usec += REXMPP_TCP_IPV6_DELAY_MS * 1000;
- if (conn->next_connection_time.tv_usec >= 1000000) {
- conn->next_connection_time.tv_usec -= 1000000;
+ clock_gettime(CLOCK_MONOTONIC, &(conn->next_connection_time));
+ conn->next_connection_time.tv_nsec += REXMPP_TCP_IPV6_DELAY_MS * 1000000;
+ if (conn->next_connection_time.tv_nsec >= 1000000000) {
+ conn->next_connection_time.tv_nsec -= 1000000000;
conn->next_connection_time.tv_sec++;
}
}
@@ -74,29 +71,52 @@ void rexmpp_tcp_cleanup (rexmpp_tcp_conn_t *conn) {
}
}
if (conn->resolution_v4 != REXMPP_CONN_RESOLUTION_INACTIVE) {
- ares_destroy(conn->resolver_channel);
conn->resolution_v4 = REXMPP_CONN_RESOLUTION_INACTIVE;
conn->resolution_v6 = REXMPP_CONN_RESOLUTION_INACTIVE;
}
- if (conn->addr_v4 != NULL) {
- ares_free_hostent(conn->addr_v4);
- conn->addr_v4 = NULL;
+ if (conn->resolved_v4 != NULL) {
+ rexmpp_dns_result_free(conn->resolved_v4);
+ conn->resolved_v4 = NULL;
}
- if (conn->addr_v6 != NULL) {
- ares_free_hostent(conn->addr_v6);
- conn->addr_v6 = NULL;
+ if (conn->resolved_v6 != NULL) {
+ rexmpp_dns_result_free(conn->resolved_v6);
+ conn->resolved_v6 = NULL;
}
}
rexmpp_tcp_conn_error_t
rexmpp_tcp_connected (rexmpp_tcp_conn_t *conn, int fd) {
+ struct sockaddr sa;
+ socklen_t sa_len = sizeof(sa);
+ getsockname(fd, &sa, &sa_len);
+ if (sa.sa_family == AF_INET && conn->resolved_v4 != NULL) {
+ conn->dns_secure = conn->resolved_v4->secure;
+ } else if (sa.sa_family == AF_INET6 && conn->resolved_v6 != NULL) {
+ conn->dns_secure = conn->resolved_v6->secure;
+ }
conn->fd = fd;
rexmpp_tcp_cleanup(conn);
return REXMPP_CONN_DONE;
}
+int rexmpp_tcp_socket(rexmpp_t *s, int domain) {
+ int sock = socket(domain, SOCK_STREAM, 0);
+
+ /* Make it non-blocking */
+ int flags = fcntl(sock, F_GETFL, 0);
+ fcntl(sock, F_SETFL, flags | O_NONBLOCK);
+
+ /* Call the socket creation callback, if provided */
+ if (s->socket_cb != NULL) {
+ s->socket_cb(s, sock);
+ }
+
+ return sock;
+}
+
rexmpp_tcp_conn_error_t
-rexmpp_tcp_conn_init (rexmpp_tcp_conn_t *conn,
+rexmpp_tcp_conn_init (rexmpp_t *s,
+ rexmpp_tcp_conn_t *conn,
const char *host,
uint16_t port)
{
@@ -106,24 +126,21 @@ rexmpp_tcp_conn_init (rexmpp_tcp_conn_t *conn,
}
conn->connection_attempts = 0;
conn->port = port;
- conn->addr_v4 = NULL;
- conn->addr_v6 = NULL;
+ conn->resolved_v4 = NULL;
+ conn->resolved_v6 = NULL;
conn->fd = -1;
+ conn->dns_secure = 0;
conn->next_connection_time.tv_sec = 0;
- conn->next_connection_time.tv_usec = 0;
+ conn->next_connection_time.tv_nsec = 0;
conn->resolution_v4 = REXMPP_CONN_RESOLUTION_INACTIVE;
conn->resolution_v6 = REXMPP_CONN_RESOLUTION_INACTIVE;
struct sockaddr_in addr_v4;
- int flags;
- if (inet_pton(AF_INET, host, &addr_v4)) {
+ if (inet_pton(AF_INET, host, &(addr_v4.sin_addr))) {
addr_v4.sin_family = AF_INET;
addr_v4.sin_port = htons(port);
- conn->sockets[conn->connection_attempts] =
- socket(AF_INET, SOCK_STREAM, 0);
- flags = fcntl(conn->sockets[conn->connection_attempts], F_GETFL, 0);
- fcntl(conn->sockets[conn->connection_attempts], F_SETFL, flags | O_NONBLOCK);
+ conn->sockets[conn->connection_attempts] = rexmpp_tcp_socket(s, AF_INET);
if (connect(conn->sockets[conn->connection_attempts],
(struct sockaddr*)&addr_v4,
sizeof(addr_v4))) {
@@ -137,14 +154,13 @@ rexmpp_tcp_conn_init (rexmpp_tcp_conn_t *conn,
conn->connection_attempts++;
return REXMPP_CONN_IN_PROGRESS;
}
- struct sockaddr_in addr_v6;
- if (inet_pton(AF_INET6, host, &addr_v6)) {
- addr_v6.sin_family = AF_INET6;
- addr_v6.sin_port = htons(port);
- conn->sockets[conn->connection_attempts] =
- socket(AF_INET6, SOCK_STREAM, 0);
- flags = fcntl(conn->sockets[conn->connection_attempts], F_GETFL, 0);
- fcntl(conn->sockets[conn->connection_attempts], F_SETFL, flags | O_NONBLOCK);
+ struct sockaddr_in6 addr_v6;
+ if (inet_pton(AF_INET6, host, &(addr_v6.sin6_addr))) {
+ addr_v6.sin6_family = AF_INET6;
+ addr_v6.sin6_port = htons(port);
+ addr_v6.sin6_flowinfo = 0;
+ addr_v6.sin6_scope_id = 0;
+ conn->sockets[conn->connection_attempts] = rexmpp_tcp_socket(s, AF_INET6);
if (connect(conn->sockets[conn->connection_attempts],
(struct sockaddr*)&addr_v6,
sizeof(addr_v6))) {
@@ -160,15 +176,11 @@ rexmpp_tcp_conn_init (rexmpp_tcp_conn_t *conn,
}
conn->resolution_v4 = REXMPP_CONN_RESOLUTION_WAITING;
conn->resolution_v6 = REXMPP_CONN_RESOLUTION_WAITING;
- conn->resolver_error = ares_init(&(conn->resolver_channel));
- if (conn->resolver_error != ARES_SUCCESS) {
- return REXMPP_CONN_RESOLVER_ERROR;
- }
- ares_query(conn->resolver_channel, host,
- ns_c_in, ns_t_aaaa, rexmpp_dns_aaaa_cb, conn);
- ares_query(conn->resolver_channel, host,
- ns_c_in, ns_t_a, rexmpp_dns_a_cb, conn);
+ rexmpp_dns_resolve(s, host, 28, 1,
+ conn, rexmpp_tcp_dns_aaaa_cb);
+ rexmpp_dns_resolve(s, host, 1, 1,
+ conn, rexmpp_tcp_dns_a_cb);
return REXMPP_CONN_IN_PROGRESS;
}
@@ -180,22 +192,24 @@ int rexmpp_tcp_conn_finish (rexmpp_tcp_conn_t *conn) {
int rexmpp_tcp_conn_ipv4_available(rexmpp_tcp_conn_t *conn) {
return (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_SUCCESS &&
- conn->addr_v4 != NULL &&
- conn->addr_v4->h_addr_list[conn->addr_cur_v4 + 1] != NULL);
+ conn->resolved_v4 != NULL &&
+ conn->resolved_v4->data[conn->addr_cur_v4 + 1] != NULL);
}
int rexmpp_tcp_conn_ipv6_available(rexmpp_tcp_conn_t *conn) {
return (conn->resolution_v6 == REXMPP_CONN_RESOLUTION_SUCCESS &&
- conn->addr_v6 != NULL &&
- conn->addr_v6->h_addr_list[conn->addr_cur_v6 + 1] != NULL);
+ conn->resolved_v6 != NULL &&
+ conn->resolved_v6->data[conn->addr_cur_v6 + 1] != NULL);
}
rexmpp_tcp_conn_error_t
-rexmpp_tcp_conn_proceed (rexmpp_tcp_conn_t *conn,
+rexmpp_tcp_conn_proceed (rexmpp_t *s,
+ rexmpp_tcp_conn_t *conn,
fd_set *read_fds,
fd_set *write_fds)
{
- struct timeval now;
+ (void)read_fds; /* Not checking any read FDs at the moment. */
+ struct timespec now;
int i;
/* Check for successful connections. */
@@ -219,7 +233,7 @@ rexmpp_tcp_conn_proceed (rexmpp_tcp_conn_t *conn,
/* Name resolution. */
if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING ||
conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING) {
- ares_process(conn->resolver_channel, read_fds, write_fds);
+ rexmpp_dns_process(s, read_fds, write_fds);
}
if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_FAILURE &&
@@ -235,10 +249,10 @@ rexmpp_tcp_conn_proceed (rexmpp_tcp_conn_t *conn,
if (conn->connection_attempts < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS &&
(rexmpp_tcp_conn_ipv4_available(conn) ||
rexmpp_tcp_conn_ipv6_available(conn))) {
- gettimeofday(&now, NULL);
+ clock_gettime(CLOCK_MONOTONIC, &now);
if (now.tv_sec > conn->next_connection_time.tv_sec ||
(now.tv_sec == conn->next_connection_time.tv_sec &&
- now.tv_usec >= conn->next_connection_time.tv_usec)) {
+ now.tv_nsec >= conn->next_connection_time.tv_nsec)) {
/* Time to attempt a new connection. */
int use_ipv6 = 0;
if (rexmpp_tcp_conn_ipv4_available(conn) &&
@@ -255,39 +269,47 @@ rexmpp_tcp_conn_proceed (rexmpp_tcp_conn_t *conn,
struct sockaddr *addr;
socklen_t addrlen;
int domain;
+ int len;
if (use_ipv6) {
conn->addr_cur_v6++;
+ len = sizeof(addr_v6.sin6_addr);
+ if (len > conn->resolved_v6->len[conn->addr_cur_v6]) {
+ len = conn->resolved_v6->len[conn->addr_cur_v6];
+ }
memcpy(&addr_v6.sin6_addr,
- conn->addr_v6->h_addr_list[conn->addr_cur_v6],
- conn->addr_v6->h_length);
- addr_v6.sin6_family = conn->addr_v6->h_addrtype;
+ conn->resolved_v6->data[conn->addr_cur_v6],
+ len);
+ addr_v6.sin6_family = AF_INET6;
addr_v6.sin6_port = htons(conn->port);
- domain = conn->addr_v6->h_addrtype;
+ addr_v6.sin6_flowinfo = 0;
+ addr_v6.sin6_scope_id = 0;
+ domain = AF_INET6;
addr = (struct sockaddr*)&addr_v6;
addrlen = sizeof(addr_v6);
} else {
conn->addr_cur_v4++;
+ len = sizeof(addr_v4.sin_addr);
+ if (len > conn->resolved_v4->len[conn->addr_cur_v4]) {
+ len = conn->resolved_v4->len[conn->addr_cur_v4];
+ }
memcpy(&addr_v4.sin_addr,
- conn->addr_v4->h_addr_list[conn->addr_cur_v4],
- conn->addr_v4->h_length);
- addr_v4.sin_family = conn->addr_v4->h_addrtype;
+ conn->resolved_v4->data[conn->addr_cur_v4],
+ len);
+ addr_v4.sin_family = AF_INET;
addr_v4.sin_port = htons(conn->port);
- domain = conn->addr_v4->h_addrtype;
+ domain = AF_INET;
addr = (struct sockaddr*)&addr_v4;
addrlen = sizeof(addr_v4);
}
- conn->sockets[conn->connection_attempts] =
- socket(domain, SOCK_STREAM, 0);
- int flags = fcntl(conn->sockets[conn->connection_attempts], F_GETFL, 0);
- fcntl(conn->sockets[conn->connection_attempts], F_SETFL, flags | O_NONBLOCK);
+ conn->sockets[conn->connection_attempts] = rexmpp_tcp_socket(s, domain);
if (connect(conn->sockets[conn->connection_attempts], addr, addrlen)) {
if (errno == EINPROGRESS) {
- gettimeofday(&(conn->next_connection_time), NULL);
- conn->next_connection_time.tv_usec += REXMPP_TCP_CONN_DELAY_MS * 1000;
- if (conn->next_connection_time.tv_usec >= 1000000) {
- conn->next_connection_time.tv_usec -= 1000000;
+ clock_gettime(CLOCK_MONOTONIC, &(conn->next_connection_time));
+ conn->next_connection_time.tv_nsec += REXMPP_TCP_CONN_DELAY_MS * 1000000;
+ if (conn->next_connection_time.tv_nsec >= 1000000000) {
+ conn->next_connection_time.tv_nsec -= 1000000000;
conn->next_connection_time.tv_sec++;
}
conn->connection_attempts++;
@@ -316,28 +338,29 @@ rexmpp_tcp_conn_proceed (rexmpp_tcp_conn_t *conn,
}
}
- gettimeofday(&now, NULL);
+ clock_gettime(CLOCK_MONOTONIC, &now);
if (active_connections ||
conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING ||
conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING ||
(conn->next_connection_time.tv_sec > now.tv_sec ||
(conn->next_connection_time.tv_sec == now.tv_sec &&
- conn->next_connection_time.tv_usec > now.tv_usec))) {
+ conn->next_connection_time.tv_nsec > now.tv_nsec))) {
return REXMPP_CONN_IN_PROGRESS;
} else {
return REXMPP_CONN_FAILURE;
}
}
-int rexmpp_tcp_conn_fds (rexmpp_tcp_conn_t *conn,
+int rexmpp_tcp_conn_fds (rexmpp_t *s,
+ rexmpp_tcp_conn_t *conn,
fd_set *read_fds,
fd_set *write_fds)
{
int max_fd = 0, i;
if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING ||
conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING) {
- max_fd = ares_fds(conn->resolver_channel, read_fds, write_fds);
+ max_fd = rexmpp_dns_fds(s, read_fds, write_fds);
}
for (i = 0; i < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS; i++) {
if (conn->sockets[i] != -1) {
@@ -350,34 +373,35 @@ int rexmpp_tcp_conn_fds (rexmpp_tcp_conn_t *conn,
return max_fd;
}
-struct timeval *rexmpp_tcp_conn_timeout (rexmpp_tcp_conn_t *conn,
- struct timeval *max_tv,
- struct timeval *tv)
+struct timespec *rexmpp_tcp_conn_timeout (rexmpp_t *s,
+ rexmpp_tcp_conn_t *conn,
+ struct timespec *max_tv,
+ struct timespec *tv)
{
- struct timeval now;
- struct timeval *ret = max_tv;
+ struct timespec now;
+ struct timespec *ret = max_tv;
if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING ||
conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING) {
- ret = ares_timeout(conn->resolver_channel, max_tv, tv);
+ ret = rexmpp_dns_timeout(s, max_tv, tv);
}
if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_SUCCESS ||
conn->resolution_v6 == REXMPP_CONN_RESOLUTION_SUCCESS ||
(conn->resolution_v4 == REXMPP_CONN_RESOLUTION_INACTIVE &&
conn->resolution_v6 == REXMPP_CONN_RESOLUTION_INACTIVE)) {
- gettimeofday(&now, NULL);
+ clock_gettime(CLOCK_MONOTONIC, &now);
if (now.tv_sec < conn->next_connection_time.tv_sec ||
(now.tv_sec == conn->next_connection_time.tv_sec &&
- now.tv_usec <= conn->next_connection_time.tv_usec)) {
+ now.tv_nsec <= conn->next_connection_time.tv_nsec)) {
if (ret == NULL ||
ret->tv_sec > conn->next_connection_time.tv_sec - now.tv_sec ||
(ret->tv_sec == conn->next_connection_time.tv_sec - now.tv_sec &&
- ret->tv_usec > conn->next_connection_time.tv_usec - now.tv_usec)) {
+ ret->tv_nsec > conn->next_connection_time.tv_nsec - now.tv_nsec)) {
ret = tv;
tv->tv_sec = conn->next_connection_time.tv_sec - now.tv_sec;
- if (conn->next_connection_time.tv_usec > now.tv_usec) {
- tv->tv_usec = conn->next_connection_time.tv_usec - now.tv_usec;
+ if (conn->next_connection_time.tv_nsec > now.tv_nsec) {
+ tv->tv_nsec = conn->next_connection_time.tv_nsec - now.tv_nsec;
} else {
- tv->tv_usec = conn->next_connection_time.tv_usec + 1000000 - now.tv_usec;
+ tv->tv_nsec = conn->next_connection_time.tv_nsec + 1000000000 - now.tv_nsec;
tv->tv_sec--;
}
}
diff --git a/src/rexmpp_tcp.h b/src/rexmpp_tcp.h
index e4a6dff..8ee32a0 100644
--- a/src/rexmpp_tcp.h
+++ b/src/rexmpp_tcp.h
@@ -19,13 +19,16 @@
#ifndef REXMPP_TCP_H
#define REXMPP_TCP_H
+#include <sys/time.h>
+#include <stdbool.h>
+
+#include "rexmpp.h"
+#include "rexmpp_dns.h"
+
#define REXMPP_TCP_MAX_CONNECTION_ATTEMPTS 20
#define REXMPP_TCP_IPV6_DELAY_MS 50
#define REXMPP_TCP_CONN_DELAY_MS 250
-typedef enum rexmpp_tcp_conn_resolution_status
-rexmpp_tcp_conn_resolution_status_t;
-
/**
@brief Resolution status.
*/
@@ -40,7 +43,8 @@ enum rexmpp_tcp_conn_resolution_status {
REXMPP_CONN_RESOLUTION_FAILURE
};
-typedef enum rexmpp_tcp_conn_error rexmpp_tcp_conn_error_t;
+typedef enum rexmpp_tcp_conn_resolution_status
+rexmpp_tcp_conn_resolution_status_t;
/**
@brief Connection errors.
@@ -59,6 +63,8 @@ enum rexmpp_tcp_conn_error {
REXMPP_CONN_ERROR
};
+typedef enum rexmpp_tcp_conn_error rexmpp_tcp_conn_error_t;
+
typedef struct rexmpp_tcp_connection rexmpp_tcp_conn_t;
/** @brief A connection establishment structure. */
@@ -68,19 +74,13 @@ struct rexmpp_tcp_connection {
/** @brief A port we are connecting to. */
uint16_t port;
- /** @brief Resolver channel. */
- ares_channel resolver_channel;
- /** @brief Resolver error is stored here when
- ::REXMPP_CONN_RESOLVER_ERROR is returned. */
- int resolver_error;
-
/** @brief State of A record resolution. */
enum rexmpp_tcp_conn_resolution_status resolution_v4;
/** @brief Status of A record resolution, as returned by the
resolver. */
int resolver_status_v4;
- /** @brief AF_INET (IPv4) hostent structure. */
- struct hostent *addr_v4;
+ /** @brief Resolved A records. */
+ rexmpp_dns_result_t *resolved_v4;
/** @brief The AF_INET address number we are currently at. */
int addr_cur_v4;
@@ -89,8 +89,8 @@ struct rexmpp_tcp_connection {
/** @brief Status of AAAA record resolution, as returned by the
resolver. */
int resolver_status_v6;
- /** @brief AF_INET6 (IPv6) hostent structure. */
- struct hostent *addr_v6;
+ /** @brief Resolved AAAA records. */
+ rexmpp_dns_result_t *resolved_v6;
/** @brief The AF_INET6 address number we are currently at. */
int addr_cur_v6;
@@ -99,14 +99,18 @@ struct rexmpp_tcp_connection {
/** @brief The number of connection attempts so far. */
int connection_attempts;
- /** @brief Next scheduled connection time. */
- struct timeval next_connection_time;
+ /** @brief Next scheduled connection time (monotonic). */
+ struct timespec next_connection_time;
/** @brief File descriptor of a connected socket. */
int fd;
+ /** @brief Whether the A or AAAA records used to establish the final
+ connection were verified with DNSSEC. */
+ bool dns_secure;
};
/**
@brief Initiates a connection.
+ @param[in] s ::rexmpp
@param[out] conn An allocated connection structure.
@param[in] host A host to connect to. This could be a domain name,
or a textual representation of an IPv4 or an IPv6 address.
@@ -114,19 +118,22 @@ struct rexmpp_tcp_connection {
@returns A ::rexmpp_tcp_conn_error state.
*/
rexmpp_tcp_conn_error_t
-rexmpp_tcp_conn_init (rexmpp_tcp_conn_t *conn,
+rexmpp_tcp_conn_init (rexmpp_t *s,
+ rexmpp_tcp_conn_t *conn,
const char *host,
uint16_t port);
/**
@brief Continues a connection process.
+ @param[in] s ::rexmpp
@param[in,out] conn An active connection structure.
@param[in] read_fds File descriptors available for reading from.
@param[in] write_fds File descriptors available for writing to.
@returns A ::rexmpp_tcp_conn_error state.
*/
rexmpp_tcp_conn_error_t
-rexmpp_tcp_conn_proceed (rexmpp_tcp_conn_t *conn,
+rexmpp_tcp_conn_proceed (rexmpp_t *s,
+ rexmpp_tcp_conn_t *conn,
fd_set *read_fds,
fd_set *write_fds);
@@ -151,6 +158,7 @@ int rexmpp_tcp_conn_finish (rexmpp_tcp_conn_t *conn);
File descriptors are only added to an @c fd_set, so the ones it
already contains will not be lost.
+ @param[in] s ::rexmpp
@param[in] conn An active connection structure.
@param[out] read_fds File descriptors a connection process is
interested in reading from.
@@ -158,20 +166,23 @@ int rexmpp_tcp_conn_finish (rexmpp_tcp_conn_t *conn);
interested in writing to.
@returns Maximum file descriptor number, plus 1.
*/
-int rexmpp_tcp_conn_fds (rexmpp_tcp_conn_t *conn,
+int rexmpp_tcp_conn_fds (rexmpp_t *s,
+ rexmpp_tcp_conn_t *conn,
fd_set *read_fds,
fd_set *write_fds);
/**
@brief Reports timeouts.
+ @param[in] s ::rexmpp
@param[in] conn An active connection structure.
@param[in] max_tv An existing maximum timeout.
- @param[out] tv A timeval structure to store a new timeout in.
+ @param[out] tv A timespec structure to store a new timeout in.
@returns A pointer to either max_tv or tv, depending on which one
is smaller.
*/
-struct timeval *rexmpp_tcp_conn_timeout (rexmpp_tcp_conn_t *conn,
- struct timeval *max_tv,
- struct timeval *tv);
+struct timespec *rexmpp_tcp_conn_timeout (rexmpp_t *s,
+ rexmpp_tcp_conn_t *conn,
+ struct timespec *max_tv,
+ struct timespec *tv);
#endif
diff --git a/src/rexmpp_tcp.rs b/src/rexmpp_tcp.rs
new file mode 100644
index 0000000..56c204c
--- /dev/null
+++ b/src/rexmpp_tcp.rs
@@ -0,0 +1,469 @@
+use std::os::raw::{c_int, c_char};
+use libc::*;
+use std::ptr::{null_mut,null};
+use std::mem;
+use errno::{errno};
+
+use super::{rexmpp_dns, rexmpp};
+
+
+#[link(name = "libc")]
+extern {
+ fn inet_pton (af: c_int, src: *const c_char, dst: *mut c_void) -> c_int;
+}
+
+
+const REXMPP_TCP_MAX_CONNECTION_ATTEMPTS: usize = 20;
+const REXMPP_TCP_IPV6_DELAY_MS: i64 = 50;
+const REXMPP_TCP_CONN_DELAY_MS: i64 = 250;
+
+#[derive(PartialEq, Copy, Clone)]
+#[repr(C)]
+pub enum ResolutionStatus {
+ Inactive,
+ Waiting,
+ Success,
+ Failure
+}
+
+#[derive(PartialEq, Copy, Clone)]
+#[repr(C)]
+pub enum ConnectionError {
+ Done,
+ ResolverError,
+ InProgress,
+ Failure,
+ Error
+}
+
+#[repr(C)]
+pub struct RexmppTCPConnection {
+ pub host: *const c_char,
+ pub port: u16,
+ pub resolution_v4: ResolutionStatus,
+ pub resolver_status_v4: c_int,
+ pub resolved_v4: *mut rexmpp_dns::RexmppDNSResult,
+ pub addr_cur_v4: c_int,
+ pub resolution_v6: ResolutionStatus,
+ pub resolver_status_v6: c_int,
+ pub resolved_v6: *mut rexmpp_dns::RexmppDNSResult,
+ pub addr_cur_v6: c_int,
+ pub sockets: [c_int; REXMPP_TCP_MAX_CONNECTION_ATTEMPTS],
+ pub connection_attempts: c_int,
+ pub next_connection_time: timespec,
+ pub fd: c_int,
+ pub dns_secure: bool
+}
+
+#[no_mangle]
+unsafe extern "C"
+fn rexmpp_tcp_dns_aaaa_cb (_s: *mut rexmpp::Rexmpp,
+ ptr: *mut c_void,
+ result: *mut rexmpp_dns::RexmppDNSResult)
+ -> () {
+ let conn = ptr as *mut RexmppTCPConnection;
+ (*conn).resolved_v6 = result;
+ if result != null_mut() {
+ (*conn).resolution_v6 = ResolutionStatus::Success;
+ (*conn).addr_cur_v6 = -1;
+ } else {
+ (*conn).resolution_v6 = ResolutionStatus::Failure;
+ }
+}
+
+#[no_mangle]
+unsafe extern "C"
+fn rexmpp_tcp_dns_a_cb (_s: *mut rexmpp::Rexmpp,
+ ptr: *mut c_void,
+ result: *mut rexmpp_dns::RexmppDNSResult)
+ -> () {
+ let conn = ptr as *mut RexmppTCPConnection;
+ (*conn).resolved_v4 = result;
+ if result != null_mut() {
+ (*conn).resolution_v4 = ResolutionStatus::Success;
+ (*conn).addr_cur_v4 = -1;
+ if (*conn).resolution_v6 == ResolutionStatus::Waiting {
+ // Wait a bit (usually 50 ms) for IPv6
+ clock_gettime(CLOCK_MONOTONIC, &mut (*conn).next_connection_time);
+ (*conn).next_connection_time.tv_nsec += REXMPP_TCP_IPV6_DELAY_MS * 1000000;
+ if (*conn).next_connection_time.tv_nsec >= 1000000000 {
+ (*conn).next_connection_time.tv_nsec -= 1000000000;
+ (*conn).next_connection_time.tv_sec += 1;
+ }
+ }
+ } else {
+ (*conn).resolution_v4 = ResolutionStatus::Failure;
+ }
+}
+
+
+#[no_mangle]
+unsafe extern "C"
+fn rexmpp_tcp_cleanup (conn: *mut RexmppTCPConnection) -> () {
+ for i in 0..=(REXMPP_TCP_MAX_CONNECTION_ATTEMPTS - 1) {
+ if (*conn).sockets[i] != -1 && (*conn).sockets[i] != (*conn).fd {
+ close((*conn).sockets[i]);
+ (*conn).sockets[i] = -1;
+ }
+ }
+ if (*conn).resolution_v4 != ResolutionStatus::Inactive {
+ (*conn).resolution_v4 = ResolutionStatus::Inactive;
+ (*conn).resolution_v6 = ResolutionStatus::Inactive;
+ }
+ if (*conn).resolved_v4 != null_mut() {
+ rexmpp_dns::rexmpp_dns_result_free((*conn).resolved_v4);
+ (*conn).resolved_v4 = null_mut();
+ }
+ if (*conn).resolved_v6 != null_mut() {
+ rexmpp_dns::rexmpp_dns_result_free((*conn).resolved_v6);
+ (*conn).resolved_v6 = null_mut();
+ }
+}
+
+#[no_mangle]
+unsafe extern "C"
+fn rexmpp_tcp_connected (conn: *mut RexmppTCPConnection, fd: c_int)
+ -> ConnectionError {
+ let mut sa_ptr = mem::MaybeUninit::<sockaddr>::uninit();
+ let mut sa_len : socklen_t = mem::size_of::<sockaddr>() as u32;
+ getsockname(fd, sa_ptr.as_mut_ptr(), &mut sa_len);
+ let sa = sa_ptr.assume_init();
+ if sa.sa_family == (AF_INET as u16)
+ && (*conn).resolved_v4 != null_mut() {
+ (*conn).dns_secure = (*(*conn).resolved_v4).secure;
+ }
+ else if sa.sa_family == (AF_INET6 as u16)
+ && (*conn).resolved_v6 != null_mut() {
+ (*conn).dns_secure = (*(*conn).resolved_v6).secure;
+ }
+ (*conn).fd = fd;
+ rexmpp_tcp_cleanup(conn);
+ return ConnectionError::Done;
+}
+
+#[no_mangle]
+unsafe extern "C"
+fn rexmpp_tcp_socket (s: *mut rexmpp::Rexmpp, domain: c_int) -> c_int {
+ let sock: c_int = socket(domain, SOCK_STREAM, 0);
+
+ // Make it non-blocking
+ let flags: c_int = fcntl(sock, F_GETFL, 0);
+ fcntl(sock, F_SETFL, flags | O_NONBLOCK);
+
+ // Call the socket creation callback, if provided
+ ((*s).socket_cb).map(|cb| cb(s, sock));
+
+ return sock;
+}
+
+#[no_mangle]
+unsafe extern "C"
+fn rexmpp_tcp_conn_init (s: *mut rexmpp::Rexmpp,
+ conn: *mut RexmppTCPConnection,
+ host: *const c_char,
+ port: u16)
+ -> ConnectionError {
+ for i in 0..=(REXMPP_TCP_MAX_CONNECTION_ATTEMPTS - 1) {
+ (*conn).sockets[i] = -1;
+ }
+ (*conn).connection_attempts = 0;
+ (*conn).port = port;
+ (*conn).resolved_v4 = null_mut();
+ (*conn).resolved_v6 = null_mut();
+ (*conn).fd = -1;
+ (*conn).dns_secure = false;
+ (*conn).next_connection_time.tv_sec = 0;
+ (*conn).next_connection_time.tv_nsec = 0;
+
+ (*conn).resolution_v4 = ResolutionStatus::Inactive;
+ (*conn).resolution_v6 = ResolutionStatus::Inactive;
+
+ let mut addr_v4 = mem::MaybeUninit::<sockaddr_in>::uninit();
+ if inet_pton(AF_INET, host,
+ &mut ((*addr_v4.as_mut_ptr()).sin_addr)
+ as *mut in_addr as *mut c_void) == 1 {
+ (*addr_v4.as_mut_ptr()).sin_family = AF_INET as u16;
+ (*addr_v4.as_mut_ptr()).sin_port = port.to_be();
+ (*conn).sockets[(*conn).connection_attempts as usize] =
+ rexmpp_tcp_socket(s, AF_INET);
+ if connect((*conn).sockets[(*conn).connection_attempts as usize],
+ addr_v4.as_mut_ptr() as *mut sockaddr,
+ mem::size_of::<sockaddr_in>() as u32) != 0 {
+ if errno().0 != EINPROGRESS {
+ return ConnectionError::Error;
+ }
+ } else {
+ return rexmpp_tcp_connected(conn,
+ (*conn).sockets[(*conn).connection_attempts as usize]);
+ }
+ (*conn).connection_attempts += 1;
+ return ConnectionError::InProgress;
+ }
+
+ let mut addr_v6 = mem::MaybeUninit::<sockaddr_in6>::uninit();
+ if inet_pton(AF_INET6, host,
+ &mut ((*addr_v6.as_mut_ptr()).sin6_addr)
+ as *mut in6_addr as *mut c_void) == 1 {
+ (*addr_v6.as_mut_ptr()).sin6_family = AF_INET as u16;
+ (*addr_v6.as_mut_ptr()).sin6_port = port.to_be();
+ (*addr_v6.as_mut_ptr()).sin6_flowinfo = 0;
+ (*addr_v6.as_mut_ptr()).sin6_scope_id = 0;
+ (*conn).sockets[(*conn).connection_attempts as usize] =
+ rexmpp_tcp_socket(s, AF_INET6);
+ if connect((*conn).sockets[(*conn).connection_attempts as usize],
+ addr_v6.as_mut_ptr() as *mut sockaddr,
+ mem::size_of::<sockaddr_in6>() as u32) != 0 {
+ if errno().0 != EINPROGRESS {
+ return ConnectionError::Error;
+ }
+ } else {
+ return rexmpp_tcp_connected(conn,
+ (*conn).sockets[(*conn).connection_attempts as usize]);
+ }
+ (*conn).connection_attempts += 1;
+ return ConnectionError::InProgress;
+ }
+ (*conn).resolution_v4 = ResolutionStatus::Waiting;
+ (*conn).resolution_v6 = ResolutionStatus::Waiting;
+
+ rexmpp_dns::rexmpp_dns_resolve(s, host, 28, 1,
+ conn as *mut c_void,
+ rexmpp_tcp_dns_aaaa_cb);
+ rexmpp_dns::rexmpp_dns_resolve(s, host, 1, 1,
+ conn as *mut c_void,
+ rexmpp_tcp_dns_a_cb);
+ return ConnectionError::InProgress;
+}
+
+#[no_mangle]
+unsafe extern "C"
+fn rexmpp_tcp_conn_finish (conn: *mut RexmppTCPConnection) -> c_int {
+ rexmpp_tcp_cleanup(conn);
+ return (*conn).fd;
+}
+
+#[no_mangle]
+unsafe extern "C"
+fn rexmpp_tcp_conn_ipv4_available (conn: *mut RexmppTCPConnection) -> bool {
+ (*conn).resolution_v4 == ResolutionStatus::Success
+ && (*conn).resolved_v4 != null_mut()
+ && *(*(*conn).resolved_v4).data
+ .offset(((*conn).addr_cur_v4 + 1) as isize) != null_mut()
+}
+
+#[no_mangle]
+unsafe extern "C"
+fn rexmpp_tcp_conn_ipv6_available (conn: *mut RexmppTCPConnection) -> bool {
+ (*conn).resolution_v6 == ResolutionStatus::Success
+ && (*conn).resolved_v6 != null_mut()
+ && *(*(*conn).resolved_v6).data
+ .offset(((*conn).addr_cur_v6 + 1) as isize) != null_mut()
+}
+
+#[no_mangle]
+unsafe extern "C"
+fn rexmpp_tcp_conn_proceed (s: *mut rexmpp::Rexmpp,
+ conn: *mut RexmppTCPConnection,
+ read_fds: *mut fd_set,
+ write_fds: *mut fd_set) -> ConnectionError {
+ // Check for successful connections.
+ for i in 0..=(REXMPP_TCP_MAX_CONNECTION_ATTEMPTS - 1) {
+ if (*conn).sockets[i] != -1 && FD_ISSET((*conn).sockets[i], write_fds) {
+ let mut err: c_int = 0;
+ let mut err_len: socklen_t = mem::size_of::<c_int>() as u32;
+ if getsockopt((*conn).sockets[i], SOL_SOCKET, SO_ERROR,
+ &mut err as *mut c_int as *mut c_void,
+ &mut err_len) < 0 {
+ return ConnectionError::Error;
+ } else {
+ if err == 0 {
+ return rexmpp_tcp_connected(conn, (*conn).sockets[i]);
+ } else if err != EINPROGRESS {
+ close((*conn).sockets[i]);
+ (*conn).sockets[i] = -1;
+ }
+ }
+ }
+ }
+
+ // Name resolution
+ if (*conn).resolution_v4 == ResolutionStatus::Waiting
+ || (*conn).resolution_v6 == ResolutionStatus::Waiting {
+ rexmpp_dns::rexmpp_dns_process(s, read_fds, write_fds);
+ }
+ if (*conn).resolution_v4 == ResolutionStatus::Failure
+ && (*conn).resolution_v6 == ResolutionStatus::Failure {
+ // Failed to resolve anything
+ return ConnectionError::Failure;
+ }
+
+ // New connections
+ let mut repeat: bool;
+ let mut now = mem::MaybeUninit::<timespec>::uninit();
+ let now_ptr = now.as_mut_ptr();
+ loop {
+ repeat = false;
+ if (*conn).connection_attempts < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS as i32
+ && (rexmpp_tcp_conn_ipv4_available(conn)
+ || rexmpp_tcp_conn_ipv6_available(conn)) {
+ clock_gettime(CLOCK_MONOTONIC, now_ptr);
+ if (*now_ptr).tv_sec > (*conn).next_connection_time.tv_sec
+ || ((*now_ptr).tv_sec == (*conn).next_connection_time.tv_sec
+ && (*now_ptr).tv_nsec >= (*conn).next_connection_time.tv_nsec) {
+ // Time to attempt a new connection
+ let mut use_ipv6 = false;
+ if rexmpp_tcp_conn_ipv4_available(conn) &&
+ rexmpp_tcp_conn_ipv6_available(conn) {
+ if (*conn).addr_cur_v4 >= (*conn).addr_cur_v6 {
+ use_ipv6 = true;
+ }
+ } else if rexmpp_tcp_conn_ipv6_available(conn) {
+ use_ipv6 = true;
+ }
+
+ let addr: *mut sockaddr;
+ let addrlen: socklen_t;
+ let domain: c_int;
+ if use_ipv6 {
+ let mut addr_v6: sockaddr_in6 = mem::zeroed();
+ (*conn).addr_cur_v6 += 1;
+ let len = (mem::size_of::<in6_addr>() as i32)
+ .min(*(*(*conn).resolved_v6).len.offset((*conn).addr_cur_v6 as isize));
+ memcpy(&mut addr_v6.sin6_addr as *mut in6_addr as *mut c_void,
+ *(*(*conn).resolved_v6).data.offset((*conn).addr_cur_v6 as isize),
+ len as usize);
+ addr_v6.sin6_family = AF_INET6 as u16;
+ addr_v6.sin6_port = (*conn).port.to_be();
+ addr_v6.sin6_flowinfo = 0;
+ addr_v6.sin6_scope_id = 0;
+ domain = AF_INET6;
+ addr = &mut addr_v6 as *mut sockaddr_in6 as *mut sockaddr;
+ addrlen = mem::size_of::<sockaddr_in6>() as u32;
+ } else {
+ let mut addr_v4: sockaddr_in = mem::zeroed();
+ (*conn).addr_cur_v4 += 1;
+ let len = (mem::size_of::<in_addr>() as i32)
+ .min(*(*(*conn).resolved_v4).len.offset((*conn).addr_cur_v4 as isize));
+ memcpy(&mut addr_v4.sin_addr as *mut in_addr as *mut c_void,
+ *(*(*conn).resolved_v4).data.offset((*conn).addr_cur_v4 as isize),
+ len as usize);
+ addr_v4.sin_family = AF_INET as u16;
+ addr_v4.sin_port = (*conn).port.to_be();
+ domain = AF_INET;
+ addr = &mut addr_v4 as *mut sockaddr_in as *mut sockaddr;
+ addrlen = mem::size_of::<sockaddr_in>() as u32;
+ }
+ (*conn).sockets[(*conn).connection_attempts as usize] =
+ rexmpp_tcp_socket(s, domain);
+ if connect((*conn).sockets[(*conn).connection_attempts as usize],
+ addr, addrlen) != 0 {
+ if errno().0 == EINPROGRESS {
+ clock_gettime(CLOCK_MONOTONIC, &mut (*conn).next_connection_time);
+ (*conn).next_connection_time.tv_nsec +=
+ REXMPP_TCP_CONN_DELAY_MS * 1000000;
+ if (*conn).next_connection_time.tv_nsec >= 1000000000 {
+ (*conn).next_connection_time.tv_nsec -= 1000000000;
+ (*conn).next_connection_time.tv_sec += 1;
+ }
+ (*conn).connection_attempts += 1;
+ } else {
+ close((*conn).sockets[(*conn).connection_attempts as usize]);
+ (*conn).sockets[(*conn).connection_attempts as usize] = -1;
+ if (*conn).connection_attempts < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS as i32
+ && (rexmpp_tcp_conn_ipv4_available(conn) ||
+ rexmpp_tcp_conn_ipv6_available(conn)) {
+ repeat = true;
+ }
+ }
+ } else {
+ return rexmpp_tcp_connected(conn,
+ (*conn).sockets[(*conn).connection_attempts as usize]);
+ }
+ }
+ }
+ if ! repeat {
+ break;
+ }
+ }
+
+ let mut active_connections = false;
+ for i in 0..=(REXMPP_TCP_MAX_CONNECTION_ATTEMPTS - 1) {
+ if (*conn).sockets[i] != -1 {
+ active_connections = true;
+ break;
+ }
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, now_ptr);
+
+ if active_connections
+ || (*conn).resolution_v4 == ResolutionStatus::Waiting
+ || (*conn).resolution_v6 == ResolutionStatus::Waiting
+ || ((*conn).next_connection_time.tv_sec > (*now_ptr).tv_sec
+ || ((*conn).next_connection_time.tv_sec == (*now_ptr).tv_sec
+ && (*conn).next_connection_time.tv_nsec > (*now_ptr).tv_nsec)) {
+ ConnectionError::InProgress
+ } else {
+ ConnectionError::Failure
+ }
+}
+
+#[no_mangle]
+unsafe extern "C"
+fn rexmpp_tcp_conn_fds (s: *mut rexmpp::Rexmpp,
+ conn: *mut RexmppTCPConnection,
+ read_fds: *mut fd_set,
+ write_fds: *mut fd_set) -> c_int {
+ let mut max_fd: c_int = 0;
+ if (*conn).resolution_v4 == ResolutionStatus::Waiting
+ || (*conn).resolution_v6 == ResolutionStatus::Waiting {
+ max_fd = rexmpp_dns::rexmpp_dns_fds(s, read_fds, write_fds);
+ }
+ for i in 0..=(REXMPP_TCP_MAX_CONNECTION_ATTEMPTS - 1) {
+ if (*conn).sockets[i] != -1 {
+ FD_SET((*conn).sockets[i], write_fds);
+ if max_fd < (*conn).sockets[i] + 1 {
+ max_fd = (*conn).sockets[i] + 1;
+ }
+ }
+ }
+ max_fd
+}
+
+#[no_mangle]
+unsafe extern "C"
+fn rexmpp_tcp_conn_timeout (s: *mut rexmpp::Rexmpp,
+ conn: *mut RexmppTCPConnection,
+ max_tv: *mut timespec,
+ tv: *mut timespec) -> *mut timespec {
+ let mut now: timespec = mem::zeroed();
+ let mut ret: *mut timespec = max_tv;
+ if (*conn).resolution_v4 == ResolutionStatus::Waiting
+ || (*conn).resolution_v6 == ResolutionStatus::Waiting {
+ ret = rexmpp_dns::rexmpp_dns_timeout(s, max_tv, tv);
+ }
+ if (*conn).resolution_v4 == ResolutionStatus::Success
+ || (*conn).resolution_v6 == ResolutionStatus::Success
+ || ((*conn).resolution_v4 == ResolutionStatus::Inactive
+ && (*conn).resolution_v4 == ResolutionStatus::Inactive) {
+ clock_gettime(CLOCK_MONOTONIC, &mut now);
+ if now.tv_sec < (*conn).next_connection_time.tv_sec
+ || (now.tv_sec == (*conn).next_connection_time.tv_sec
+ && now.tv_nsec <= (*conn).next_connection_time.tv_nsec) {
+ if ret == null_mut()
+ || (*ret).tv_sec > (*conn).next_connection_time.tv_sec - now.tv_sec
+ || ((*ret).tv_sec == (*conn).next_connection_time.tv_sec - now.tv_sec
+ && (*ret).tv_nsec > (*conn).next_connection_time.tv_nsec - now.tv_sec) {
+ ret = tv;
+ (*tv).tv_sec = (*conn).next_connection_time.tv_sec - now.tv_sec;
+ if (*conn).next_connection_time.tv_nsec > now.tv_nsec {
+ (*tv).tv_nsec = (*conn).next_connection_time.tv_nsec - now.tv_nsec;
+ } else {
+ (*tv).tv_nsec = (*conn).next_connection_time.tv_nsec + 1000000000 - now.tv_nsec;
+ (*tv).tv_sec -= 1;
+ }
+ }
+ }
+ }
+ ret
+}
diff --git a/src/rexmpp_tls.c b/src/rexmpp_tls.c
new file mode 100644
index 0000000..2a7c903
--- /dev/null
+++ b/src/rexmpp_tls.c
@@ -0,0 +1,987 @@
+/**
+ @file rexmpp_tls.c
+ @brief TLS abstraction
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+*/
+
+#include <syslog.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "config.h"
+
+#if defined(USE_GNUTLS)
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#include <gnutls/x509.h>
+#include <gnutls/dane.h>
+#include <gnutls/dtls.h>
+#elif defined(USE_OPENSSL)
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/err.h>
+#endif
+
+#include "rexmpp.h"
+#include "rexmpp_digest.h"
+#include "rexmpp_tls.h"
+
+rexmpp_tls_t *rexmpp_jingle_component_dtls(void *p);
+ssize_t
+rexmpp_jingle_dtls_push_func (void *p, const void *data, size_t size);
+int rexmpp_jingle_dtls_pull_timeout_func (void *p,
+ unsigned int ms);
+
+#if defined(USE_OPENSSL)
+rexmpp_tls_err_t rexmpp_process_openssl_ret (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ const char *func,
+ int ret)
+{
+ int err = SSL_get_error(tls_ctx->openssl_conn, ret);
+ tls_ctx->openssl_direction = REXMPP_OPENSSL_NONE;
+ if (ret == 1) {
+ return REXMPP_TLS_SUCCESS;
+ } else if (err == SSL_ERROR_WANT_READ) {
+ tls_ctx->openssl_direction = REXMPP_OPENSSL_READ;
+ return REXMPP_TLS_E_AGAIN;
+ } else if (err == SSL_ERROR_WANT_WRITE) {
+ tls_ctx->openssl_direction = REXMPP_OPENSSL_WRITE;
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ rexmpp_log(s, LOG_ERR, "OpenSSL error %d (ret %d) in %s",
+ err, ret, func);
+ ERR_print_errors_fp(stderr);
+ return REXMPP_TLS_E_OTHER;
+ }
+}
+#endif
+
+rexmpp_tls_t *rexmpp_tls_ctx_new (rexmpp_t *s, int dtls) {
+ rexmpp_tls_t *tls_ctx = malloc(sizeof(rexmpp_tls_t));
+ if (tls_ctx == NULL) {
+ rexmpp_log(s, LOG_CRIT, "Failed to allocate memory for a TLS context");
+ return NULL;
+ }
+#if defined(USE_GNUTLS)
+ (void)dtls;
+ int err;
+ tls_ctx->tls_session_data = NULL;
+ tls_ctx->tls_session_data_size = 0;
+
+ err = gnutls_certificate_allocate_credentials(&(tls_ctx->gnutls_cred));
+ if (err) {
+ rexmpp_log(s, LOG_CRIT, "gnutls credentials allocation error: %s",
+ gnutls_strerror(err));
+ free(tls_ctx);
+ return NULL;
+ }
+ if (! dtls) {
+ err = gnutls_certificate_set_x509_system_trust(tls_ctx->gnutls_cred);
+ }
+ if (err < 0) {
+ rexmpp_log(s, LOG_CRIT, "Certificates loading error: %s",
+ gnutls_strerror(err));
+ free(tls_ctx);
+ return NULL;
+ }
+
+ tls_ctx->dtls_buf_len = 0;
+#elif defined(USE_OPENSSL)
+ tls_ctx->openssl_direction = REXMPP_OPENSSL_NONE;
+ tls_ctx->openssl_conn = NULL;
+ tls_ctx->openssl_ctx = SSL_CTX_new(dtls
+ ? DTLS_method()
+ : TLS_method());
+ if (tls_ctx->openssl_ctx == NULL) {
+ rexmpp_log(s, LOG_CRIT, "OpenSSL context creation error");
+ free(tls_ctx);
+ return NULL;
+ }
+ SSL_CTX_set_verify(tls_ctx->openssl_ctx, SSL_VERIFY_PEER, NULL);
+ if (SSL_CTX_set_default_verify_paths(tls_ctx->openssl_ctx) == 0) {
+ rexmpp_log(s, LOG_CRIT,
+ "Failed to set default verify paths for OpenSSL context");
+ SSL_CTX_free(tls_ctx->openssl_ctx);
+ tls_ctx->openssl_ctx = NULL;
+ free(tls_ctx);
+ return NULL;
+ }
+#else
+ (void)s;
+ (void)dtls;
+#endif
+ return tls_ctx;
+}
+
+void rexmpp_tls_ctx_free (rexmpp_tls_t *tls_ctx) {
+#if defined(USE_GNUTLS)
+ gnutls_certificate_free_credentials(tls_ctx->gnutls_cred);
+ if (tls_ctx->tls_session_data != NULL) {
+ free(tls_ctx->tls_session_data);
+ tls_ctx->tls_session_data = NULL;
+ }
+#elif defined(USE_OPENSSL)
+ if (tls_ctx->openssl_ctx != NULL) {
+ SSL_CTX_free(tls_ctx->openssl_ctx);
+ }
+ tls_ctx->openssl_ctx = NULL;
+#endif
+ free(tls_ctx);
+}
+
+int rexmpp_tls_init (rexmpp_t *s) {
+#if defined(USE_OPENSSL)
+ SSL_library_init();
+ SSL_load_error_strings();
+#endif
+ s->tls = rexmpp_tls_ctx_new(s, 0);
+ return (s->tls == NULL);
+}
+
+void rexmpp_tls_session_free (rexmpp_tls_t *tls_ctx) {
+#if defined(USE_GNUTLS)
+ gnutls_deinit(tls_ctx->gnutls_session);
+#elif defined(USE_OPENSSL)
+ if (tls_ctx->openssl_conn != NULL) {
+ SSL_free(tls_ctx->openssl_conn);
+ tls_ctx->openssl_conn = NULL;
+ /* bio_conn is freed implicitly by SSL_free. */
+ tls_ctx->bio_conn = NULL;
+ }
+ if (tls_ctx->bio_io != NULL) {
+ BIO_free(tls_ctx->bio_io);
+ tls_ctx->bio_io = NULL;
+ }
+ tls_ctx->openssl_direction = REXMPP_OPENSSL_NONE;
+#else
+ (void)tls_ctx;
+#endif
+}
+
+void rexmpp_tls_cleanup (rexmpp_t *s) {
+ if (s->tls_state != REXMPP_TLS_INACTIVE &&
+ s->tls_state != REXMPP_TLS_AWAITING_DIRECT) {
+ rexmpp_tls_session_free(s->tls);
+ }
+}
+
+void rexmpp_tls_deinit (rexmpp_t *s) {
+ if (s->tls != NULL) {
+ rexmpp_tls_ctx_free(s->tls);
+ s->tls = NULL;
+ }
+}
+
+#if defined(USE_GNUTLS)
+ssize_t
+rexmpp_dtls_jingle_pull_func_gnutls (gnutls_transport_ptr_t p,
+ void *data,
+ size_t size)
+{
+ rexmpp_tls_t *tls_ctx = rexmpp_jingle_component_dtls(p);
+ ssize_t received;
+
+ char *tls_buf = tls_ctx->dtls_buf;
+ size_t *tls_buf_len = &(tls_ctx->dtls_buf_len);
+
+ rexmpp_tls_err_t ret = REXMPP_TLS_SUCCESS;
+ if (*tls_buf_len > 0) {
+ if (size >= *tls_buf_len) {
+ memcpy(data, tls_buf, *tls_buf_len);
+ received = *tls_buf_len;
+ *tls_buf_len = 0;
+ } else {
+ if (size > DTLS_SRTP_BUF_SIZE) {
+ size = DTLS_SRTP_BUF_SIZE;
+ }
+ memcpy(data, tls_buf, size);
+ memmove(tls_buf, tls_buf + size, DTLS_SRTP_BUF_SIZE - size);
+ received = size;
+ *tls_buf_len = *tls_buf_len - size;
+ }
+ } else {
+ ret = REXMPP_TLS_E_AGAIN;
+ }
+
+ if (ret == REXMPP_TLS_SUCCESS) {
+ return received;
+ } else if (ret == REXMPP_TLS_E_AGAIN) {
+ gnutls_transport_set_errno(tls_ctx->gnutls_session, EAGAIN);
+ }
+ return -1;
+}
+#endif
+
+#if defined(USE_OPENSSL)
+long rexmpp_dtls_openssl_bio_cb(BIO *b, int oper, const char *argp,
+ size_t len, int argi,
+ long argl, int ret, size_t *processed) {
+ (void)argi;
+ (void)argl;
+ (void)processed;
+ if (oper == BIO_CB_WRITE) {
+ rexmpp_jingle_dtls_push_func(BIO_get_callback_arg(b), argp, len);
+ }
+ return ret;
+}
+#endif
+
+#if defined(USE_OPENSSL)
+int rexmpp_openssl_verify_accept_all (int preverify_ok,
+ X509_STORE_CTX *x509_ctx)
+{
+ (void)preverify_ok;
+ (void)x509_ctx;
+ return 1;
+}
+#endif
+
+rexmpp_tls_err_t
+rexmpp_dtls_connect (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ void *user_data,
+ int client) {
+#if defined(USE_GNUTLS)
+ gnutls_session_t *tls_session = &(tls_ctx->gnutls_session);
+ gnutls_init(tls_session,
+ (client ? GNUTLS_CLIENT : GNUTLS_SERVER) |
+ GNUTLS_DATAGRAM |
+ GNUTLS_NONBLOCK);
+ if (! client) {
+ gnutls_certificate_server_set_request(*tls_session, GNUTLS_CERT_REQUIRE);
+ }
+ gnutls_set_default_priority(*tls_session);
+ rexmpp_tls_set_x509_key_file(s, tls_ctx, NULL, NULL);
+ gnutls_credentials_set(*tls_session, GNUTLS_CRD_CERTIFICATE,
+ tls_ctx->gnutls_cred);
+
+ gnutls_transport_set_ptr(*tls_session, user_data);
+ gnutls_transport_set_push_function
+ (*tls_session, rexmpp_jingle_dtls_push_func);
+ gnutls_transport_set_pull_function
+ (*tls_session, rexmpp_dtls_jingle_pull_func_gnutls);
+ gnutls_transport_set_pull_timeout_function
+ (*tls_session, rexmpp_jingle_dtls_pull_timeout_func);
+ /* todo: use the profile/crypto-suite from <crypto/> element */
+ gnutls_srtp_set_profile(*tls_session, GNUTLS_SRTP_AES128_CM_HMAC_SHA1_80);
+ return REXMPP_TLS_SUCCESS;
+#elif defined(USE_OPENSSL)
+ (void)client;
+ int err;
+ /* Setup credentials */
+ rexmpp_tls_set_x509_key_file(s, tls_ctx, NULL, NULL);
+ /* Create a connection. */
+ tls_ctx->openssl_conn = SSL_new(tls_ctx->openssl_ctx);
+ SSL_set_verify(tls_ctx->openssl_conn,
+ SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+ rexmpp_openssl_verify_accept_all);
+ /* Set a BIO */
+ BIO_new_bio_pair(&(tls_ctx->bio_conn), 4096, &(tls_ctx->bio_io), 4096);
+ BIO_up_ref(tls_ctx->bio_conn);
+ SSL_set0_rbio(tls_ctx->openssl_conn, tls_ctx->bio_conn);
+ SSL_set0_wbio(tls_ctx->openssl_conn, tls_ctx->bio_conn);
+ /* Set a callback to track writes */
+ BIO_set_callback_ex(tls_ctx->bio_conn, rexmpp_dtls_openssl_bio_cb);
+ BIO_set_callback_arg(tls_ctx->bio_conn, user_data);
+ BIO_set_ssl(tls_ctx->bio_conn, tls_ctx->openssl_conn, BIO_NOCLOSE);
+ /* Enable SRTP (TODO: support different profiles) */
+ err = SSL_set_tlsext_use_srtp(tls_ctx->openssl_conn,
+ "SRTP_AES128_CM_SHA1_80");
+ if (err) {
+ rexmpp_log(s, LOG_ERR, "Failed to setup SRTP for the DTLS connection");
+ return REXMPP_TLS_E_OTHER;
+ }
+ if (client) {
+ err = SSL_connect(tls_ctx->openssl_conn);
+ } else {
+ err = SSL_accept(tls_ctx->openssl_conn);
+ }
+ return rexmpp_process_openssl_ret(s, tls_ctx, "rexmpp_dtls_connect", err);
+#else
+ (void)s;
+ (void)tls_ctx;
+ (void)user_data;
+ (void)client;
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+void rexmpp_dtls_feed(rexmpp_t *s, rexmpp_tls_t *tls_ctx, uint8_t *buf, size_t len) {
+#if defined(USE_GNUTLS)
+ if (tls_ctx->dtls_buf_len + len < DTLS_SRTP_BUF_SIZE) {
+ memcpy(tls_ctx->dtls_buf + tls_ctx->dtls_buf_len, buf, len);
+ tls_ctx->dtls_buf_len += len;
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Dropping a DTLS packet");
+ }
+#elif defined(USE_OPENSSL)
+ (void)s;
+ BIO_write(tls_ctx->bio_io, buf, len);
+#else
+ (void)s;
+ (void)tls_ctx;
+ (void)buf;
+ (void)len;
+#endif
+}
+
+rexmpp_tls_err_t rexmpp_tls_handshake (rexmpp_t *s, rexmpp_tls_t *tls_ctx) {
+#if defined(USE_GNUTLS)
+ int ret = gnutls_handshake(tls_ctx->gnutls_session);
+ if (ret == 0) {
+ return REXMPP_TLS_SUCCESS;
+ } else if (ret == GNUTLS_E_AGAIN) {
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ rexmpp_log(s, LOG_ERR, "Error during a TLS handshake: %s",
+ gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ return rexmpp_process_openssl_ret(s, tls_ctx, "rexmpp_tls_handshake",
+ SSL_do_handshake(tls_ctx->openssl_conn));
+#else
+ (void)s;
+ (void)tls_ctx;
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_connect (rexmpp_t *s) {
+ if (s->x509_key_file != NULL && s->x509_cert_file != NULL) {
+ rexmpp_tls_set_x509_key_file(s, s->tls, NULL, NULL);
+ }
+
+#if defined(USE_GNUTLS)
+ if (s->tls_state != REXMPP_TLS_HANDSHAKE) {
+ gnutls_datum_t xmpp_client_protocol =
+ {(unsigned char*)"xmpp-client", strlen("xmpp-client")};
+ rexmpp_log(s, LOG_DEBUG, "starting TLS");
+ gnutls_init(&s->tls->gnutls_session, GNUTLS_CLIENT);
+ gnutls_session_set_ptr(s->tls->gnutls_session, s);
+ gnutls_alpn_set_protocols(s->tls->gnutls_session, &xmpp_client_protocol, 1, 0);
+ gnutls_server_name_set(s->tls->gnutls_session, GNUTLS_NAME_DNS,
+ s->initial_jid.domain,
+ strlen(s->initial_jid.domain));
+ gnutls_set_default_priority(s->tls->gnutls_session);
+ gnutls_credentials_set(s->tls->gnutls_session, GNUTLS_CRD_CERTIFICATE,
+ s->tls->gnutls_cred);
+ gnutls_transport_set_int(s->tls->gnutls_session, s->server_socket);
+ gnutls_handshake_set_timeout(s->tls->gnutls_session,
+ GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+ if (s->tls->tls_session_data != NULL) {
+ int ret = gnutls_session_set_data(s->tls->gnutls_session,
+ s->tls->tls_session_data,
+ s->tls->tls_session_data_size);
+ if (ret != GNUTLS_E_SUCCESS) {
+ rexmpp_log(s, LOG_WARNING, "Failed to set TLS session data: %s",
+ gnutls_strerror(ret));
+ free(s->tls->tls_session_data);
+ s->tls->tls_session_data = NULL;
+ s->tls->tls_session_data_size = 0;
+ }
+ }
+ }
+
+ int ret = gnutls_handshake(s->tls->gnutls_session);
+ if (ret == GNUTLS_E_AGAIN) {
+ rexmpp_log(s, LOG_DEBUG, "Waiting for TLS handshake to complete");
+ return REXMPP_TLS_E_AGAIN;
+ } else if (ret == 0) {
+ unsigned int status;
+
+ int srv_is_secure = 0;
+ if (s->stream_state == REXMPP_STREAM_NONE &&
+ s->server_srv_tls != NULL) { /* Direct TLS */
+ srv_is_secure = s->server_srv_tls->secure;
+ } else if (s->stream_state != REXMPP_STREAM_NONE &&
+ s->server_srv != NULL) { /* STARTTLS connection */
+ srv_is_secure = s->server_srv->secure;
+ }
+
+ /* Check DANE TLSA records; experimental and purely informative
+ now, but may be nice to (optionally) rely on it in the
+ future. */
+ if ((srv_is_secure || s->manual_host != NULL) &&
+ s->server_socket_dns_secure) {
+ /* Apparently GnuTLS only checks against the target
+ server/derived host, while another possibility is a
+ service/source host
+ (<https://tools.ietf.org/html/rfc7712#section-5.1>,
+ <https://tools.ietf.org/html/rfc7673#section-6>). */
+ ret = dane_verify_session_crt(NULL, s->tls->gnutls_session, s->server_host,
+ "tcp", s->server_port, 0, 0, &status);
+ if (ret) {
+ rexmpp_log(s, LOG_WARNING, "DANE verification error: %s",
+ dane_strerror(ret));
+ } else if (status) {
+ if (status & DANE_VERIFY_CA_CONSTRAINTS_VIOLATED) {
+ rexmpp_log(s, LOG_WARNING, "The CA constraints were violated");
+ }
+ if (status & DANE_VERIFY_CERT_DIFFERS) {
+ rexmpp_log(s, LOG_WARNING, "The certificate obtained via DNS differs");
+ }
+ if (status & DANE_VERIFY_UNKNOWN_DANE_INFO) {
+ rexmpp_log(s, LOG_WARNING,
+ "No known DANE data was found in the DNS record");
+ }
+ } else {
+ rexmpp_log(s, LOG_INFO,
+ "DANE verification did not reject the certificate");
+ }
+ }
+
+ ret = gnutls_certificate_verify_peers3(s->tls->gnutls_session,
+ s->initial_jid.domain,
+ &status);
+ if (ret || status) {
+ if (ret) {
+ rexmpp_log(s, LOG_ERR, "Certificate parsing error: %s",
+ gnutls_strerror(ret));
+ } else if (status & GNUTLS_CERT_UNEXPECTED_OWNER) {
+ rexmpp_log(s, LOG_ERR, "Unexpected certificate owner");
+ } else {
+ rexmpp_log(s, LOG_ERR, "Untrusted certificate");
+ }
+ gnutls_bye(s->tls->gnutls_session, GNUTLS_SHUT_RDWR);
+ return REXMPP_TLS_E_OTHER;
+ }
+
+ if (gnutls_session_is_resumed(s->tls->gnutls_session)) {
+ rexmpp_log(s, LOG_INFO, "TLS session is resumed");
+ } else {
+ if (s->tls->tls_session_data != NULL) {
+ rexmpp_log(s, LOG_DEBUG, "TLS session is not resumed");
+ free(s->tls->tls_session_data);
+ s->tls->tls_session_data = NULL;
+ }
+ gnutls_session_get_data(s->tls->gnutls_session, NULL,
+ &s->tls->tls_session_data_size);
+ s->tls->tls_session_data = malloc(s->tls->tls_session_data_size);
+ ret = gnutls_session_get_data(s->tls->gnutls_session, s->tls->tls_session_data,
+ &s->tls->tls_session_data_size);
+ if (ret != GNUTLS_E_SUCCESS) {
+ rexmpp_log(s, LOG_ERR, "Failed to get TLS session data: %s",
+ gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+ }
+
+ return REXMPP_TLS_SUCCESS;
+ } else {
+ rexmpp_log(s, LOG_ERR, "Unexpected TLS handshake error: %s",
+ gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ if (s->tls_state != REXMPP_TLS_HANDSHAKE) {
+ s->tls->openssl_conn = SSL_new(s->tls->openssl_ctx);
+ if (s->tls->openssl_conn == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to create an OpenSSL connection object");
+ return REXMPP_TLS_E_OTHER;
+ }
+ if (SSL_set_fd(s->tls->openssl_conn, s->server_socket) == 0) {
+ rexmpp_log(s, LOG_ERR, "Failed to set a file descriptor for OpenSSL connection");
+ return REXMPP_TLS_E_OTHER;
+ }
+ if (SSL_set1_host(s->tls->openssl_conn, s->initial_jid.domain) == 0) {
+ rexmpp_log(s, LOG_ERR, "Failed to set a hostname for OpenSSL connection");
+ return REXMPP_TLS_E_OTHER;
+ }
+ /* For SNI */
+ if (SSL_set_tlsext_host_name(s->tls->openssl_conn, s->initial_jid.domain) == 0) {
+ rexmpp_log(s, LOG_ERR, "Failed to set a tlsext hostname for OpenSSL connection");
+ return REXMPP_TLS_E_OTHER;
+ }
+ }
+ return rexmpp_process_openssl_ret(s, s->tls, "rexmpp_tls_connect",
+ SSL_connect(s->tls->openssl_conn));
+#else
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_disconnect (rexmpp_t *s, rexmpp_tls_t *tls_ctx) {
+#if defined(USE_GNUTLS)
+ int ret = gnutls_bye(tls_ctx->gnutls_session, GNUTLS_SHUT_RDWR);
+ if (ret == GNUTLS_E_SUCCESS) {
+ return REXMPP_TLS_SUCCESS;
+ } else if (ret == GNUTLS_E_AGAIN) {
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Failed to close TLS connection: %s",
+ gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ int ret = SSL_shutdown(tls_ctx->openssl_conn);
+ if (ret == 0) {
+ tls_ctx->openssl_direction = REXMPP_OPENSSL_READ;
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ return rexmpp_process_openssl_ret(s, tls_ctx,
+ "rexmpp_tls_disconnect", ret);
+ }
+#else
+ (void)tls_ctx;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+int
+rexmpp_tls_srtp_get_keys (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ size_t key_len,
+ size_t salt_len,
+ unsigned char *key_mat)
+{
+#if defined(USE_GNUTLS)
+ int key_mat_size;
+ key_mat_size =
+ gnutls_srtp_get_keys(tls_ctx->gnutls_session,
+ key_mat, (key_len + salt_len) * 2,
+ NULL, NULL, NULL, NULL);
+ if (key_mat_size == GNUTLS_E_SHORT_MEMORY_BUFFER ||
+ key_mat_size < 0) {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to retrieve DTLS key material for SRTP: %s",
+ gnutls_strerror(key_mat_size));
+ }
+ return 0;
+#elif defined(USE_OPENSSL)
+ /* https://www.rfc-editor.org/rfc/rfc5764.html */
+ const char *extractor = "EXTRACTOR-dtls_srtp";
+ int err = SSL_export_keying_material(tls_ctx->openssl_conn,
+ key_mat, 2 * (key_len + salt_len),
+ extractor, strlen(extractor),
+ NULL, 0, 0);
+ return rexmpp_process_openssl_ret(s, tls_ctx,
+ "rexmpp_tls_srtp_get_keys", err);
+#else
+ (void)s;
+ (void)tls_ctx;
+ (void)key_len;
+ (void)salt_len;
+ (void)key_mat;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return -1;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_send (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ void *data,
+ size_t data_size,
+ ssize_t *written)
+{
+ *written = -1;
+#if defined(USE_GNUTLS)
+ ssize_t ret = gnutls_record_send(tls_ctx->gnutls_session,
+ data,
+ data_size);
+ if (ret >= 0) {
+ *written = ret;
+ return REXMPP_TLS_SUCCESS;
+ } else if (ret == GNUTLS_E_AGAIN) {
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ rexmpp_log(s, LOG_ERR, "TLS send error: %s", gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ int ret = SSL_write_ex(tls_ctx->openssl_conn, data, data_size,
+ (size_t*)written);
+ if (ret > 0) {
+ return REXMPP_TLS_SUCCESS;
+ } else {
+ return rexmpp_process_openssl_ret(s, tls_ctx, "rexmpp_tls_send", ret);
+ }
+#else
+ (void)data;
+ (void)data_size;
+ (void)tls_ctx;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_recv (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ void *data,
+ size_t data_size,
+ ssize_t *received)
+{
+#if defined(USE_GNUTLS)
+ *received = -1;
+ ssize_t ret = gnutls_record_recv(tls_ctx->gnutls_session, data, data_size);
+ if (ret >= 0) {
+ *received = ret;
+ return REXMPP_TLS_SUCCESS;
+ } else if (ret == GNUTLS_E_AGAIN) {
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ rexmpp_log(s, LOG_ERR, "TLS recv error: %s", gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ *received = -1;
+ int ret = SSL_read_ex(tls_ctx->openssl_conn, data, data_size,
+ (size_t*)received);
+ if (ret > 0) {
+ return REXMPP_TLS_SUCCESS;
+ } else {
+ return rexmpp_process_openssl_ret(s, tls_ctx, "rexmpp_tls_recv", ret);
+ }
+#else
+ (void)data;
+ (void)data_size;
+ (void)received;
+ (void)tls_ctx;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+unsigned int rexmpp_dtls_timeout (rexmpp_t *s, rexmpp_tls_t *tls_ctx) {
+ (void)s;
+#if defined(USE_GNUTLS)
+ return gnutls_dtls_get_timeout(tls_ctx->gnutls_session);
+#else
+ (void)tls_ctx;
+ return -1;
+#endif
+}
+
+int rexmpp_tls_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
+#if defined(USE_GNUTLS)
+ if (gnutls_record_get_direction(s->tls->gnutls_session) == 0) {
+ FD_SET(s->server_socket, read_fds);
+ } else {
+ FD_SET(s->server_socket, write_fds);
+ }
+ return s->server_socket + 1;
+#elif defined(USE_OPENSSL)
+ if (s->tls->openssl_direction == REXMPP_OPENSSL_READ) {
+ FD_SET(s->server_socket, read_fds);
+ return s->server_socket + 1;
+ }
+ if (s->tls->openssl_direction == REXMPP_OPENSSL_WRITE) {
+ FD_SET(s->server_socket, write_fds);
+ return s->server_socket + 1;
+ }
+ return 0;
+#else
+ (void)s;
+ (void)read_fds;
+ (void)write_fds;
+ return 0;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_set_x509_key_file (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ const char *cert_file,
+ const char *key_file)
+{
+ if (cert_file == NULL) {
+ cert_file = s->x509_cert_file;
+ }
+ if (key_file == NULL) {
+ key_file = s->x509_key_file;
+ }
+ if (cert_file == NULL || key_file == NULL) {
+ rexmpp_log(s, LOG_ERR, "No certificate or key file defined");
+ return REXMPP_TLS_E_OTHER;
+ }
+#if defined(USE_GNUTLS)
+ int ret = gnutls_certificate_set_x509_key_file(tls_ctx->gnutls_cred,
+ cert_file,
+ key_file,
+ GNUTLS_X509_FMT_DER);
+ if (ret == 0) {
+ return REXMPP_TLS_SUCCESS;
+ } else {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to set a key file: %s", gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ if (SSL_CTX_use_certificate_file(tls_ctx->openssl_ctx,
+ cert_file,
+ SSL_FILETYPE_ASN1) != 1) {
+ rexmpp_log(s, LOG_ERR, "Failed to set a certificate file");
+ return REXMPP_TLS_E_OTHER;
+ }
+ if (SSL_CTX_use_PrivateKey_file(tls_ctx->openssl_ctx,
+ key_file,
+ SSL_FILETYPE_ASN1) != 1) {
+ rexmpp_log(s, LOG_ERR, "Failed to set a key file");
+ return REXMPP_TLS_E_OTHER;
+ }
+ return REXMPP_TLS_SUCCESS;
+#else
+ (void)cert_file;
+ (void)key_file;
+ (void)tls_ctx;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_set_x509_trust_file (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ const char *trust_file)
+{
+ if (trust_file == NULL) {
+ trust_file = s->x509_trust_file;
+ }
+ if (trust_file == NULL) {
+ rexmpp_log(s, LOG_ERR, "No trust file is defined");
+ return REXMPP_TLS_E_OTHER;
+ }
+#if defined(USE_GNUTLS)
+ gnutls_certificate_set_x509_trust_file(tls_ctx->gnutls_cred,
+ trust_file,
+ GNUTLS_X509_FMT_DER);
+ return REXMPP_TLS_SUCCESS;
+#elif defined(USE_OPENSSL)
+ if (SSL_CTX_load_verify_locations(tls_ctx->openssl_ctx, trust_file, NULL) != 1) {
+ rexmpp_log(s, LOG_ERR, "Failed to set a trusted certificate file");
+ return REXMPP_TLS_E_OTHER;
+ }
+ return REXMPP_TLS_SUCCESS;
+#else
+ (void)trust_file;
+ (void)tls_ctx;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+
+int rexmpp_tls_peer_fp (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ const char *algo_str,
+ char *raw_fp,
+ char *fp_str,
+ size_t *fp_size)
+{
+#if defined(USE_GNUTLS)
+ unsigned int cert_list_size = 0;
+ const gnutls_datum_t *cert_list;
+ cert_list =
+ gnutls_certificate_get_peers(tls_ctx->gnutls_session, &cert_list_size);
+ if (cert_list_size != 1) {
+ rexmpp_log(s, LOG_ERR,
+ "Unexpected peer certificate list size: %d",
+ cert_list_size);
+ return -1;
+ }
+ return rexmpp_x509_raw_cert_fp(s, algo_str, cert_list,
+ raw_fp, fp_str, fp_size);
+#elif defined(USE_OPENSSL)
+ if (strcmp(algo_str, "sha-256") != 0) {
+ rexmpp_log(s, LOG_ERR,
+ "Unsupported hash function algorithm: %s", algo_str);
+ return -1;
+ }
+ X509 *peer_cert = SSL_get0_peer_certificate(tls_ctx->openssl_conn);
+ if (peer_cert == NULL) {
+ rexmpp_log(s, LOG_ERR, "No peer certificate found");
+ return -1;
+ }
+ unsigned int len;
+ X509_digest(peer_cert, EVP_sha256(), (unsigned char*)raw_fp, &len);
+ *fp_size = len;
+ size_t i;
+ for (i = 0; i < *fp_size; i++) {
+ snprintf(fp_str + i * 3, 4, "%02X:", raw_fp[i] & 0xFF);
+ }
+ fp_str[*fp_size * 3 - 1] = 0;
+ return 0;
+#else
+ (void)tls_ctx;
+ (void)algo_str;
+ (void)raw_fp;
+ (void)fp_str;
+ (void)fp_size;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return -1;
+#endif
+}
+
+/* TODO: handle different algorithms, and maybe apply this to
+ arbitrary files. */
+int rexmpp_tls_my_fp (rexmpp_t *s,
+ char *raw_fp,
+ char *fp_str,
+ size_t *fp_size)
+{
+ rexmpp_digest_t digest_ctx;
+ if (rexmpp_digest_init(&digest_ctx, REXMPP_DIGEST_SHA256)) {
+ rexmpp_log(s, LOG_ERR, "Failed to initialize a digest object");
+ return -1;
+ }
+
+ if (s->x509_cert_file == NULL) {
+ rexmpp_log(s, LOG_WARNING, "No X.509 certificate file defined");
+ return -1;
+ }
+ FILE *fh = fopen(s->x509_cert_file, "r");
+ if (fh == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to open the X.509 certificate file");
+ return -1;
+ }
+ unsigned char *buf[4096];
+ size_t len = fread(buf, 1, 4096, fh);
+ while (len > 0) {
+ rexmpp_digest_update(&digest_ctx, buf, len);
+ len = fread(buf, 1, 4096, fh);
+ }
+ fclose(fh);
+
+ *fp_size = rexmpp_digest_len(REXMPP_DIGEST_SHA256);
+ rexmpp_digest_finish(&digest_ctx, raw_fp, *fp_size);
+
+ size_t i;
+ for (i = 0; i < (*fp_size); i++) {
+ snprintf(fp_str + i * 3, 4, "%02X:", raw_fp[i] & 0xFF);
+ }
+ fp_str[(*fp_size) * 3 - 1] = 0;
+ return 0;
+}
+
+int rexmpp_tls_session_fp (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ const char *algo_str,
+ char *raw_fp,
+ char *fp_str,
+ size_t *fp_size)
+{
+#if defined(USE_GNUTLS)
+ gnutls_x509_crt_t *cert_list;
+ unsigned int cert_list_size = 0;
+ int err =
+ gnutls_certificate_get_x509_crt(tls_ctx->gnutls_cred,
+ 0, &cert_list, &cert_list_size);
+ if (err) {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to read own certificate list: %s",
+ gnutls_strerror(err));
+ return -1;
+ }
+
+ err = rexmpp_x509_cert_fp(s, algo_str, cert_list[0], raw_fp, fp_str, fp_size);
+
+ size_t i;
+ for (i = 0; i < cert_list_size; i++) {
+ gnutls_x509_crt_deinit(cert_list[i]);
+ }
+ gnutls_free(cert_list);
+ return err;
+#else
+ (void)s;
+ (void)tls_ctx;
+ (void)algo_str;
+ (void)raw_fp;
+ (void)fp_str;
+ (void)fp_size;
+ return -1;
+#endif
+}
+
+int rexmpp_x509_cert_fp (rexmpp_t *s,
+ const char *algo_str,
+ void *cert,
+ char *raw_fp,
+ char *fp_str,
+ size_t *fp_size)
+{
+#if defined(USE_GNUTLS)
+ gnutls_datum_t raw_cert;
+ int err = gnutls_x509_crt_export2(cert, GNUTLS_X509_FMT_DER, &raw_cert);
+ if (err != GNUTLS_E_SUCCESS) {
+ rexmpp_log(s, LOG_ERR, "Failed to export a certificate: %s",
+ gnutls_strerror(err));
+ return err;
+ }
+ err = rexmpp_x509_raw_cert_fp(s, algo_str, &raw_cert, raw_fp, fp_str, fp_size);
+ gnutls_free(raw_cert.data);
+ return err;
+#else
+ (void)s;
+ (void)algo_str;
+ (void)cert;
+ (void)raw_fp;
+ (void)fp_str;
+ (void)fp_size;
+ return -1;
+#endif
+}
+
+int rexmpp_x509_raw_cert_fp (rexmpp_t *s,
+ const char *algo_str,
+ const void *raw_cert,
+ char *raw_fp,
+ char *fp_str,
+ size_t *fp_size)
+{
+#if defined(USE_GNUTLS)
+ const gnutls_datum_t *cert = (const gnutls_datum_t*)raw_cert;
+ gnutls_digest_algorithm_t algo = GNUTLS_DIG_UNKNOWN;
+ /* gnutls_digest_get_id uses different names, so
+ checking manually here. These are SDP options,
+ <https://datatracker.ietf.org/doc/html/rfc4572#page-8>. */
+ if (strcmp(algo_str, "sha-1") == 0) {
+ algo = GNUTLS_DIG_SHA1;
+ } else if (strcmp(algo_str, "sha-224") == 0) {
+ algo = GNUTLS_DIG_SHA224;
+ } else if (strcmp(algo_str, "sha-256") == 0) {
+ algo = GNUTLS_DIG_SHA256;
+ } else if (strcmp(algo_str, "sha-384") == 0) {
+ algo = GNUTLS_DIG_SHA384;
+ } else if (strcmp(algo_str, "sha-512") == 0) {
+ algo = GNUTLS_DIG_SHA512;
+ } else if (strcmp(algo_str, "md5") == 0) {
+ algo = GNUTLS_DIG_MD5;
+ }
+ if (algo == GNUTLS_DIG_UNKNOWN) {
+ rexmpp_log(s, LOG_ERR, "Unknown hash algorithm: %s", algo_str);
+ return -1;
+ }
+
+ int err = gnutls_fingerprint(algo, cert, raw_fp, fp_size);
+ if (err != GNUTLS_E_SUCCESS) {
+ rexmpp_log(s, LOG_ERR, "Failed to calculate a fingerprint: %s",
+ gnutls_strerror(err));
+ return -1;
+ }
+ if (fp_str != NULL) {
+ size_t i;
+ for (i = 0; i < (*fp_size); i++) {
+ snprintf(fp_str + i * 3, 4, "%02X:", raw_fp[i] & 0xFF);
+ }
+ fp_str[(*fp_size) * 3 - 1] = 0;
+ }
+ return 0;
+#else
+ (void)s;
+ (void)algo_str;
+ (void)raw_cert;
+ (void)raw_fp;
+ (void)fp_str;
+ (void)fp_size;
+ return -1;
+#endif
+}
diff --git a/src/rexmpp_tls.h b/src/rexmpp_tls.h
new file mode 100644
index 0000000..4a966ca
--- /dev/null
+++ b/src/rexmpp_tls.h
@@ -0,0 +1,161 @@
+/**
+ @file rexmpp_tls.h
+ @brief TLS abstraction
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+
+These functions only alter the rexmpp structure's tls member (in
+particular, they don't change other state variables), but use rexmpp_t
+to write logs and read other values (including server socket).
+
+*/
+
+
+#ifndef REXMPP_TLS_H
+#define REXMPP_TLS_H
+
+#include <stdint.h>
+
+#include "rexmpp.h"
+#include "config.h"
+
+#define DTLS_SRTP_BUF_SIZE 0x4000
+
+typedef struct rexmpp_tls rexmpp_tls_t;
+
+/**
+ @brief TLS operation results.
+*/
+enum rexmpp_tls_err {
+ REXMPP_TLS_SUCCESS,
+ REXMPP_TLS_E_AGAIN,
+ REXMPP_TLS_E_OTHER
+};
+
+typedef enum rexmpp_tls_err rexmpp_tls_err_t;
+
+/**
+ @brief TLS context.
+*/
+#if defined(USE_GNUTLS)
+#include <gnutls/gnutls.h>
+struct rexmpp_tls {
+ void *tls_session_data;
+ size_t tls_session_data_size;
+ gnutls_session_t gnutls_session;
+ gnutls_certificate_credentials_t gnutls_cred;
+ char dtls_buf[DTLS_SRTP_BUF_SIZE];
+ size_t dtls_buf_len;
+};
+#elif defined(USE_OPENSSL)
+#include <openssl/ssl.h>
+enum rexmpp_openssl_direction {
+ REXMPP_OPENSSL_NONE,
+ REXMPP_OPENSSL_READ,
+ REXMPP_OPENSSL_WRITE
+};
+struct rexmpp_tls {
+ SSL_CTX *openssl_ctx;
+ SSL *openssl_conn;
+ BIO *bio_conn;
+ BIO *bio_io;
+ enum rexmpp_openssl_direction openssl_direction;
+};
+#else
+struct rexmpp_tls {
+ int dummy;
+};
+#endif
+
+int rexmpp_tls_init(rexmpp_t *s);
+void rexmpp_tls_cleanup(rexmpp_t *s);
+void rexmpp_tls_deinit(rexmpp_t *s);
+
+rexmpp_tls_t *rexmpp_tls_ctx_new (rexmpp_t *s, int dtls);
+void rexmpp_tls_ctx_free (rexmpp_tls_t *tls_ctx);
+
+void rexmpp_tls_session_free (rexmpp_tls_t *tls_ctx);
+
+rexmpp_tls_err_t rexmpp_tls_connect (rexmpp_t *s);
+rexmpp_tls_err_t rexmpp_tls_handshake (rexmpp_t *s, rexmpp_tls_t *tls_ctx);
+rexmpp_tls_err_t rexmpp_tls_disconnect (rexmpp_t *s, rexmpp_tls_t *tls_ctx);
+rexmpp_tls_err_t
+rexmpp_dtls_connect (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ void *user_data,
+ int client);
+void rexmpp_dtls_feed(rexmpp_t *s, rexmpp_tls_t *tls_ctx, uint8_t *buf, size_t len);
+
+int
+rexmpp_tls_srtp_get_keys (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ size_t key_len,
+ size_t salt_len,
+ unsigned char *key_mat);
+
+rexmpp_tls_err_t
+rexmpp_tls_send (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ void *data,
+ size_t data_size,
+ ssize_t *written);
+rexmpp_tls_err_t
+rexmpp_tls_recv (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ void *data,
+ size_t data_size,
+ ssize_t *received);
+
+unsigned int rexmpp_dtls_timeout (rexmpp_t *s, rexmpp_tls_t *tls_ctx);
+int rexmpp_tls_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds);
+
+/**
+ @brief Sets credentials for a given TLS context: either provided
+ ones or defined for the whole ::rexmpp structure.
+*/
+rexmpp_tls_err_t
+rexmpp_tls_set_x509_key_file (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ const char *cert_file,
+ const char *key_file);
+
+rexmpp_tls_err_t
+rexmpp_tls_set_x509_trust_file (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ const char *cert_file);
+
+int rexmpp_tls_peer_fp (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ const char *algo_str,
+ char *raw_fp,
+ char *fp_str,
+ size_t *fp_size);
+
+int rexmpp_tls_my_fp (rexmpp_t *s,
+ char *raw_fp,
+ char *fp_str,
+ size_t *fp_size);
+
+int rexmpp_tls_session_fp (rexmpp_t *s,
+ rexmpp_tls_t *tls_ctx,
+ const char *algo_str,
+ char *raw_fp,
+ char *fp_str,
+ size_t *fp_size);
+
+int rexmpp_x509_cert_fp (rexmpp_t *s,
+ const char *algo_str,
+ void *cert,
+ char *raw_fp,
+ char *fp_str,
+ size_t *fp_size);
+
+int rexmpp_x509_raw_cert_fp (rexmpp_t *s,
+ const char *algo_str,
+ const void *raw_cert,
+ char *raw_fp,
+ char *fp_str,
+ size_t *fp_size);
+
+#endif
diff --git a/src/rexmpp_utf8.h b/src/rexmpp_utf8.h
new file mode 100644
index 0000000..4096aac
--- /dev/null
+++ b/src/rexmpp_utf8.h
@@ -0,0 +1,93 @@
+/**
+ @file rexmpp_utf8.h
+ @brief UTF-8 helper functions
+ @author defanor <defanor@uberspace.net>
+ @date 2023
+ @copyright MIT license.
+*/
+
+#ifndef REXMPP_UTF8_H
+#define REXMPP_UTF8_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef HAVE_ICU
+
+#include <unicode/utf8.h>
+#define REXMPP_U8_NEXT U8_NEXT
+
+#else
+
+#define REXMPP_U8_NEXT(str, pos, len, out) \
+ rexmpp_utf8_next(str, &pos, len, &out);
+
+/**
+ @brief Similar to libicu's U8_NEXT macros: reads a single UTF-8
+ code point, advances the position.
+ @param[in] str A string to read.
+ @param[in,out] pos Byte position within the string. Advanced by the
+ number of bytes read to produce a code point, not advanced on
+ failure.
+ @param[in] len String length.
+ @param[in,out] out A pointer to the location for writing the code
+ point.
+ @returns 0 on failure, 1 on success.
+*/
+inline static
+void rexmpp_utf8_next (const uint8_t *str,
+ size_t *pos,
+ size_t len,
+ int32_t *out)
+{
+ if (*pos >= len) {
+ *out = -1;
+ return;
+ }
+
+ if ((str[*pos] & 0x80) == 0
+ && *pos + 1 <= len)
+ /* U+0000 to U+007F: 0xxxxxxx */
+ {
+ *out = str[*pos];
+ *pos = *pos + 1;
+ } else if ((str[*pos] & 0xe0) == 0xc0
+ && *pos + 2 <= len
+ && (str[*pos + 1] & 0xc0) == 0x80)
+ /* U+0080 to U+07FF: 110xxxxx 10xxxxxx */
+ {
+ *out = (((int32_t)(str[*pos] & 0x1f) << 6)
+ | ((int32_t)str[*pos + 1] & 0x3f));
+ *pos = *pos + 2;
+ } else if ((str[*pos] & 0xf0) == 0xe0
+ && *pos + 3 <= len
+ && (str[*pos + 1] & 0xc0) == 0x80
+ && (str[*pos + 2] & 0xc0) == 0x80)
+ /* U+0800 to U+FFFF: 1110xxxx 10xxxxxx 10xxxxxx */
+ {
+ *out = (((((int32_t)(str[*pos] & 0xf) << 6)
+ | ((int32_t)str[*pos + 1] & 0x3f)) << 6)
+ | ((int32_t)str[*pos + 2] & 0x3f));
+ *pos = *pos + 3;
+ } else if ((str[*pos] & 0xf8) == 0xf0
+ && *pos + 4 <= len
+ && (str[*pos + 1] & 0xc0) == 0x80
+ && (str[*pos + 2] & 0xc0) == 0x80
+ && (str[*pos + 3] & 0xc0) == 0x80)
+ /* U+10000 to U+10FFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
+ {
+ *out = (((((((int32_t)(str[*pos] & 7) << 6)
+ | ((int32_t)str[*pos + 1] & 0x3f)) << 6)
+ | (((int32_t)str[*pos + 2] & 0x3f))) << 6)
+ | ((int32_t)str[*pos + 3] & 0x3f));
+ *pos = *pos + 4;
+ } else
+ /* Invalid UTF-8 */
+ {
+ *out = -1;
+ }
+}
+
+#endif /* HAVE_ICU */
+
+#endif /* REXMPP_UTF8_H */
diff --git a/src/rexmpp_xml.c b/src/rexmpp_xml.c
new file mode 100644
index 0000000..8b106ab
--- /dev/null
+++ b/src/rexmpp_xml.c
@@ -0,0 +1,805 @@
+/**
+ @file rexmpp_xml.c
+ @brief XML structures and functions for rexmpp
+ @author defanor <defanor@uberspace.net>
+ @date 2023
+ @copyright MIT license.
+*/
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include "rexmpp.h"
+#include "rexmpp_utf8.h"
+#include "rexmpp_xml.h"
+#include "rexmpp_random.h"
+
+#ifndef USE_RUST
+void rexmpp_xml_qname_free (rexmpp_xml_qname_t *qname) {
+ if (qname->name != NULL) {
+ free(qname->name);
+ qname->name = NULL;
+ }
+ if (qname->namespace != NULL) {
+ free(qname->namespace);
+ qname->namespace = NULL;
+ }
+}
+
+void rexmpp_xml_attribute_free (rexmpp_xml_attr_t *attr) {
+ if (attr == NULL) {
+ return;
+ }
+ rexmpp_xml_qname_free(&(attr->qname));
+ if (attr->value != NULL) {
+ free(attr->value);
+ attr->value = NULL;
+ }
+ free(attr);
+}
+
+void rexmpp_xml_attribute_free_list (rexmpp_xml_attr_t *attr) {
+ rexmpp_xml_attr_t *next = attr;
+ while (attr != NULL) {
+ next = attr->next;
+ rexmpp_xml_attribute_free(attr);
+ attr = next;
+ }
+}
+
+void rexmpp_xml_free (rexmpp_xml_t *node) {
+ if (node == NULL) {
+ return;
+ }
+ if (node->type == REXMPP_XML_TEXT) {
+ if (node->alt.text != NULL) {
+ free(node->alt.text);
+ node->alt.text = NULL;
+ }
+ } if (node->type == REXMPP_XML_ELEMENT) {
+ rexmpp_xml_qname_free(&(node->alt.elem.qname));
+ rexmpp_xml_attribute_free_list(node->alt.elem.attributes);
+ rexmpp_xml_free_list(node->alt.elem.children);
+ }
+ free(node);
+}
+
+void rexmpp_xml_free_list (rexmpp_xml_t *node) {
+ rexmpp_xml_t *next = node;
+ while (node != NULL) {
+ next = node->next;
+ rexmpp_xml_free(node);
+ node = next;
+ }
+}
+
+rexmpp_xml_t *rexmpp_xml_clone (rexmpp_xml_t *node) {
+ if (node == NULL) {
+ return NULL;
+ }
+
+ if (node->type == REXMPP_XML_TEXT) {
+ return rexmpp_xml_new_text(node->alt.text);
+ } else if (node->type == REXMPP_XML_ELEMENT) {
+ rexmpp_xml_t *ret =
+ rexmpp_xml_new_elem(node->alt.elem.qname.name,
+ node->alt.elem.qname.namespace);
+ rexmpp_xml_attr_t **next_attr = &(ret->alt.elem.attributes);
+ rexmpp_xml_attr_t *old_attr;
+ for (old_attr = node->alt.elem.attributes;
+ old_attr != NULL;
+ old_attr = old_attr->next)
+ {
+ rexmpp_xml_attr_t *new_attr =
+ rexmpp_xml_attr_new(old_attr->qname.name,
+ old_attr->qname.namespace,
+ old_attr->value);
+ *next_attr = new_attr;
+ next_attr = &(new_attr->next);
+ }
+
+ ret->alt.elem.children =
+ rexmpp_xml_clone_list(node->alt.elem.children);
+ return ret;
+ }
+ return NULL;
+}
+
+rexmpp_xml_t *rexmpp_xml_clone_list (rexmpp_xml_t *node) {
+ rexmpp_xml_t *first, *last;
+ if (node == NULL) {
+ return NULL;
+ }
+ first = rexmpp_xml_clone(node);
+ for (last = first, node = node->next;
+ node != NULL;
+ last = last->next, node = node->next)
+ {
+ last->next = rexmpp_xml_clone(node);
+ }
+ return first;
+}
+
+rexmpp_xml_t *rexmpp_xml_new_text (const char *str) {
+ rexmpp_xml_t *node = malloc(sizeof(rexmpp_xml_t));
+ node->type = REXMPP_XML_TEXT;
+ node->alt.text = strdup(str);
+ node->next = NULL;
+ return node;
+}
+
+rexmpp_xml_t *rexmpp_xml_new_text_len (const char *str, size_t len) {
+ rexmpp_xml_t *node = malloc(sizeof(rexmpp_xml_t));
+ node->type = REXMPP_XML_TEXT;
+ node->alt.text = strndup(str, len);
+ node->next = NULL;
+ return node;
+}
+
+void rexmpp_xml_add_child (rexmpp_xml_t *node,
+ rexmpp_xml_t *child)
+{
+ rexmpp_xml_t **last_ptr = &(node->alt.elem.children);
+ while (*last_ptr != NULL) {
+ last_ptr = &((*last_ptr)->next);
+ }
+ *last_ptr = child;
+}
+
+int rexmpp_xml_add_text (rexmpp_xml_t *node,
+ const char *str)
+{
+ rexmpp_xml_t *text_node = rexmpp_xml_new_text(str);
+ if (text_node != NULL) {
+ rexmpp_xml_add_child(node, text_node);
+ return 0;
+ }
+ return -1;
+}
+
+int rexmpp_xml_add_text_len (rexmpp_xml_t *node,
+ const char *str,
+ size_t len)
+{
+ rexmpp_xml_t *text_node = rexmpp_xml_new_text_len(str, len);
+ if (text_node != NULL) {
+ rexmpp_xml_add_child(node, text_node);
+ return 0;
+ }
+ return -1;
+}
+
+rexmpp_xml_t *rexmpp_xml_new_elem (const char *name,
+ const char *namespace)
+{
+ rexmpp_xml_t *node = malloc(sizeof(rexmpp_xml_t));
+ node->type = REXMPP_XML_ELEMENT;
+ node->alt.elem.qname.name = strdup(name);
+ if (namespace != NULL) {
+ node->alt.elem.qname.namespace = strdup(namespace);
+ } else {
+ node->alt.elem.qname.namespace = NULL;
+ }
+ node->alt.elem.attributes = NULL;
+ node->alt.elem.children = NULL;
+ node->next = NULL;
+ return node;
+}
+
+rexmpp_xml_attr_t *rexmpp_xml_attr_new (const char *name,
+ const char *namespace,
+ const char *value)
+{
+ rexmpp_xml_attr_t *attr = malloc(sizeof(rexmpp_xml_attr_t));
+ attr->qname.name = strdup(name);
+ if (namespace != NULL) {
+ attr->qname.namespace = strdup(namespace);
+ } else {
+ attr->qname.namespace = NULL;
+ }
+ attr->value = strdup(value);
+ attr->next = NULL;
+ return attr;
+}
+
+int rexmpp_xml_add_attr_ns (rexmpp_xml_t *node,
+ const char *name,
+ const char *namespace,
+ const char *value)
+{
+ if (node == NULL || node->type != REXMPP_XML_ELEMENT) {
+ return -1;
+ }
+ rexmpp_xml_attr_t *attr =
+ rexmpp_xml_attr_new(name, namespace, value);
+ attr->next = node->alt.elem.attributes;
+ node->alt.elem.attributes = attr;
+ return 0;
+}
+
+int rexmpp_xml_remove_attr_ns (rexmpp_xml_t *node,
+ const char *name,
+ const char *namespace) {
+ if (node == NULL || node->type != REXMPP_XML_ELEMENT) {
+ return -1;
+ }
+
+ rexmpp_xml_attr_t **attr, *next_attr;
+ for (attr = &(node->alt.elem.attributes); *attr != NULL; attr = &((*attr)->next)) {
+ if (rexmpp_xml_attr_match(*attr, namespace, name)) {
+ next_attr = (*attr)->next;
+ rexmpp_xml_attribute_free(*attr);
+ *attr = next_attr;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int rexmpp_xml_add_attr (rexmpp_xml_t *node,
+ const char *name,
+ const char *value)
+{
+ return rexmpp_xml_add_attr_ns(node, name, NULL, value);
+}
+
+int rexmpp_xml_remove_attr (rexmpp_xml_t *node,
+ const char *name) {
+ return rexmpp_xml_remove_attr_ns(node, name, NULL);
+}
+
+/* Adds a character, grows the string as needed. */
+static inline char *rexmpp_str_putc (char *str, size_t *len, char c) {
+ char *ret = str;
+ if ((*len) % 1024 == 0) {
+ ret = realloc(str, (*len) + 1024);
+ if (ret == NULL) {
+ /* A failure to realloc. */
+ if (str != NULL) {
+ free(str);
+ }
+ return NULL;
+ }
+ }
+ ret[*len] = c;
+ *len = (*len) + 1;
+ return ret;
+}
+
+static inline
+char *rexmpp_str_putc_escaped (char *str, size_t *len, char c) {
+ char *ret = str;
+ char buf[7];
+ char *esc = buf;
+ size_t i = 0;
+ size_t esc_len;
+ if (c == '<') {
+ esc = "&lt;";
+ } else if (c == '>') {
+ esc = "&gt;";
+ } else if (c == '&') {
+ esc = "&amp;";
+ } else if (c == '\'') {
+ esc = "&apos;";
+ } else if (c == '"') {
+ esc = "&quot;";
+ } else {
+ snprintf(esc, 7, "&#%u;", c);
+ }
+ esc_len = strlen(esc);
+ while (i < esc_len) {
+ ret = rexmpp_str_putc(ret, len, esc[i]);
+ i++;
+ }
+ return ret;
+}
+
+char *rexmpp_xml_print_name (char *str, size_t *len, const char *name) {
+ char *ret = str;
+ size_t name_len = strlen(name);
+ size_t i = 0;
+ int32_t c = 0; /* matches ICU's UChar32 */
+ size_t prev_i = 0, j;
+ do {
+ REXMPP_U8_NEXT(name, i, name_len, c);
+ if (c >= 0) {
+ if (c == ':'
+ || (c >= 'A' && c <= 'Z')
+ || c == '_'
+ || (c >= 'a' && c <= 'z')
+ || (c >= 0xC0 && c <= 0xD6)
+ || (c >= 0xD8 && c <= 0xF6)
+ || (c >= 0xF8 && c <= 0x2FF)
+ || (c >= 0x370 && c <= 0x37D)
+ || (c >= 0x37F && c <= 0x1FFF)
+ || (c >= 0x200C && c <= 0x200D)
+ || (c >= 0x2070 && c <= 0x218F)
+ || (c >= 0x2C00 && c <= 0x2FEF)
+ || (c >= 0x3001 && c <= 0xD7FF)
+ || (c >= 0xF900 && c <= 0xFDCF)
+ || (c >= 0xFDF0 && c <= 0xFFF0)
+ || (c >= 0x10000 && c <= 0xEFFFF)
+ || ((i > 0) &&
+ (c == '-'
+ || c == '.'
+ || (c >= '0' && c <= '9')
+ || c == 0xB7
+ || (c >= 0x0300 && c <= 0x036F)
+ || (c >= 0x203F && c <= 0x2040))))
+ {
+ /* Print the allowed characters. */
+ for (j = prev_i; j < i; j++) {
+ ret = rexmpp_str_putc(ret, len, name[j]);
+ }
+ }
+ } else {
+ /* Skip invalid characters. */
+ i++;
+ }
+ prev_i = i;
+ } while (i < name_len);
+ return ret;
+}
+
+char *rexmpp_xml_print_text (char *str, size_t *len, const char *text) {
+ char *ret = str;
+ size_t i = 0;
+ size_t text_len = strlen(text);
+ while (i < text_len && ret != NULL) {
+ char c = text[i];
+ if (strchr("<&>'\"", c)) {
+ /* Escape the few special characters. */
+ ret = rexmpp_str_putc_escaped(ret, len, c);
+ } else {
+ /* Write others as is. */
+ ret = rexmpp_str_putc(ret, len, c);
+ }
+ i++;
+ }
+ return ret;
+}
+
+char *rexmpp_xml_print_raw (char *str, size_t *len, const char *text) {
+ char *ret = str;
+ size_t i = 0;
+ size_t text_len = strlen(text);
+ while (i < text_len && ret != NULL) {
+ char c = text[i];
+ ret = rexmpp_str_putc(ret, len, c);
+ i++;
+ }
+ return ret;
+}
+
+static inline char *rexmpp_xml_print_indent (char *str,
+ size_t *len,
+ int indent) {
+ if (indent <= 0) {
+ return str;
+ }
+ int i;
+ char *ret = str;
+ for (i = 0; i < indent * 2; i++) {
+ ret = rexmpp_str_putc(ret, len, ' ');
+ }
+ return ret;
+}
+
+char *rexmpp_xml_print (char *str,
+ size_t *len,
+ const rexmpp_xml_t *node,
+ int indent) {
+ char *ret = str;
+ if (node->type == REXMPP_XML_TEXT) {
+ ret = rexmpp_xml_print_text(ret, len, node->alt.text);
+ } else if (node->type == REXMPP_XML_ELEMENT) {
+ if (indent > 0) {
+ ret = rexmpp_str_putc(ret, len, '\n');
+ ret = rexmpp_xml_print_indent(ret, len, indent);
+ }
+ ret = rexmpp_str_putc(ret, len, '<');
+ ret = rexmpp_xml_print_name(ret, len, node->alt.elem.qname.name);
+ if (node->alt.elem.qname.namespace != NULL) {
+ ret = rexmpp_xml_print_raw(ret, len, " xmlns=\"");
+ ret = rexmpp_xml_print_text(ret, len, node->alt.elem.qname.namespace);
+ ret = rexmpp_str_putc(ret, len, '"');
+ }
+ if (node->alt.elem.attributes != NULL) {
+ rexmpp_xml_attr_t *attr;
+ for (attr = node->alt.elem.attributes; attr != NULL; attr = attr->next) {
+ ret = rexmpp_str_putc(ret, len, ' ');
+ /* Ignoring namespaces here for now. */
+ ret = rexmpp_xml_print_name(ret, len, attr->qname.name);
+ ret = rexmpp_xml_print_raw(ret, len, "=\"");
+ ret = rexmpp_xml_print_text(ret, len, attr->value);
+ ret = rexmpp_str_putc(ret, len, '"');
+ }
+ }
+ if (node->alt.elem.children == NULL) {
+ ret = rexmpp_xml_print_raw(ret, len, "/>");
+ } else {
+ ret = rexmpp_str_putc(ret, len, '>');
+ rexmpp_xml_t *child;
+ int last_child_is_textual = 0;
+ for (child = rexmpp_xml_children(node);
+ child != NULL;
+ child = child->next)
+ {
+ ret = rexmpp_xml_print(ret, len, child,
+ indent > -1 ? indent + 1 : -1);
+ last_child_is_textual = child->type == REXMPP_XML_TEXT;
+ }
+ if (indent >= 0 && ! last_child_is_textual) {
+ ret = rexmpp_str_putc(ret, len, '\n');
+ ret = rexmpp_xml_print_indent(ret, len, indent);
+ }
+ ret = rexmpp_xml_print_raw(ret, len, "</");
+ ret = rexmpp_xml_print_name(ret, len, node->alt.elem.qname.name);
+ ret = rexmpp_str_putc(ret, len, '>');
+ }
+ }
+ return ret;
+}
+
+char *rexmpp_xml_serialize (const rexmpp_xml_t *node, int pretty) {
+ size_t s_len = 0;
+ char *s = NULL;
+ s = rexmpp_xml_print(s, &s_len, node, pretty ? 0 : -1);
+ s = rexmpp_str_putc(s, &s_len, '\0');
+ return s;
+}
+
+rexmpp_xml_t *
+rexmpp_xml_add_id (rexmpp_xml_t *node)
+{
+ char *buf = rexmpp_random_id();
+ if (buf == NULL) {
+ return NULL;
+ }
+ rexmpp_xml_add_attr(node, "id", buf);
+ free(buf);
+ return node;
+}
+#endif
+
+/* These SAX handlers are similar to those from rexmpp.c, and perhaps
+ can be reused. */
+void rexmpp_xml_parse_sax_characters (struct rexmpp_xml_builder *builder,
+ const char *ch,
+ size_t len)
+{
+ if (builder->current != NULL) {
+ rexmpp_xml_t *last_node = builder->current->alt.elem.children;
+ if (last_node != NULL && last_node->type == REXMPP_XML_TEXT) {
+ /* The last child is textual as well, just extend it */
+ size_t last_len = strlen(last_node->alt.text);
+ char *new_alt_text = realloc(last_node->alt.text, last_len + len + 1);
+ if (new_alt_text == NULL) {
+ /* TODO: Would be nice a report an error here. */
+ return;
+ }
+ last_node->alt.text = new_alt_text;
+ strncpy(last_node->alt.text + last_len, ch, len);
+ last_node->alt.text[last_len + len] = '\0';
+ } else {
+ rexmpp_xml_t *text_node = rexmpp_xml_new_text_len(ch, len);
+ if (text_node != NULL) {
+ text_node->next = builder->current->alt.elem.children;
+ builder->current->alt.elem.children = text_node;
+ }
+ }
+ }
+}
+
+void rexmpp_xml_parse_sax_start_elem_ns (struct rexmpp_xml_builder *builder,
+ const char *name,
+ const char *namespace,
+ rexmpp_xml_attr_t *attributes)
+{
+ if (builder->current == NULL && builder->root == NULL) {
+ /* Just started */
+ builder->current = rexmpp_xml_new_elem(name, namespace);
+ builder->root = builder->current;
+ } else if (builder->current != NULL) {
+ /* Parsing is in progress */
+ rexmpp_xml_t *node = rexmpp_xml_new_elem(name, namespace);
+ node->next = builder->current->alt.elem.children;
+ builder->current->alt.elem.children = node;
+ builder->current = node;
+ } else {
+ /* The parsind is over, but we are receiving these events
+ still. Just free the attribute lists, ignore the rest. */
+ rexmpp_xml_attribute_free_list(attributes);
+ }
+ builder->current->alt.elem.attributes = attributes;
+}
+
+void rexmpp_xml_parse_sax_end_elem_ns (struct rexmpp_xml_builder *builder)
+{
+ if (builder->current != builder->root) {
+ /* Find the parent, set it as current element. */
+ rexmpp_xml_t *parent = builder->root;
+ while (parent->alt.elem.children != builder->current) {
+ parent = parent->alt.elem.children;
+ }
+ builder->current = parent;
+ } else {
+ /* Done parsing this element; reverse all the lists of children. */
+ builder->current = NULL;
+ rexmpp_xml_reverse_children(builder->root);
+ }
+}
+
+struct rexmpp_xml_parser_handlers builder_sax = {
+ (rexmpp_xml_parser_element_start)rexmpp_xml_parse_sax_start_elem_ns,
+ (rexmpp_xml_parser_element_end)rexmpp_xml_parse_sax_end_elem_ns,
+ (rexmpp_xml_parser_characters)rexmpp_xml_parse_sax_characters
+};
+
+rexmpp_xml_t *rexmpp_xml_parse (const char *str, int str_len) {
+ struct rexmpp_xml_builder builder = { NULL, NULL };
+ rexmpp_xml_parser_ctx_t parser =
+ rexmpp_xml_parser_new(&builder_sax, &builder);
+ rexmpp_xml_parser_feed(parser, str, str_len, 1);
+ rexmpp_xml_parser_free(parser);
+ if (builder.current != NULL) {
+ /* The parsing is not complete. */
+ rexmpp_xml_free(builder.root);
+ return NULL;
+ }
+ return builder.root;
+}
+
+rexmpp_xml_t *rexmpp_xml_read_fd (int fd) {
+ struct rexmpp_xml_builder builder = { NULL, NULL };
+ rexmpp_xml_parser_ctx_t parser =
+ rexmpp_xml_parser_new(&builder_sax, &builder);
+ if (parser == NULL) {
+ return NULL;
+ }
+
+ char buf[4096];
+ ssize_t buf_len = 0;
+ do {
+ buf_len = read(fd, buf, 4096);
+ if (buf_len > 0) {
+ rexmpp_xml_parser_feed(parser, buf, buf_len, 0);
+ }
+ } while (buf_len > 0 &&
+ ! (builder.root != NULL && builder.current == NULL) );
+
+ rexmpp_xml_parser_free(parser);
+
+ if (builder.current != NULL) {
+ /* The parsing is not complete. */
+ rexmpp_xml_free(builder.root);
+ return NULL;
+ }
+
+ return builder.root;
+}
+
+rexmpp_xml_t *rexmpp_xml_read_file (const char *path) {
+ int fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ return NULL;
+ }
+ rexmpp_xml_t *node = rexmpp_xml_read_fd(fd);
+ close(fd);
+ return node;
+}
+
+#ifndef USE_RUST
+int rexmpp_xml_write_file (const char *path, rexmpp_xml_t* node) {
+ FILE *fd = fopen(path, "w");
+ if (fd == NULL) {
+ return -1;
+ }
+ char *serialized = rexmpp_xml_serialize(node, 1);
+ fputs("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n", fd);
+ fputs(serialized, fd);
+ fclose(fd);
+ return 0;
+}
+
+unsigned int rexmpp_xml_siblings_count (rexmpp_xml_t *node) {
+ unsigned int i = 0;
+ for (i = 0; node != NULL; i++) {
+ node = node->next;
+ }
+ return i;
+}
+
+int rexmpp_xml_match (rexmpp_xml_t *node,
+ const char *namespace,
+ const char *name)
+{
+ if (node == NULL) {
+ return 0;
+ }
+ if (node->type != REXMPP_XML_ELEMENT) {
+ return 0;
+ }
+ if (name != NULL) {
+ if (strcmp(name, node->alt.elem.qname.name) != 0) {
+ return 0;
+ }
+ }
+ if (namespace != NULL) {
+ if (node->alt.elem.qname.namespace == NULL &&
+ strcmp(namespace, "jabber:client") != 0) {
+ return 0;
+ } else if (node->alt.elem.qname.namespace != NULL) {
+ if (strcmp(namespace, node->alt.elem.qname.namespace) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+
+int rexmpp_xml_attr_match (rexmpp_xml_attr_t *attr,
+ const char *namespace,
+ const char *name)
+{
+ if (attr == NULL) {
+ return 0;
+ }
+ if (name != NULL) {
+ if (strcmp(name, attr->qname.name) != 0) {
+ return 0;
+ }
+ }
+ if (namespace != NULL) {
+ if (attr->qname.namespace == NULL &&
+ strcmp(namespace, "jabber:client") != 0) {
+ return 0;
+ } else if (strcmp(namespace, attr->qname.namespace) != 0) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int rexmpp_xml_is_stanza (rexmpp_xml_t *node) {
+ return rexmpp_xml_match(node, "jabber:client", "message") ||
+ rexmpp_xml_match(node, "jabber:client", "iq") ||
+ rexmpp_xml_match(node, "jabber:client", "presence");
+}
+
+rexmpp_xml_t *rexmpp_xml_error (const char *type, const char *condition) {
+ rexmpp_xml_t * error = rexmpp_xml_new_elem("error", NULL);
+ rexmpp_xml_add_attr(error, "type", type);
+ rexmpp_xml_t * cond =
+ rexmpp_xml_new_elem(condition, "urn:ietf:params:xml:ns:xmpp-stanzas");
+ rexmpp_xml_add_child(error, cond);
+ return error;
+}
+
+rexmpp_xml_attr_t *
+rexmpp_xml_find_attr (rexmpp_xml_t *node,
+ const char *name,
+ const char *namespace)
+{
+ if (node == NULL || node->type != REXMPP_XML_ELEMENT) {
+ return NULL;
+ }
+ rexmpp_xml_attr_t *attr;
+ for (attr = node->alt.elem.attributes; attr != NULL; attr = attr->next) {
+ if (rexmpp_xml_attr_match(attr, namespace, name)) {
+ return attr;
+ }
+ }
+ return NULL;
+}
+
+const char *rexmpp_xml_find_attr_val_ns (rexmpp_xml_t *node,
+ const char *name,
+ const char *namespace) {
+ rexmpp_xml_attr_t *attr = rexmpp_xml_find_attr(node, name, namespace);
+ if (attr != NULL) {
+ return attr->value;
+ }
+ return NULL;
+}
+
+const char *rexmpp_xml_find_attr_val (rexmpp_xml_t *node,
+ const char *name) {
+ return rexmpp_xml_find_attr_val_ns(node, name, NULL);
+}
+
+rexmpp_xml_t *
+rexmpp_xml_find_child (rexmpp_xml_t *node,
+ const char *namespace,
+ const char *name)
+{
+ if (node == NULL || node->type != REXMPP_XML_ELEMENT) {
+ return NULL;
+ }
+ rexmpp_xml_t *child;
+ for (child = node->alt.elem.children; child != NULL; child = child->next) {
+ if (rexmpp_xml_match(child, namespace, name)) {
+ return child;
+ }
+ }
+ return NULL;
+}
+
+int rexmpp_xml_eq (rexmpp_xml_t *n1, rexmpp_xml_t *n2) {
+ /* Just serialize and compare strings for now: awkward, but
+ simple. */
+ char *n1str = rexmpp_xml_serialize(n1, 0);
+ char *n2str = rexmpp_xml_serialize(n2, 0);
+ int eq = (strcmp(n1str, n2str) == 0);
+ free(n1str);
+ free(n2str);
+ return eq;
+}
+
+rexmpp_xml_t *rexmpp_xml_children (const rexmpp_xml_t *node) {
+ if (node != NULL && node->type == REXMPP_XML_ELEMENT) {
+ return node->alt.elem.children;
+ }
+ return NULL;
+}
+
+rexmpp_xml_t *rexmpp_xml_first_elem_child (rexmpp_xml_t *node) {
+ rexmpp_xml_t *child;
+ for (child = rexmpp_xml_children(node); child != NULL; child = child->next) {
+ if (child->type == REXMPP_XML_ELEMENT) {
+ return child;
+ }
+ }
+ return NULL;
+}
+
+rexmpp_xml_t *rexmpp_xml_next_elem_sibling (rexmpp_xml_t *node) {
+ if (node == NULL) {
+ return NULL;
+ }
+ rexmpp_xml_t *sibling;
+ for (sibling = node->next; sibling != NULL; sibling = sibling->next) {
+ if (sibling->type == REXMPP_XML_ELEMENT) {
+ return sibling;
+ }
+ }
+ return NULL;
+}
+
+char *rexmpp_xml_text (rexmpp_xml_t *node) {
+ if (node != NULL && node->type == REXMPP_XML_TEXT) {
+ return node->alt.text;
+ }
+ return NULL;
+}
+
+char *rexmpp_xml_text_child (rexmpp_xml_t *node) {
+ return rexmpp_xml_text(rexmpp_xml_children(node));
+}
+
+rexmpp_xml_t *rexmpp_xml_reverse_list (rexmpp_xml_t *node) {
+ rexmpp_xml_t *next, *prev = NULL;
+ while (node != NULL) {
+ next = node->next;
+ node->next = prev;
+ prev = node;
+ node = next;
+ }
+ return prev;
+}
+
+void rexmpp_xml_reverse_children (rexmpp_xml_t *node) {
+ if (node == NULL || node->type != REXMPP_XML_ELEMENT) {
+ return;
+ }
+ node->alt.elem.children = rexmpp_xml_reverse_list(node->alt.elem.children);
+ rexmpp_xml_t *cur;
+ for (cur = node->alt.elem.children; cur != NULL; cur = cur->next) {
+ if (cur->type == REXMPP_XML_ELEMENT && cur->alt.elem.children != NULL) {
+ rexmpp_xml_reverse_children(cur);
+ }
+ }
+}
+
+#endif
diff --git a/src/rexmpp_xml.h b/src/rexmpp_xml.h
new file mode 100644
index 0000000..38142ae
--- /dev/null
+++ b/src/rexmpp_xml.h
@@ -0,0 +1,269 @@
+/**
+ @file rexmpp_xml.h
+ @brief XML structures and functions for rexmpp
+ @author defanor <defanor@uberspace.net>
+ @date 2023
+ @copyright MIT license.
+*/
+
+#ifndef REXMPP_XML_H
+#define REXMPP_XML_H
+
+#include <stdio.h>
+
+typedef struct rexmpp_xml_qname rexmpp_xml_qname_t;
+typedef struct rexmpp_xml_attribute rexmpp_xml_attr_t;
+typedef struct rexmpp_xml_node rexmpp_xml_t;
+
+struct rexmpp_xml_qname {
+ char *name;
+ char *namespace;
+};
+
+struct rexmpp_xml_attribute {
+ rexmpp_xml_qname_t qname;
+ char *value;
+ rexmpp_xml_attr_t *next;
+};
+
+enum rexmpp_xml_node_type {
+ REXMPP_XML_ELEMENT,
+ REXMPP_XML_TEXT
+};
+
+typedef enum rexmpp_xml_node_type rexmpp_xml_node_type_t;
+
+struct rexmpp_xml_node {
+ rexmpp_xml_node_type_t type;
+ union {
+ struct {
+ rexmpp_xml_qname_t qname;
+ rexmpp_xml_attr_t *attributes;
+ rexmpp_xml_t *children;
+ } elem;
+ char *text;
+ } alt;
+ rexmpp_xml_t *next;
+};
+
+struct rexmpp_xml_builder {
+ rexmpp_xml_t *current;
+ rexmpp_xml_t *root;
+};
+
+void rexmpp_xml_qname_free (rexmpp_xml_qname_t *qname);
+void rexmpp_xml_attribute_free (rexmpp_xml_attr_t *attr);
+void rexmpp_xml_attribute_free_list (rexmpp_xml_attr_t *attr);
+
+/**
+ @brief Frees a single XML node. Does not free its siblings.
+*/
+void rexmpp_xml_free (rexmpp_xml_t *node);
+
+/**
+ @brief Frees an XML node and its siblings.
+*/
+void rexmpp_xml_free_list (rexmpp_xml_t *node);
+
+/**
+ @brief Clones a single XML node, without its siblings.
+*/
+rexmpp_xml_t *rexmpp_xml_clone (rexmpp_xml_t *node);
+
+/**
+ @brief Clones an XML node, together with its siblings.
+*/
+rexmpp_xml_t *rexmpp_xml_clone_list (rexmpp_xml_t *node);
+
+/**
+ @brief Creates a textual ::rexmpp_xml_t XML node (with type =
+ ::REXMPP_XML_TEXT).
+*/
+rexmpp_xml_t *rexmpp_xml_new_text (const char *str);
+
+/**
+ @brief Creates a textual ::rexmpp_xml_t XML node (with type =
+ ::REXMPP_XML_TEXT).
+*/
+rexmpp_xml_t *rexmpp_xml_new_text_len (const char *str, size_t len);
+
+/**
+ @brief Creates an element ::rexmpp_xml_t XML node (with type =
+ ::REXMPP_XML_ELEMENT).
+*/
+rexmpp_xml_t *rexmpp_xml_new_elem (const char *name,
+ const char *namespace);
+
+/**
+ @brief Adds a child node.
+*/
+void rexmpp_xml_add_child (rexmpp_xml_t *node,
+ rexmpp_xml_t *child);
+
+/**
+ @brief Creates a text node, and adds it as a child.
+*/
+int rexmpp_xml_add_text (rexmpp_xml_t *node,
+ const char *str);
+
+/**
+ @brief Creates a text node, and adds it as a child.
+*/
+int rexmpp_xml_add_text_len (rexmpp_xml_t *node,
+ const char *str,
+ size_t len);
+
+rexmpp_xml_attr_t *rexmpp_xml_attr_new (const char *name,
+ const char *namespace,
+ const char *value);
+
+int rexmpp_xml_add_attr (rexmpp_xml_t *node,
+ const char *name,
+ const char *value);
+
+int rexmpp_xml_remove_attr_ns (rexmpp_xml_t *node,
+ const char *name,
+ const char *namespace);
+
+int rexmpp_xml_remove_attr (rexmpp_xml_t *node,
+ const char *name);
+
+int rexmpp_xml_add_attr_ns (rexmpp_xml_t *node,
+ const char *name,
+ const char *namespace,
+ const char *value);
+
+/**
+ @brief Adds an "id" attribute to an XML stanza.
+ @param[in,out] s ::rexmpp
+ @param[in] node A pointer to an XML stanza.
+ @returns The same pointer as on input, for more convenient
+ composition.
+*/
+rexmpp_xml_t *
+rexmpp_xml_add_id (rexmpp_xml_t *node);
+
+/**
+ @brief A helper function for XML serialisation.
+ @param[in] node An XML node.
+ @returns A string (must be freed by the caller).
+*/
+char *rexmpp_xml_serialize (const rexmpp_xml_t *node, int pretty);
+
+/**
+ @brief Count the number of siblings after a given node.
+*/
+unsigned int rexmpp_xml_siblings_count (rexmpp_xml_t *node);
+
+/**
+ @brief Compares the node's name and namespace to given ones.
+*/
+int rexmpp_xml_match (rexmpp_xml_t *node,
+ const char *namespace,
+ const char *name);
+
+int rexmpp_xml_is_stanza (rexmpp_xml_t *node);
+
+/**
+ @brief Compose an 'error' element.
+*/
+rexmpp_xml_t *rexmpp_xml_error (const char *type, const char *condition);
+
+/**
+ @brief Matches an XML node against a namespace and an element name.
+ @param[in] node An XML node to match.
+ @param[in] namespace An XML namespace. Can be NULL (matches
+ anything), and it is assumed that the default namespace is
+ "jabber:client" (so if it is "jabber:client" and an element doesn't
+ have a namespace defined, this function counts that as a match).
+ @param[in] name Element name. Can be NULL (matches anything).
+ @returns 1 on a successful match, 0 otherwise.
+*/
+int rexmpp_xml_attr_match (rexmpp_xml_attr_t *attr,
+ const char *namespace,
+ const char *name);
+
+rexmpp_xml_attr_t *rexmpp_xml_find_attr (rexmpp_xml_t *node,
+ const char *name,
+ const char *namespace);
+
+const char *rexmpp_xml_find_attr_val_ns (rexmpp_xml_t *node,
+ const char *name,
+ const char *namespace);
+
+const char *rexmpp_xml_find_attr_val (rexmpp_xml_t *node,
+ const char *name);
+
+/**
+ @brief Finds a child element of an XML node, which matches the
+ given namespace and name.
+ @param[in] node The node containing child nodes.
+ @param[in] namespace The namespace to look for.
+ @param[in] name The element name to look for.
+ @returns A pointer to the first matching child node, or NULL if no
+ matching child elements found.
+*/
+rexmpp_xml_t *rexmpp_xml_find_child (rexmpp_xml_t *node,
+ const char *namespace,
+ const char *name);
+
+rexmpp_xml_t *rexmpp_xml_children (const rexmpp_xml_t *node);
+
+char *rexmpp_xml_text (rexmpp_xml_t *node);
+
+char *rexmpp_xml_text_child (rexmpp_xml_t *node);
+
+rexmpp_xml_t *rexmpp_xml_first_elem_child (rexmpp_xml_t *node);
+
+rexmpp_xml_t *rexmpp_xml_next_elem_sibling (rexmpp_xml_t *node);
+
+/**
+ @brief Compares two XML elements.
+*/
+int rexmpp_xml_eq (rexmpp_xml_t *n1, rexmpp_xml_t *n2);
+
+/**
+ @brief A helper function for XML parsing.
+ @param[in] str A string to parse.
+ @param[in] str_len String length.
+ @returns Parsed XML, or NULL on failure.
+*/
+rexmpp_xml_t *rexmpp_xml_parse (const char *str, int str_len);
+
+/**
+ @brief Reads XML from a file stream, reading the stream line by
+ line.
+ @param[in] fd A file descriptor
+ @returns Parsed XML, or NULL on failure.
+*/
+rexmpp_xml_t *rexmpp_xml_read_fd (int fd);
+
+/**
+ @brief Reads XML from a file
+ @param[in] path A file path
+ @returns Parsed XML, or NULL on failure.
+*/
+rexmpp_xml_t *rexmpp_xml_read_file (const char *path);
+
+/**
+ @brief Writes XML into a file
+ @param[in] path A file path
+ @param[in] node XML to write
+ @returns 0 on success, -1 on failure.
+*/
+int rexmpp_xml_write_file (const char *path, rexmpp_xml_t* node);
+
+/**
+ @brief Reverses a linked list of XML nodes
+ @param[in,out] node The head of the list to reverse
+ @returns The new head of the list
+*/
+rexmpp_xml_t *rexmpp_xml_reverse_list (rexmpp_xml_t *node);
+
+/**
+ @brief Recursively reverses children of an XML element
+ @param[in,out] node The root XML element
+*/
+void rexmpp_xml_reverse_children (rexmpp_xml_t *node);
+
+#endif
diff --git a/src/rexmpp_xml.rs b/src/rexmpp_xml.rs
new file mode 100644
index 0000000..f0d292a
--- /dev/null
+++ b/src/rexmpp_xml.rs
@@ -0,0 +1,1040 @@
+extern crate libc;
+use libc::{strdup, strndup, free, strcmp};
+use std::os::raw::{c_char, c_int, c_void, c_uint};
+use std::ptr;
+use std::ffi::{CStr, CString};
+use std::clone::Clone;
+use std::fs::File;
+use std::io::Write;
+
+use super::{rexmpp};
+use super::{rexmpp_random};
+
+// extern {
+// fn rexmpp_xml_parse (str: *mut c_char, str_len: c_int) -> *mut RexmppXML;
+// }
+
+#[repr(C)]
+pub struct RexmppXMLQName {
+ pub name: *mut c_char,
+ pub namespace: *mut c_char
+}
+
+impl Copy for RexmppXMLQName { }
+
+impl Clone for RexmppXMLQName {
+ fn clone(&self) -> RexmppXMLQName {
+ RexmppXMLQName {
+ name: unsafe { strdup(self.name) },
+ namespace: if self.namespace != ptr::null_mut() {
+ unsafe { strdup(self.namespace) }
+ } else {
+ ptr::null_mut()
+ }
+ }
+ }
+}
+
+#[repr(C)]
+pub struct RexmppXMLAttribute {
+ pub qname: RexmppXMLQName,
+ pub value: *mut c_char,
+ pub next: *mut RexmppXMLAttribute
+}
+
+impl Copy for RexmppXMLAttribute { }
+
+impl Clone for RexmppXMLAttribute {
+ fn clone(&self) -> RexmppXMLAttribute {
+ RexmppXMLAttribute {
+ qname: Clone::clone(&self.qname),
+ value: unsafe { strdup(self.value) },
+ next: ptr::null_mut()
+ }
+ }
+}
+
+#[derive(Copy, Clone)]
+#[derive(PartialEq)]
+#[repr(C)]
+pub enum NodeType {
+ Element,
+ Text
+}
+
+#[repr(C)]
+pub struct RexmppXMLAltElem {
+ pub qname: RexmppXMLQName,
+ pub attributes: *mut RexmppXMLAttribute,
+ pub children: *mut RexmppXML
+}
+
+impl Copy for RexmppXMLAltElem { }
+
+impl Clone for RexmppXMLAltElem {
+ fn clone(&self) -> RexmppXMLAltElem {
+ unsafe {
+ let mut ret = RexmppXMLAltElem {
+ qname: Clone::clone(&self.qname),
+ attributes: ptr::null_mut(),
+ children: ptr::null_mut()
+ };
+ let mut old_attr_ptr = self.attributes;
+ let mut next_attr_ptr_ptr : *mut *mut RexmppXMLAttribute = &mut ret.attributes;
+ loop {
+ match old_attr_ptr.as_mut() {
+ None => break,
+ Some(old_attr) => {
+ let new_attr_ptr = rexmpp_xml_attr_new(old_attr.qname.name,
+ old_attr.qname.namespace,
+ old_attr.value);
+ next_attr_ptr_ptr.write(new_attr_ptr);
+ next_attr_ptr_ptr = &mut ((*new_attr_ptr).next);
+ old_attr_ptr = old_attr.next;
+ }
+ }
+ }
+ ret.children = rexmpp_xml_clone_list(self.children);
+ return ret;
+ }
+ }
+}
+
+#[derive(Copy, Clone)]
+#[repr(C)]
+pub enum RexmppXMLAlt {
+ Elem(RexmppXMLAltElem),
+ Text(*mut c_char)
+}
+
+#[repr(C)]
+pub struct RexmppXML {
+ pub alt: RexmppXMLAlt,
+ pub next: *mut RexmppXML
+}
+
+impl Copy for RexmppXML { }
+
+impl Clone for RexmppXML {
+ fn clone(&self) -> RexmppXML {
+ RexmppXML {
+ alt: match self.alt {
+ RexmppXMLAlt::Text(text) =>
+ RexmppXMLAlt::Text(unsafe { strdup(text) }),
+ RexmppXMLAlt::Elem(e) =>
+ RexmppXMLAlt::Elem(Clone::clone(& e))
+ },
+ next: ptr::null_mut()
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_qname_free (qname_ptr: *mut RexmppXMLQName) {
+ match unsafe { qname_ptr.as_mut() } {
+ None => return,
+ Some(qname) => {
+ if qname.name != ptr::null_mut() {
+ unsafe { free(qname.name as *mut c_void) };
+ qname.name = ptr::null_mut();
+ }
+ if qname.namespace != ptr::null_mut() {
+ unsafe { free(qname.namespace as *mut c_void) };
+ qname.namespace = ptr::null_mut();
+ }
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_attribute_free (attr_ptr: *mut RexmppXMLAttribute) {
+ if attr_ptr == ptr::null_mut() {
+ return;
+ }
+ let mut attr : RexmppXMLAttribute = unsafe { *Box::from_raw(attr_ptr) };
+ rexmpp_xml_qname_free(&mut (attr.qname));
+ if attr.value != ptr::null_mut() {
+ unsafe { free(attr.value as *mut c_void) }
+ attr.value = ptr::null_mut();
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_attribute_free_list (mut attr_ptr: *mut RexmppXMLAttribute) {
+ let mut next;
+ while attr_ptr != ptr::null_mut() {
+ next = unsafe { (*attr_ptr).next };
+ rexmpp_xml_attribute_free(attr_ptr);
+ attr_ptr = next;
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_free (node_ptr: *mut RexmppXML) {
+ if node_ptr == ptr::null_mut() {
+ return;
+ }
+ let mut node : RexmppXML = unsafe { *Box::from_raw(node_ptr) };
+ unsafe {
+ match node.alt {
+ RexmppXMLAlt::Text(mut t) => {
+ free(t as *mut c_void);
+ t = ptr::null_mut();
+ },
+ RexmppXMLAlt::Elem(mut element) => {
+ rexmpp_xml_qname_free(&mut (element.qname));
+ rexmpp_xml_attribute_free_list(element.attributes);
+ rexmpp_xml_free_list(element.children);
+ }
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_free_list (mut node_ptr: *mut RexmppXML) {
+ let mut next;
+ while node_ptr != ptr::null_mut() {
+ next = unsafe { (*node_ptr).next };
+ rexmpp_xml_free(node_ptr);
+ node_ptr = next;
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_clone (node_ptr: *mut RexmppXML) -> *mut RexmppXML {
+ if node_ptr == ptr::null_mut() {
+ return ptr::null_mut();
+ }
+ return Box::into_raw(Box::new(Clone::clone(& unsafe { *node_ptr })));
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_clone_list (mut node_ptr: *mut RexmppXML) -> *mut RexmppXML {
+ if node_ptr == ptr::null_mut() {
+ return ptr::null_mut();
+ }
+ let first_ptr = rexmpp_xml_clone(node_ptr);
+ let mut last_ptr = first_ptr;
+ node_ptr = unsafe { (*node_ptr).next };
+ while node_ptr != ptr::null_mut() {
+ unsafe { (*last_ptr).next = rexmpp_xml_clone(node_ptr) };
+ last_ptr = unsafe { (*last_ptr).next };
+ node_ptr = unsafe { (*node_ptr).next };
+ }
+ return first_ptr;
+}
+
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_new_text (str: *const c_char) -> *mut RexmppXML {
+ let node = RexmppXML {
+ alt: RexmppXMLAlt::Text( unsafe { strdup(str) } ),
+ next: ptr::null_mut()
+ };
+ let b = Box::new(node);
+ return Box::into_raw(b);
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_new_text_len (str: *const c_char, len: usize) -> *mut RexmppXML {
+ let node = RexmppXML {
+ alt: RexmppXMLAlt::Text( unsafe { strndup(str, len) } ),
+ next: ptr::null_mut()
+ };
+ let b = Box::new(node);
+ return Box::into_raw(b);
+}
+
+#[no_mangle]
+pub extern "C" fn rexmpp_xml_add_child (node: *mut RexmppXML,
+ child: *mut RexmppXML) -> () {
+ // It is important to wrap everything in "unsafe" here; somehow
+ // the enum fields are not mutated otherwise.
+ unsafe {
+ match (*node).alt {
+ RexmppXMLAlt::Elem(ref mut elem) => {
+ let mut last_ptr : &mut *mut RexmppXML =
+ &mut ((*elem).children);
+ while *last_ptr != ptr::null_mut() {
+ last_ptr = &mut ((*(* last_ptr)).next);
+ }
+ *last_ptr = child;
+ },
+ _ => ()
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rexmpp_xml_add_text (node: *mut RexmppXML,
+ str: *const c_char) -> c_int {
+ let text_node : *mut RexmppXML = rexmpp_xml_new_text(str);
+ if text_node != ptr::null_mut() {
+ rexmpp_xml_add_child(node, text_node);
+ return 1;
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rexmpp_xml_add_text_len (node: *mut RexmppXML,
+ str: *const c_char,
+ len: usize) -> c_int {
+ let text_node : *mut RexmppXML = rexmpp_xml_new_text_len(str, len);
+ if text_node != ptr::null_mut() {
+ rexmpp_xml_add_child(node, text_node);
+ return 1;
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_new_elem (name: *const c_char,
+ namespace: *const c_char) -> *mut RexmppXML {
+ let node = RexmppXML {
+ alt: RexmppXMLAlt::Elem (
+ RexmppXMLAltElem {
+ qname: RexmppXMLQName {
+ name: unsafe { strdup(name) },
+ namespace: if namespace == ptr::null_mut() {
+ ptr::null_mut()
+ } else {
+ unsafe { strdup(namespace) }
+ }
+ },
+ attributes: ptr::null_mut(),
+ children: ptr::null_mut()
+ }
+ ),
+ next: ptr::null_mut()
+ };
+ let b = Box::new(node);
+ return Box::into_raw(b);
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_attr_new (name: *const c_char,
+ namespace: *const c_char,
+ value: *const c_char) -> *mut RexmppXMLAttribute {
+ let node = RexmppXMLAttribute {
+ qname: RexmppXMLQName {
+ name: unsafe { strdup(name) },
+ namespace: if namespace == ptr::null_mut() {
+ ptr::null_mut()
+ } else {
+ unsafe { strdup(namespace) }
+ }
+ },
+ value: unsafe { strdup(value) },
+ next: ptr::null_mut()
+ };
+ return Box::into_raw(Box::new(node));
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_add_attr_ns (node: *mut RexmppXML,
+ name: *const c_char,
+ namespace: *const c_char,
+ value: *const c_char) -> c_int {
+ if node == ptr::null_mut() {
+ return -1;
+ }
+ // Wrapping everything into "unsafe", otherwise enum fields are
+ // not mutated.
+ unsafe {
+ match(*node).alt {
+ RexmppXMLAlt::Elem(ref mut elem) => {
+ let attr = rexmpp_xml_attr_new(name, namespace, value);
+ (*attr).next = (*elem).attributes;
+ (*elem).attributes = attr;
+ 0
+ },
+ _ => -1
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_remove_attr_ns (node: *mut RexmppXML,
+ name: *const c_char,
+ namespace: *const c_char) -> c_int {
+ if node == ptr::null_mut() {
+ return -1;
+ }
+ // Wrapping everything into "unsafe", otherwise enum fields are
+ // not mutated.
+ unsafe {
+ match (*node).alt {
+ RexmppXMLAlt::Elem(ref mut elem) => {
+ let mut attr_ptr_ptr: *mut *mut RexmppXMLAttribute =
+ &mut (*elem).attributes;
+ while *attr_ptr_ptr != ptr::null_mut() {
+ if rexmpp_xml_attr_match(*attr_ptr_ptr,
+ namespace, name) > 0 {
+ let next_attr_ptr : *mut RexmppXMLAttribute =
+ (**attr_ptr_ptr).next;
+ rexmpp_xml_attribute_free(*attr_ptr_ptr);
+ *attr_ptr_ptr = next_attr_ptr;
+ return 0;
+ }
+ attr_ptr_ptr = &mut (**attr_ptr_ptr).next;
+ }
+ 1
+ },
+ _ => -1,
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_add_attr (node: *mut RexmppXML,
+ name: *const c_char,
+ value: *const c_char) -> c_int {
+ rexmpp_xml_add_attr_ns(node, name, ptr::null_mut(), value)
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_remove_attr (node: *mut RexmppXML,
+ name: *const c_char) -> c_int {
+ rexmpp_xml_remove_attr_ns(node, name, ptr::null_mut())
+}
+
+
+fn rexmpp_xml_push_escaped (c: char, s: &mut String) {
+ if c == '<' {
+ s.push_str("&lt;")
+ } else if c == '>' {
+ s.push_str("&gt;")
+ } else if c == '&' {
+ s.push_str("&amp;")
+ } else if c == '\'' {
+ s.push_str("&apos;")
+ } else if c == '"' {
+ s.push_str("&quot;")
+ } else {
+ s.push_str(format!("&#{};", u32::from(c)).as_str());
+ };
+}
+
+fn rexmpp_xml_print_text (c: char, s: &mut String) {
+ if "<&>'\"".chars().any(|sc| sc == c) {
+ rexmpp_xml_push_escaped(c, s);
+ } else {
+ s.push(c);
+ }
+}
+
+fn rexmpp_xml_print_name (i: usize, c: char, s: &mut String) {
+ if c == ':'
+ || (c >= 'A' && c <= 'Z')
+ || c == '_'
+ || (c >= 'a' && c <= 'z')
+ || (c >= '\u{C0}' && c <= '\u{D6}')
+ || (c >= '\u{D8}' && c <= '\u{F6}')
+ || (c >= '\u{F8}' && c <= '\u{2FF}')
+ || (c >= '\u{370}' && c <= '\u{37D}')
+ || (c >= '\u{37F}' && c <= '\u{1FFF}')
+ || (c >= '\u{200C}' && c <= '\u{200D}')
+ || (c >= '\u{2070}' && c <= '\u{218F}')
+ || (c >= '\u{2C00}' && c <= '\u{2FEF}')
+ || (c >= '\u{3001}' && c <= '\u{D7FF}')
+ || (c >= '\u{F900}' && c <= '\u{FDCF}')
+ || (c >= '\u{FDF0}' && c <= '\u{FFF0}')
+ || (c >= '\u{10000}' && c <= '\u{EFFFF}')
+ || ((i > 0) &&
+ (c == '-'
+ || c == '.'
+ || (c >= '0' && c <= '9')
+ || c == '\u{B7}'
+ || (c >= '\u{0300}' && c <= '\u{036F}')
+ || (c >= '\u{203F}' && c <= '\u{2040}')))
+ {
+ // Print the allowed characters.
+ s.push(c);
+ }
+}
+
+fn rexmpp_xml_print_indent (indent: i32, s: &mut String) {
+ let mut i = 0;
+ while i < indent {
+ s.push_str(" ");
+ i = i + 1;
+ }
+}
+
+fn rexmpp_xml_print (node_ptr: *const RexmppXML,
+ ret: &mut String,
+ indent: i32)
+ -> ()
+{
+ unsafe {
+ let node : RexmppXML = *node_ptr;
+ match node {
+ RexmppXML { alt : RexmppXMLAlt::Text (text_ptr),
+ next: _} => {
+ let text_cstr : &CStr = CStr::from_ptr(text_ptr);
+ let text_str : String =
+ String::from_utf8_lossy(text_cstr.to_bytes())
+ .to_string();
+ // let mut escaped = String::with_capacity(text_str.capacity());
+ text_str.chars().
+ for_each(|c| rexmpp_xml_print_text(c, ret));
+ },
+ RexmppXML { alt : RexmppXMLAlt::Elem (element),
+ next: _} => {
+ // let mut ret = String::with_capacity(1024);
+ let name_cstr : &CStr =
+ CStr::from_ptr(element.qname.name);
+ let name_str : String =
+ String::from_utf8_lossy(name_cstr.to_bytes())
+ .to_string();
+ if indent > 0 {
+ ret.push('\n');
+ rexmpp_xml_print_indent(indent, ret);
+ }
+ ret.push('<');
+ name_str.char_indices().
+ for_each(|(i, c)| rexmpp_xml_print_name(i, c, ret));
+ if element.qname.namespace != ptr::null_mut() {
+ let namespace_cstr : &CStr =
+ CStr::from_ptr(element.qname.namespace);
+ let namespace_str : String =
+ String::from_utf8_lossy(namespace_cstr.to_bytes())
+ .to_string();
+ ret.push_str(" xmlns=\"");
+ namespace_str.chars().
+ for_each(|c| rexmpp_xml_print_text(c, ret));
+ ret.push('"');
+ }
+ if element.attributes != ptr::null_mut() {
+ let mut attr_ptr : *mut RexmppXMLAttribute =
+ element.attributes;
+ while attr_ptr != ptr::null_mut() {
+ let attr : RexmppXMLAttribute = *attr_ptr;
+ let attr_name_cstr =
+ CStr::from_ptr(attr.qname.name);
+ let attr_name_str =
+ String::from_utf8_lossy(attr_name_cstr.to_bytes())
+ .to_string();
+ // TODO: handle attribute namespaces someday.
+ let attr_value_cstr =
+ CStr::from_ptr(attr.value);
+ let attr_value_str =
+ String::from_utf8_lossy(attr_value_cstr.to_bytes())
+ .to_string();
+ ret.push(' ');
+ attr_name_str.char_indices().
+ for_each(|(i, c)|
+ rexmpp_xml_print_name(i, c, ret));
+ ret.push_str("=\"");
+ attr_value_str.chars().
+ for_each(|c| rexmpp_xml_print_text(c, ret));
+ ret.push('"');
+ attr_ptr = (*attr_ptr).next;
+ }
+ }
+ if element.children == ptr::null_mut() {
+ ret.push_str("/>");
+ } else {
+ ret.push('>');
+ let mut child = rexmpp_xml_children(node_ptr);
+ let mut last_child_is_textual = false;
+ while child != ptr::null_mut() {
+ rexmpp_xml_print(child, ret,
+ if indent > -1 { indent + 1 }
+ else { -1 } );
+ last_child_is_textual =
+ matches!((*child).alt, RexmppXMLAlt::Text(_));
+ child = (*child).next;
+ }
+ if indent > 0 && ! last_child_is_textual {
+ ret.push('\n');
+ rexmpp_xml_print_indent(indent, ret);
+ }
+ ret.push_str("</");
+ name_str.char_indices().
+ for_each(|(i, c)|
+ rexmpp_xml_print_name(i, c, ret));
+ ret.push('>');
+ }
+ }
+ }
+ }
+}
+
+fn rexmpp_xml_serialize_str (node_ptr: *const RexmppXML,
+ pretty: bool)
+ -> String
+{
+ let mut out = String::with_capacity(4096);
+ rexmpp_xml_print(node_ptr, &mut out, if pretty { 0 } else { -1 });
+ return out;
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_serialize (node_ptr: *const RexmppXML,
+ pretty: bool)
+ -> *mut c_char
+{
+ let out = rexmpp_xml_serialize_str(node_ptr, pretty);
+ match CString::new(out) {
+ Ok(cstr) => unsafe { strdup(cstr.as_ptr()) },
+ Err(_) => ptr::null_mut()
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_add_id (node: *mut RexmppXML)
+ -> *mut RexmppXML
+{
+ match CString::new("id") {
+ Err(_) => return ptr::null_mut(),
+ Ok(id_cstr) => {
+ let buf = unsafe { rexmpp_random::rexmpp_random_id() };
+ if buf == ptr::null_mut() {
+ return ptr::null_mut();
+ }
+ rexmpp_xml_add_attr(node, id_cstr.as_ptr(), buf);
+ unsafe { free(buf as *mut c_void) };
+ return node;
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_write_file (path: *const c_char,
+ node: *const RexmppXML)
+ -> c_int
+{
+ let path_cstr : &CStr = unsafe { CStr::from_ptr(path) };
+ let path_str : String =
+ String::from_utf8_lossy(path_cstr.to_bytes())
+ .to_string();
+ match File::create(path_str) {
+ Ok(mut fd) => {
+ fd.write_all(b"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+ fd.write_all(rexmpp_xml_serialize_str(node, false).as_bytes());
+ },
+ Err(_) => { return -1; }
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_siblings_count (mut node: *const RexmppXML) -> c_uint {
+ let mut i : c_uint = 0;
+ while node != ptr::null() {
+ node = unsafe { (*node).next };
+ i = i + 1;
+ }
+ return i;
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_match (node: *const RexmppXML,
+ namespace: *const c_char,
+ name: *const c_char) -> c_int {
+ if node == ptr::null_mut() {
+ return 0;
+ }
+ match unsafe { (*node).alt } {
+ RexmppXMLAlt::Text(_) => return 0,
+ RexmppXMLAlt::Elem(elem) => {
+ if name != ptr::null_mut() {
+ let name_cstr : &CStr = unsafe { CStr::from_ptr(name) };
+ let elem_name_cstr : &CStr =
+ unsafe { CStr::from_ptr(elem.qname.name) };
+ if name_cstr != elem_name_cstr {
+ return 0;
+ }
+ }
+ if namespace != ptr::null_mut() {
+ let namespace_cstr : &CStr = unsafe { CStr::from_ptr(namespace) };
+ if unsafe { elem.qname.namespace } == ptr::null_mut() {
+ match CStr::to_str(namespace_cstr) {
+ Ok(namespace_str) => if namespace_str == "jabber:client" {
+ return 1;
+ },
+ Err(_) => return 0
+ }
+ return 0;
+ }
+ let elem_namespace_cstr : &CStr =
+ unsafe { CStr::from_ptr(elem.qname.namespace) };
+ if namespace_cstr != elem_namespace_cstr {
+ return 0;
+ }
+ }
+ }
+ }
+ return 1;
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_attr_match (attr: *const RexmppXMLAttribute,
+ namespace: *const c_char,
+ name: *const c_char) -> c_int {
+ if attr == ptr::null() {
+ return 0;
+ }
+ if name != ptr::null() {
+ let name_cstr : &CStr = unsafe { CStr::from_ptr(name) };
+ let attr_name_cstr : &CStr = unsafe { CStr::from_ptr((*attr).qname.name) };
+ if name_cstr != attr_name_cstr {
+ return 0;
+ }
+ }
+ if namespace != ptr::null() {
+ let namespace_cstr : &CStr = unsafe { CStr::from_ptr(namespace) };
+ if unsafe { (*attr).qname.namespace } == ptr::null_mut() {
+ match CStr::to_str(namespace_cstr) {
+ Ok(namespace_str) => if namespace_str != "jabber:client" {
+ return 0;
+ },
+ Err(_) => return 0
+ }
+ } else {
+ let attr_namespace_cstr : &CStr =
+ unsafe { CStr::from_ptr((*attr).qname.namespace) };
+ if namespace_cstr != attr_namespace_cstr {
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_is_stanza (node: *const RexmppXML) -> c_int {
+ if rexmpp_xml_match(node,
+ CString::new("jabber:client").expect("CString::new failed").as_ptr(),
+ CString::new("message").expect("CString::new failed").as_ptr()) == 1
+ || rexmpp_xml_match(node,
+ CString::new("jabber:client").expect("CString::new failed").as_ptr(),
+ CString::new("iq").expect("CString::new failed").as_ptr()) == 1
+ || rexmpp_xml_match(node,
+ CString::new("jabber:client").expect("CString::new failed").as_ptr(),
+ CString::new("presence").expect("CString::new failed").as_ptr()) == 1
+ {
+ return 1;
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_error (error_type: *const c_char, condition: *const c_char)
+ -> *mut RexmppXML {
+ let error : *mut RexmppXML =
+ rexmpp_xml_new_elem(CString::new("error")
+ .expect("CString::new failed")
+ .as_ptr(),
+ ptr::null_mut());
+ rexmpp_xml_add_attr(error,
+ CString::new("type")
+ .expect("CString::new failed")
+ .as_ptr(),
+ error_type);
+ let cond =
+ rexmpp_xml_new_elem(condition,
+ CString::new("urn:ietf:params:xml:ns:xmpp-stanzas")
+ .expect("CString::new failed")
+ .as_ptr());
+ rexmpp_xml_add_child(error, cond);
+ return error;
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_find_attr (node: *mut RexmppXML,
+ name: *const c_char,
+ namespace: *const c_char)
+ -> *mut RexmppXMLAttribute {
+ if node == ptr::null_mut() {
+ return ptr::null_mut();
+ }
+ match unsafe { (*node).alt } {
+ RexmppXMLAlt::Elem(elem) => {
+ let mut attr_ptr : *mut RexmppXMLAttribute =
+ unsafe { elem.attributes };
+ while attr_ptr != ptr::null_mut() {
+ if rexmpp_xml_attr_match(attr_ptr, namespace, name) > 0 {
+ return attr_ptr;
+ }
+ unsafe { attr_ptr = (*attr_ptr).next };
+ }
+ return ptr::null_mut();
+ },
+ _ => return ptr::null_mut(),
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_find_attr_val_ns (node: *mut RexmppXML,
+ name: *const c_char,
+ namespace: *const c_char)
+ -> *const c_char {
+ let attr : *mut RexmppXMLAttribute =
+ rexmpp_xml_find_attr(node, name, namespace);
+ if attr != ptr::null_mut() {
+ return unsafe { (*attr).value };
+ }
+ return ptr::null_mut();
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_find_attr_val (node: *mut RexmppXML,
+ name: *const c_char)
+ -> *const c_char {
+ rexmpp_xml_find_attr_val_ns(node, name, ptr::null_mut())
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_find_child (node: *mut RexmppXML,
+ namespace: *const c_char,
+ name: *const c_char)
+ -> *mut RexmppXML {
+ if node == ptr::null_mut() {
+ return ptr::null_mut();
+ }
+ match unsafe { (*node).alt } {
+ RexmppXMLAlt::Elem(elem) => {
+ let mut child: *mut RexmppXML = unsafe { elem.children };
+ while child != ptr::null_mut() {
+ if rexmpp_xml_match(child, namespace, name) > 0 {
+ return child;
+ }
+ unsafe { child = (*child).next };
+ }
+ ptr::null_mut()
+ },
+ _ => ptr::null_mut()
+ }
+}
+
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_eq (n1: *const RexmppXML, n2: *const RexmppXML) -> bool {
+ if n1 == n2 {
+ return true;
+ }
+ if n1 == ptr::null_mut() || n1 == ptr::null_mut() {
+ return false;
+ }
+ unsafe {
+ match (*n1, *n2) {
+ (RexmppXML { alt : RexmppXMLAlt::Text (text1),
+ next: _ },
+ RexmppXML { alt : RexmppXMLAlt::Text (text2),
+ next: _ }
+ ) => strcmp(text1, text2) == 0,
+ (RexmppXML
+ { alt : RexmppXMLAlt::Elem
+ ( RexmppXMLAltElem {
+ qname: RexmppXMLQName {
+ name: name1,
+ namespace: namespace1
+ },
+ attributes: attributes1,
+ children: children1
+ } ),
+ next: _},
+ RexmppXML
+ { alt : RexmppXMLAlt::Elem
+ ( RexmppXMLAltElem {
+ qname: RexmppXMLQName {
+ name: name2,
+ namespace: namespace2
+ },
+ attributes: attributes2,
+ children: children2
+ } ),
+ next: _}
+ ) => {
+ // Compare names
+ if strcmp(name1, name2) != 0
+ { return false; }
+ // Compare namespaces
+ if namespace1 != namespace2 &&
+ (namespace1 == ptr::null_mut() ||
+ namespace2 == ptr::null_mut() ||
+ strcmp(namespace1, namespace2) != 0)
+ { return false; }
+ // Compare attributes
+ let mut attr1 = attributes1;
+ let mut attr2 = attributes2;
+ while ! (attr1 == ptr::null_mut() && attr2 == ptr::null_mut()) {
+ if attr1 == ptr::null_mut() {
+ return false;
+ }
+ if attr2 == ptr::null_mut() {
+ return false;
+ }
+ if strcmp((*attr1).qname.name, (*attr2).qname.name) != 0 {
+ return false;
+ }
+ if strcmp((*attr1).value, (*attr2).value) != 0 {
+ return false;
+ }
+ attr1 = (*attr1).next;
+ attr2 = (*attr2).next;
+ }
+ // Compare children
+ let mut child1 = children1;
+ let mut child2 = children2;
+ while ! (child1 == ptr::null_mut() && child2 == ptr::null_mut())
+ {
+ if child1 == ptr::null_mut() {
+ return false;
+ }
+ if child2 == ptr::null_mut() {
+ return false;
+ }
+ if ! rexmpp_xml_eq(child1, child2) {
+ return false;
+ }
+ child1 = (*child1).next;
+ child2 = (*child2).next;
+ }
+ true
+ }
+ _ => false
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_children (node: *const RexmppXML)
+ -> *mut RexmppXML {
+ if node == ptr::null_mut() {
+ return ptr::null_mut();
+ }
+ match unsafe { (*node).alt } {
+ RexmppXMLAlt::Elem(elem) => elem.children,
+ _ => ptr::null_mut()
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_first_elem_child (node: *mut RexmppXML)
+ -> *mut RexmppXML {
+ let mut child: *mut RexmppXML = rexmpp_xml_children(node);
+ while child != ptr::null_mut() {
+ if matches!(unsafe { (*child).alt }, RexmppXMLAlt::Elem(_)) {
+ return child;
+ }
+ unsafe { child = (*child).next };
+ }
+ return ptr::null_mut();
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_next_elem_sibling (node: *mut RexmppXML)
+ -> *mut RexmppXML {
+ if node == ptr::null_mut() {
+ return ptr::null_mut();
+ }
+ let mut sibling: *mut RexmppXML = unsafe { (*node).next };
+ while sibling != ptr::null_mut() {
+ if matches!(unsafe { (*sibling).alt }, RexmppXMLAlt::Elem(_)) {
+ return sibling;
+ }
+ unsafe { sibling = (*sibling).next };
+ }
+ return ptr::null_mut();
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_text (node: *mut RexmppXML)
+ -> *mut c_char {
+ if node == ptr::null_mut() {
+ return ptr::null_mut();
+ }
+ match unsafe { (*node).alt } {
+ RexmppXMLAlt::Text(text) => text,
+ _ => ptr::null_mut()
+ }
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_text_child (node: *mut RexmppXML)
+ -> *mut c_char {
+ rexmpp_xml_text(rexmpp_xml_children(node))
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_reverse_list (mut node: *mut RexmppXML)
+ -> *mut RexmppXML {
+ let mut next;
+ let mut prev = ptr::null_mut();
+ while node != ptr::null_mut() {
+ unsafe {
+ next = (*node).next;
+ (*node).next = prev;
+ prev = node;
+ node = next;
+ }
+ }
+ return prev;
+}
+
+#[no_mangle]
+pub extern "C"
+fn rexmpp_xml_reverse_children (node: *mut RexmppXML)
+ -> *mut RexmppXML {
+ unsafe {
+ if node == ptr::null_mut() {
+ return node;
+ }
+ match (*node).alt {
+ RexmppXMLAlt::Elem(ref mut elem) => {
+ (*elem).children = rexmpp_xml_reverse_list((*elem).children);
+ let mut cur = node;
+ while cur != ptr::null_mut() {
+ match (*cur).alt {
+ RexmppXMLAlt::Elem(ref mut cur_elem) => {
+ (*cur_elem).children =
+ rexmpp_xml_reverse_children((*cur_elem).children);
+ },
+ _ => ()
+ }
+ cur = (*cur).next;
+ }
+ },
+ _ => ()
+ }
+ }
+ return node;
+}
diff --git a/src/rexmpp_xml_parser.c b/src/rexmpp_xml_parser.c
new file mode 100644
index 0000000..3f485d7
--- /dev/null
+++ b/src/rexmpp_xml_parser.c
@@ -0,0 +1,323 @@
+/**
+ @file rexmpp_xml_parser.c
+ @brief XML parsing for rexmpp
+ @author defanor <defanor@uberspace.net>
+ @date 2023
+ @copyright MIT license.
+*/
+
+#include <string.h>
+
+#include "rexmpp.h"
+#include "rexmpp_xml.h"
+#include "rexmpp_xml_parser.h"
+#include "config.h"
+
+#if defined(USE_LIBXML2)
+
+void rexmpp_xml_sax_characters (rexmpp_xml_parser_ctx_t ctx,
+ const char *ch,
+ int len)
+{
+ ctx->handlers->text(ctx->user_data, ch, len);
+}
+
+void rexmpp_xml_sax_elem_start (rexmpp_xml_parser_ctx_t ctx,
+ const char *localname,
+ const char *prefix,
+ const char *URI,
+ int nb_namespaces,
+ const char **namespaces,
+ int nb_attributes,
+ int nb_defaulted,
+ const char **attributes)
+{
+ (void)prefix;
+ (void)nb_namespaces;
+ (void)namespaces;
+ (void)nb_defaulted;
+ rexmpp_xml_attr_t *attrs = NULL;
+ int i;
+ for (i = nb_attributes - 1; i >= 0; i--) {
+ size_t attr_len = attributes[i * 5 + 4] - attributes[i * 5 + 3];
+ char *attr_val = malloc(attr_len + 1);
+ if (attr_val != NULL) {
+ attr_val[attr_len] = '\0';
+ strncpy(attr_val, attributes[i * 5 + 3], attr_len);
+ rexmpp_xml_attr_t *attr =
+ rexmpp_xml_attr_new(attributes[i * 5], NULL, attr_val);
+ free(attr_val);
+ attr->next = attrs;
+ attrs = attr;
+ }
+ }
+
+ ctx->handlers->elem_start(ctx->user_data, localname, URI, attrs);
+}
+
+void rexmpp_xml_sax_elem_end (rexmpp_xml_parser_ctx_t ctx,
+ const char *localname,
+ const char *prefix,
+ const char *URI)
+{
+ (void)localname;
+ (void)prefix;
+ (void)URI;
+ ctx->handlers->elem_end(ctx->user_data);
+}
+
+xmlSAXHandler rexmpp_xml_parser_sax = {
+ .initialized = XML_SAX2_MAGIC,
+ .characters = (charactersSAXFunc)rexmpp_xml_sax_characters,
+ .startElementNs = (startElementNsSAX2Func)rexmpp_xml_sax_elem_start,
+ .endElementNs = (endElementNsSAX2Func)rexmpp_xml_sax_elem_end,
+};
+
+
+/* rexmpp_xml_t *rexmpp_xml_from_libxml2 (xmlNodePtr from) { */
+/* if (from == NULL) { */
+/* return NULL; */
+/* } */
+
+/* rexmpp_xml_t *to = NULL; */
+/* if (from->type == XML_ELEMENT_NODE) { */
+/* to = malloc(sizeof(rexmpp_xml_t)); */
+
+/* /\* Type *\/ */
+/* to->type = REXMPP_XML_ELEMENT; */
+
+/* /\* Name and namespace *\/ */
+/* to->alt.elem.qname.name = strdup(from->name); */
+/* if (from->nsDef != NULL && from->nsDef->href != NULL) { */
+/* to->alt.elem.qname.namespace = strdup(from->nsDef->href); */
+/* } else { */
+/* to->alt.elem.qname.namespace = NULL; */
+/* } */
+
+/* /\* Attributes *\/ */
+/* to->alt.elem.attributes = NULL; */
+/* struct _xmlAttr *from_attr; */
+/* rexmpp_xml_attr_t **to_next_attr = &(to->alt.elem.attributes); */
+/* for (from_attr = from->properties; */
+/* from_attr != NULL; */
+/* from_attr = from_attr->next) */
+/* { */
+/* rexmpp_xml_attr_t *to_attr = */
+/* malloc(sizeof(rexmpp_xml_attr_t)); */
+/* to_attr->qname.name = strdup(from_attr->name); */
+/* to_attr->qname.namespace = NULL; */
+/* if (from_attr->ns != NULL && from_attr->ns->href != NULL) { */
+/* to_attr->qname.namespace = strdup(from_attr->ns->href); */
+/* to_attr->value = */
+/* xmlGetNsProp(from, to_attr->qname.name, to_attr->qname.namespace); */
+/* } else { */
+/* to_attr->value = xmlGetProp(from, to_attr->qname.name); */
+/* } */
+/* to_attr->next = NULL; */
+
+/* *to_next_attr = to_attr; */
+/* to_next_attr = &(to_attr->next); */
+/* } */
+
+/* /\* Children *\/ */
+/* to->alt.elem.children = NULL; */
+/* xmlNodePtr from_child; */
+/* rexmpp_xml_t **to_next_child = &(to->alt.elem.children); */
+/* for (from_child = from->children; */
+/* from_child != NULL; */
+/* from_child = from_child->next) */
+/* { */
+/* rexmpp_xml_t *next_child = rexmpp_xml_from_libxml2(from_child); */
+/* if (next_child != NULL) { */
+/* *to_next_child = next_child; */
+/* to_next_child = &(next_child->next); */
+/* } */
+/* } */
+
+/* /\* Next *\/ */
+/* to->next = NULL; */
+
+/* } else if (from->type == XML_TEXT_NODE) { */
+/* to = malloc(sizeof(rexmpp_xml_t)); */
+/* to->type = REXMPP_XML_TEXT; */
+/* to->alt.text = xmlNodeGetContent(from); */
+/* to->next = NULL; */
+/* } */
+/* return to; */
+/* } */
+
+/* rexmpp_xml_t *rexmpp_xml_from_libxml2_list (xmlNodePtr from) { */
+/* if (from == NULL) { */
+/* return NULL; */
+/* } */
+/* rexmpp_xml_t *to = rexmpp_xml_from_libxml2(from); */
+/* if (from->next != NULL) { */
+/* to->next = rexmpp_xml_from_libxml2_list(from->next); */
+/* } */
+/* return to; */
+/* } */
+
+/* xmlNodePtr rexmpp_xml_to_libxml2 (rexmpp_xml_t *from) { */
+/* if (from == NULL) { */
+/* return NULL; */
+/* } */
+
+/* if (from->type == REXMPP_XML_TEXT) { */
+/* xmlNodePtr to = xmlNewText(from->alt.text); */
+/* to->next = rexmpp_xml_to_libxml2(from->next); */
+/* return to; */
+/* } */
+
+/* /\* Name and namespace *\/ */
+/* xmlNodePtr to = xmlNewNode(NULL, from->alt.elem.qname.name); */
+/* if (from->alt.elem.qname.namespace != NULL) { */
+/* xmlNewNs(to, from->alt.elem.qname.namespace, NULL); */
+/* } */
+
+/* /\* Attributes *\/ */
+/* rexmpp_xml_attr_t *attr = from->alt.elem.attributes; */
+/* while (attr != NULL) { */
+/* /\* TODO: Would be nice to take namespaces into account, though */
+/* they are currently not used for attributes. *\/ */
+/* xmlNewProp(to, attr->qname.name, attr->value); */
+/* attr = attr->next; */
+/* } */
+
+/* /\* Children *\/ */
+/* rexmpp_xml_t *child = from->alt.elem.children; */
+/* while (child != NULL) { */
+/* xmlAddChild(to, rexmpp_xml_to_libxml2(child)); */
+/* child = child->next; */
+/* } */
+/* return to; */
+/* } */
+
+/* xmlNodePtr rexmpp_xml_to_libxml2_list (rexmpp_xml_t *from) { */
+/* xmlNodePtr to = rexmpp_xml_to_libxml2(from); */
+/* if (from->next != NULL) { */
+/* xmlAddNextSibling(to, rexmpp_xml_to_libxml2_list(from->next)); */
+/* } */
+/* return to; */
+/* } */
+
+#elif defined(USE_EXPAT)
+
+void XMLCALL
+rexmpp_xml_sax_elem_start (rexmpp_xml_parser_ctx_t ctx,
+ const char *el,
+ const char **attributes)
+{
+ char *buf = strdup(el);
+ char *name = NULL, *namespace = buf;
+ size_t i;
+ for (i = 0; i < strlen(namespace); i++) {
+ if (namespace[i] == '\xff') {
+ name = namespace + i + 1;
+ namespace[i] = '\0';
+ }
+ }
+ if (name == NULL) {
+ name = namespace;
+ namespace = NULL;
+ }
+ rexmpp_xml_attr_t *attrs = NULL;
+ for (i = 0; attributes[i] != NULL; i += 2) {
+ rexmpp_xml_attr_t *attr =
+ rexmpp_xml_attr_new(attributes[i], NULL, attributes[i + 1]);
+ attr->next = attrs;
+ attrs = attr;
+ }
+
+ ctx->handlers->elem_start(ctx->user_data, name, namespace, attrs);
+ free(buf);
+}
+
+void XMLCALL
+rexmpp_xml_sax_elem_end(rexmpp_xml_parser_ctx_t ctx,
+ const XML_Char *name)
+{
+ (void)name;
+ ctx->handlers->elem_end(ctx->user_data);
+}
+
+void XMLCALL
+rexmpp_xml_sax_characters (rexmpp_xml_parser_ctx_t ctx,
+ const XML_Char *ch,
+ int len)
+{
+ ctx->handlers->text(ctx->user_data, ch, len);
+}
+
+#endif
+
+
+
+rexmpp_xml_parser_ctx_t
+rexmpp_xml_parser_new (rexmpp_xml_parser_handlers_t handlers,
+ void *data)
+{
+ rexmpp_xml_parser_ctx_t ctx = malloc(sizeof(struct rexmpp_xml_parser_ctx));
+ if (ctx == NULL) {
+ return NULL;
+ }
+#if defined(USE_LIBXML2)
+ xmlParserCtxtPtr p =
+ xmlCreatePushParserCtxt(&rexmpp_xml_parser_sax, ctx, "", 0, NULL);
+#elif defined(USE_EXPAT)
+ XML_Parser p = XML_ParserCreateNS("utf-8", '\xff');
+ XML_SetUserData(p, ctx);
+ XML_SetStartElementHandler(p, (XML_StartElementHandler)
+ rexmpp_xml_sax_elem_start);
+ XML_SetEndElementHandler(p, (XML_EndElementHandler)
+ rexmpp_xml_sax_elem_end);
+ XML_SetCharacterDataHandler(p, (XML_CharacterDataHandler)
+ rexmpp_xml_sax_characters);
+#endif
+ if (p == NULL) {
+ free(ctx);
+ return NULL;
+ }
+
+ ctx->xml_parser = p;
+ ctx->handlers = handlers;
+ ctx->user_data = data;
+ return ctx;
+}
+
+void rexmpp_xml_parser_free (rexmpp_xml_parser_ctx_t ctx) {
+#if defined(USE_LIBXML2)
+ xmlFreeParserCtxt(ctx->xml_parser);
+#elif defined(USE_EXPAT)
+ XML_ParserFree(ctx->xml_parser);
+#endif
+ free(ctx);
+}
+
+rexmpp_xml_parser_ctx_t rexmpp_xml_parser_reset (rexmpp_xml_parser_ctx_t ctx) {
+#if defined(USE_LIBXML2)
+ xmlCtxtResetPush(ctx->xml_parser, "", 0, "", "utf-8");
+#elif defined(USE_EXPAT)
+ XML_ParserReset(ctx->xml_parser, "utf-8");
+ XML_SetUserData(ctx->xml_parser, ctx);
+ XML_SetStartElementHandler(ctx->xml_parser, (XML_StartElementHandler)
+ rexmpp_xml_sax_elem_start);
+ XML_SetEndElementHandler(ctx->xml_parser, (XML_EndElementHandler)
+ rexmpp_xml_sax_elem_end);
+ XML_SetCharacterDataHandler(ctx->xml_parser, (XML_CharacterDataHandler)
+ rexmpp_xml_sax_characters);
+#endif
+ return ctx;
+}
+
+void
+rexmpp_xml_parser_feed (rexmpp_xml_parser_ctx_t ctx,
+ const char *chunk,
+ size_t len,
+ int final)
+{
+#if defined(USE_LIBXML2)
+ xmlParseChunk(ctx->xml_parser, chunk, len, final);
+#elif defined(USE_EXPAT)
+ XML_Parse(ctx->xml_parser, chunk, len, final);
+#endif
+}
diff --git a/src/rexmpp_xml_parser.h b/src/rexmpp_xml_parser.h
new file mode 100644
index 0000000..66627ab
--- /dev/null
+++ b/src/rexmpp_xml_parser.h
@@ -0,0 +1,106 @@
+/**
+ @file rexmpp_xml_parser.h
+ @brief XML parsing for rexmpp
+ @author defanor <defanor@uberspace.net>
+ @date 2023
+ @copyright MIT license.
+*/
+
+#ifndef REXMPP_XML_PARSER_H
+#define REXMPP_XML_PARSER_H
+
+
+#if defined(USE_LIBXML2)
+ #include <libxml/tree.h>
+#elif defined(USE_EXPAT)
+ #include <expat.h>
+#endif
+
+#include "config.h"
+
+typedef void (*rexmpp_xml_parser_element_start) (void *data,
+ const char *name,
+ const char *namespace,
+ rexmpp_xml_attr_t *attributes);
+typedef void (*rexmpp_xml_parser_element_end) (void *data);
+typedef void (*rexmpp_xml_parser_characters) (void *data,
+ const char *ch,
+ size_t len);
+
+struct rexmpp_xml_parser_handlers {
+ rexmpp_xml_parser_element_start elem_start;
+ rexmpp_xml_parser_element_end elem_end;
+ rexmpp_xml_parser_characters text;
+};
+
+
+typedef struct rexmpp_xml_parser_ctx* rexmpp_xml_parser_ctx_t;
+typedef struct rexmpp_xml_parser_handlers* rexmpp_xml_parser_handlers_t;
+
+struct rexmpp_xml_parser_ctx {
+#if defined(USE_LIBXML2)
+ xmlParserCtxtPtr xml_parser;
+#elif defined(USE_EXPAT)
+ XML_Parser xml_parser;
+#else
+ void *xml_parser;
+#endif
+ rexmpp_xml_parser_handlers_t handlers;
+ void *user_data;
+};
+
+/**
+ @brief Allocates a new XML parser context
+ @param[in] handlers SAX-like parser event handlers
+ @param[in] data User-provided data to pass to the handlers
+ @returns A parser context pointer, or NULL on failure.
+*/
+rexmpp_xml_parser_ctx_t
+rexmpp_xml_parser_new (rexmpp_xml_parser_handlers_t handlers,
+ void *data);
+
+/**
+ @brief Frees an XML parser context
+ @param[in] ctx An XML parser context
+*/
+void rexmpp_xml_parser_free (rexmpp_xml_parser_ctx_t ctx);
+
+/**
+ @brief Feeds data to parse into an XML parser
+ @param[in] ctx An XML parser context
+ @param[in] chunk A chunk of data to parse
+ @param[in] len Length of the data chunk
+*/
+void
+rexmpp_xml_parser_feed (rexmpp_xml_parser_ctx_t ctx,
+ const char *chunk,
+ size_t len,
+ int final);
+
+/**
+ @brief Resets a parser context
+ @param[in] ctx An XML parser context
+ @returns A new pointer, since it may change during a reset
+*/
+rexmpp_xml_parser_ctx_t rexmpp_xml_parser_reset (rexmpp_xml_parser_ctx_t ctx);
+
+
+/* #if defined(USE_LIBXML2) */
+/* /\** */
+/* @brief Creates a single ::rexmpp_xml_t XML node out of libxml2's */
+/* xmlNode, without siblings. */
+/* *\/ */
+/* rexmpp_xml_t *rexmpp_xml_from_libxml2 (xmlNodePtr from); */
+
+/* /\** */
+/* @brief Creates a ::rexmpp_xml_t XML node out of libxml2's xmlNode, */
+/* with siblings. */
+/* *\/ */
+/* rexmpp_xml_t *rexmpp_xml_from_libxml2_list (xmlNodePtr from); */
+
+/* xmlNodePtr rexmpp_xml_to_libxml2 (rexmpp_xml_t *from); */
+
+/* xmlNodePtr rexmpp_xml_to_libxml2_list (rexmpp_xml_t *from); */
+/* #endif */
+
+#endif
diff --git a/src/rexmpp_xml_parser.rs b/src/rexmpp_xml_parser.rs
new file mode 100644
index 0000000..037c2f2
--- /dev/null
+++ b/src/rexmpp_xml_parser.rs
@@ -0,0 +1,145 @@
+extern crate libc;
+extern crate rxml;
+use libc::{free, strndup};
+use std::ptr;
+use std::os::raw::{c_char, c_void};
+use std::ffi::{CStr, CString};
+use std::slice;
+use rxml::{FeedParser, Error, ResolvedEvent, XmlVersion, EventRead, CData};
+use std::io;
+use super::{rexmpp_xml};
+
+type RexmppXMLParserElementStart = unsafe extern "C"
+fn (data: *mut c_void,
+ name: *const c_char,
+ namespace: *const c_char,
+ attributes: *mut rexmpp_xml::RexmppXMLAttribute) -> ();
+
+type RexmppXMLParserElementEnd = unsafe extern "C"
+fn (data: *mut c_void) -> ();
+
+type RexmppXMLParserCharacters = unsafe extern "C"
+fn (data: *mut c_void,
+ ch: *const c_char,
+ len: usize) -> ();
+
+#[repr(C)]
+struct RexmppXMLParserHandlers {
+ elem_start: RexmppXMLParserElementStart,
+ elem_end: RexmppXMLParserElementEnd,
+ text: RexmppXMLParserCharacters
+}
+
+#[repr(C)]
+struct RexmppXMLParserCtx {
+ xml_parser: *mut FeedParser,
+ handlers: *mut RexmppXMLParserHandlers,
+ user_data: *mut c_void
+}
+
+#[no_mangle]
+extern "C"
+fn rexmpp_xml_parser_new (handlers: *mut RexmppXMLParserHandlers,
+ data: *mut c_void)
+ -> *mut RexmppXMLParserCtx
+{
+ let mut fp = FeedParser::default();
+ let ctx = RexmppXMLParserCtx {
+ xml_parser: Box::into_raw(Box::new(fp)),
+ handlers: handlers,
+ user_data: data
+ };
+ Box::into_raw(Box::new(ctx))
+}
+
+#[no_mangle]
+extern "C"
+fn rexmpp_xml_parser_free (ctx: *mut RexmppXMLParserCtx) {
+ unsafe { free(ctx as *mut c_void) };
+}
+
+#[no_mangle]
+extern "C"
+fn rexmpp_xml_parser_feed (ctx: *mut RexmppXMLParserCtx,
+ chunk: *const c_char,
+ len: usize,
+ is_final: bool)
+{
+ unsafe {
+ // todo: maybe duplicate the string, since apparently a
+ // mutable one is expected by the parser.
+ let mut buf : &[u8] = slice::from_raw_parts(chunk as *mut u8, len);
+ let user_data_ptr = (*ctx).user_data;
+ let handlers = (*ctx).handlers;
+ (*((*ctx).xml_parser)).parse_all(&mut buf, is_final, |ev| {
+ match ev {
+ ResolvedEvent::StartElement(_, (namespace, name), attrs) =>
+ {
+ let name_str = name.to_string();
+ let ns_opt_cstr : Option<CString> = match namespace {
+ None => None,
+ Some(ns_arc_name) => {
+ match CString::new(ns_arc_name.to_string()) {
+ Ok(cstr) => Some(cstr),
+ Err(_) => None
+ }
+ }
+ };
+ match CString::new(name_str) {
+ Ok(name_cstr) => {
+ let name_cstr_ptr = name_cstr.as_ptr();
+ let namespace_cstr_ptr =
+ match ns_opt_cstr {
+ None => ptr::null_mut(),
+ // "ref" is important to use here,
+ // otherwise the pointer will be
+ // wrong.
+ Some(ref ns_cstr) => ns_cstr.as_ptr()
+ };
+ let mut attributes = ptr::null_mut();
+ for ((_, attr_name), attr_val) in attrs.iter() {
+ match (CString::new(attr_name.to_string()),
+ CString::new(attr_val.to_string())) {
+ (Ok(attr_name_cstr), Ok(attr_val_cstr)) => {
+ let attr =
+ rexmpp_xml::rexmpp_xml_attr_new
+ (attr_name_cstr.as_ptr(),
+ ptr::null_mut(),
+ attr_val_cstr.as_ptr());
+ (*attr).next = attributes;
+ attributes = attr;
+ },
+ _ => ()
+ }
+ }
+ ((*handlers).elem_start)
+ (user_data_ptr,
+ name_cstr_ptr,
+ namespace_cstr_ptr,
+ attributes);
+ },
+ Err(_) => ()
+ }
+ },
+ ResolvedEvent::EndElement(_) =>
+ ((*handlers).elem_end)(user_data_ptr),
+ ResolvedEvent::Text(_, cd) =>
+ ((*handlers).text)(
+ user_data_ptr,
+ cd.as_ptr() as *const i8,
+ cd.len()
+ ),
+ _ => ()
+ }
+ });
+ }
+}
+
+#[no_mangle]
+extern "C"
+fn rexmpp_xml_parser_reset (ctx_raw: *mut RexmppXMLParserCtx)
+ -> *mut RexmppXMLParserCtx
+{
+ let ctx = unsafe { Box::from_raw(ctx_raw) };
+ rexmpp_xml_parser_new((*ctx).handlers, (*ctx).user_data)
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..93c0b82
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,24 @@
+AM_CFLAGS = -Werror -Wall -Wextra -pedantic -std=gnu99
+
+COMMON_FLAGS = $(AM_CFLAGS) $(LIBXML_CFLAGS) $(EXPAT_CFLAGS) \
+ $(GNUTLS_CFLAGS) $(LIBDANE_CFLAGS) $(OPENSSL_CFLAGS) \
+ $(GSASL_CFLAGS) $(UNBOUND_CFLAGS) $(CARES_CFLAGS) $(GPGME_CFLAGS) \
+ $(ICU_I18N_CFLAGS) $(LIBGCRYPT_CFLAGS) $(CURL_CFLAGS) \
+ $(NICE_CFLAGS) $(GLIB_CFLAGS) $(SRTP_CFLAGS) \
+ $(PORTAUDIO_CFLAGS) $(OPUS_CFLAGS) $(NETTLE_CFLAGS)
+COMMON_LDADD = -L$(top_builddir)/src -lrexmpp
+
+send_to_self_CFLAGS = $(COMMON_FLAGS)
+xml_parse_and_print_CFLAGS = $(COMMON_FLAGS)
+xml_print_and_parse_CFLAGS = $(COMMON_FLAGS)
+base64_CFLAGS = $(COMMON_FLAGS)
+
+send_to_self_LDADD = $(COMMON_LDADD)
+xml_parse_and_print_LDADD = $(COMMON_LDADD)
+xml_print_and_parse_LDADD = $(COMMON_LDADD)
+base64_LDADD = $(COMMON_LDADD)
+
+check_PROGRAMS = send_to_self \
+ xml_parse_and_print xml_print_and_parse base64
+TESTS = send_to_self \
+ xml_parse_and_print xml_print_and_parse base64
diff --git a/tests/README b/tests/README
new file mode 100644
index 0000000..7e30213
--- /dev/null
+++ b/tests/README
@@ -0,0 +1,8 @@
+rexmpp tests
+
+These tests make use of an external XMPP server. Environment variables
+used by the tests:
+
+- JID and PASS: credentials.
+
+- TLS_POLICY: "require", "prefer", or "avoid".
diff --git a/tests/base64.c b/tests/base64.c
new file mode 100644
index 0000000..b69ba0e
--- /dev/null
+++ b/tests/base64.c
@@ -0,0 +1,18 @@
+#include <string.h>
+#include "rexmpp_base64.h"
+
+int main () {
+ char *original_plain = "test string";
+ char *original_base64 = "dGVzdCBzdHJpbmc=";
+ char *encoded, *decoded;
+ size_t encoded_len, decoded_len;
+ if (rexmpp_base64_to(original_plain, strlen(original_plain),
+ &encoded, &encoded_len)) {
+ return -1;
+ }
+ if (rexmpp_base64_from(original_base64, strlen(original_base64),
+ &decoded, &decoded_len)) {
+ return -1;
+ }
+ return strcmp(original_plain, decoded) || strcmp(original_base64, encoded);
+}
diff --git a/tests/send_to_self.c b/tests/send_to_self.c
new file mode 100644
index 0000000..2fcb80f
--- /dev/null
+++ b/tests/send_to_self.c
@@ -0,0 +1,192 @@
+/**
+ @file send_to_self.c
+ @brief A basic message sending test
+ @author defanor <defanor@uberspace.net>
+ @date 2022
+ @copyright MIT license.
+
+Connects to a server, sends a message to itself, receives it, checks
+that it's the expected message.
+
+*/
+
+#define TIMEOUT 30
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <syslog.h>
+#include <rexmpp.h>
+#include <rexmpp_sasl.h>
+
+enum test_stage {
+ TEST_CONNECTING,
+ TEST_MESSAGE_SENT,
+ TEST_MESSAGE_RECEIVED,
+ TEST_DONE,
+ TEST_TIMEOUT
+};
+
+enum test_stage stage = TEST_CONNECTING;
+char *jid, *pass, msg_text[256];
+
+void my_logger (rexmpp_t *s, int priority, const char *fmt, va_list args) {
+ (void)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;
+ }
+ fprintf(stdout, "[%s] ", priority_str);
+ vfprintf(stdout, fmt, args);
+ fprintf(stdout, "\n");
+}
+
+int my_sasl_property_cb (rexmpp_t *s, rexmpp_sasl_property prop) {
+ if (prop == REXMPP_SASL_PROP_PASSWORD) {
+ rexmpp_sasl_property_set (s, REXMPP_SASL_PROP_PASSWORD, pass);
+ return 0;
+ }
+ if (prop == REXMPP_SASL_PROP_AUTHID) {
+ rexmpp_sasl_property_set (s, REXMPP_SASL_PROP_AUTHID, s->initial_jid.local);
+ return 0;
+ }
+ printf("unhandled SASL property: %d\n", prop);
+ return -1;
+}
+
+int my_xml_in_cb (rexmpp_t *s, rexmpp_xml_t *node) {
+ (void)s;
+ char *xml_buf = rexmpp_xml_serialize(node, 0);
+ printf("recv: %s\n", xml_buf);
+ free(xml_buf);
+ if (stage == TEST_MESSAGE_SENT && rexmpp_xml_match(node, "jabber:client", "message")) {
+ rexmpp_xml_t *body = rexmpp_xml_find_child(node, "jabber:client", "body");
+ if (body != NULL) {
+ char *txt = rexmpp_xml_text_child(body);
+ if (txt != NULL) {
+ if (strcmp(txt, msg_text) == 0) {
+ stage = TEST_MESSAGE_RECEIVED;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+int my_xml_out_cb (rexmpp_t *s, rexmpp_xml_t *node) {
+ (void)s;
+ char *xml_buf = rexmpp_xml_serialize(node, 0);
+ printf("send: %s\n", xml_buf);
+ free(xml_buf);
+ return 0;
+}
+
+int main () {
+ jid = getenv("JID");
+ pass = getenv("PASS");
+ char *tls_policy = getenv("TLS_POLICY");
+
+ time_t t = time(NULL);
+ struct tm utc_time;
+ gmtime_r(&t, &utc_time);
+ strftime(msg_text, 256, "The current time is %FT%TZ", &utc_time);
+
+ rexmpp_t s;
+ rexmpp_err_t err;
+ err = rexmpp_init(&s, jid, my_logger);
+ if (err != REXMPP_SUCCESS) {
+ puts("Failed to initialise rexmpp.");
+ return -1;
+ }
+ if (tls_policy != NULL) {
+ if (strcasecmp(tls_policy, "require") == 0) {
+ s.tls_policy = REXMPP_TLS_REQUIRE;
+ } else if (strcasecmp(tls_policy, "prefer") == 0) {
+ s.tls_policy = REXMPP_TLS_PREFER;
+ } else if (strcasecmp(tls_policy, "avoid") == 0) {
+ s.tls_policy = REXMPP_TLS_AVOID;
+ }
+ }
+
+ s.sasl_property_cb = my_sasl_property_cb;
+ s.xml_in_cb = my_xml_in_cb;
+ s.xml_out_cb = my_xml_out_cb;
+
+ 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 {
+ err = rexmpp_run(&s, &read_fds, &write_fds);
+ if (err == REXMPP_SUCCESS) {
+ puts("done");
+ break;
+ }
+ if (err != REXMPP_E_AGAIN) {
+ printf("error: %s\n", rexmpp_strerror(err));
+ break;
+ }
+
+ if (stage == TEST_CONNECTING && s.stream_state == REXMPP_STREAM_READY) {
+ rexmpp_xml_t *msg =
+ rexmpp_xml_add_id(rexmpp_xml_new_elem("message", "jabber:client"));
+ rexmpp_xml_add_attr(msg, "to", jid);
+ rexmpp_xml_add_attr(msg, "type", "chat");
+ rexmpp_xml_t *body = rexmpp_xml_new_elem("body", NULL);
+ rexmpp_xml_add_text(body, msg_text);
+ rexmpp_xml_add_child(msg, body);
+ rexmpp_send(&s, msg);
+ stage = TEST_MESSAGE_SENT;
+ } else if (stage == TEST_MESSAGE_RECEIVED) {
+ rexmpp_stop(&s);
+ stage = TEST_DONE;
+ }
+
+ time_t now = time(NULL);
+ if (stage != TEST_DONE) {
+ if (stage != TEST_TIMEOUT && difftime(now, t) > TIMEOUT) {
+ puts("Timeout");
+ rexmpp_stop(&s);
+ stage = TEST_TIMEOUT;
+ } else if (stage == TEST_TIMEOUT && difftime(now, t) > TIMEOUT + 10) {
+ puts("Failed to close the stream properly, quitting");
+ rexmpp_done(&s);
+ return -1;
+ }
+ }
+
+ FD_ZERO(&read_fds);
+ FD_ZERO(&write_fds);
+ nfds = rexmpp_fds(&s, &read_fds, &write_fds);
+ tv.tv_sec = TIMEOUT;
+ tv.tv_nsec = 0;
+ mtv = rexmpp_timeout(&s, &tv, &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;
+ }
+
+ n = select(nfds, &read_fds, &write_fds, NULL, mtv_ms);
+ if (n == -1) {
+ printf("select error: %s\n", strerror(errno));
+ break;
+ }
+ printf("stage = %u\n", stage);
+ } while (1);
+
+ rexmpp_done(&s);
+ return (stage != TEST_DONE);
+}
diff --git a/tests/xml_parse_and_print.c b/tests/xml_parse_and_print.c
new file mode 100644
index 0000000..c76b06f
--- /dev/null
+++ b/tests/xml_parse_and_print.c
@@ -0,0 +1,29 @@
+#include <string.h>
+#include <stdlib.h>
+#include "rexmpp_xml.h"
+
+int main () {
+ int ret = 0;
+
+ char *str = "<foo bar=\"baz\">"
+ "<qux xmlns=\"urn:test\">a b c d</qux>"
+ "<quux e=\"f\" g=\"h\"/>"
+ "</foo>";
+ rexmpp_xml_t *xml = rexmpp_xml_parse (str, strlen(str));
+
+ printf("Input:\n%s\n\n", str);
+ if (xml == NULL) {
+ ret = -1;
+ } else {
+ char *str_new = rexmpp_xml_serialize (xml, 0);
+ if (str_new == NULL) {
+ ret = -2;
+ } else {
+ printf("Output:\n%s\n", str_new);
+ ret = strcmp(str, str_new);
+ free(str_new);
+ }
+ rexmpp_xml_free(xml);
+ }
+ return ret;
+}
diff --git a/tests/xml_print_and_parse.c b/tests/xml_print_and_parse.c
new file mode 100644
index 0000000..9bd7a3f
--- /dev/null
+++ b/tests/xml_print_and_parse.c
@@ -0,0 +1,64 @@
+#include <string.h>
+#include <stdlib.h>
+#include "rexmpp_xml.h"
+
+int main () {
+ int ret = 0;
+
+ rexmpp_xml_attr_t
+ foo_attributes = { .qname = {"bar", NULL},
+ .value = "baz",
+ .next = NULL },
+ quux_attributes_g = { .qname = {"g", NULL},
+ .value = "h",
+ .next = NULL },
+ quux_attributes =
+ { .qname = {"e", NULL},
+ .value = "f",
+ .next = &quux_attributes_g };
+ rexmpp_xml_t
+ quux = { .type = REXMPP_XML_ELEMENT,
+ .alt.elem =
+ { .qname = {"quux", NULL},
+ .attributes = &quux_attributes,
+ .children = NULL
+ },
+ .next = NULL
+ },
+ qux_text = { .type = REXMPP_XML_TEXT,
+ .alt.text = "a b c d",
+ .next = NULL },
+ qux = { .type = REXMPP_XML_ELEMENT,
+ .alt.elem =
+ { .qname = {"qux", "urn:dummy"},
+ .attributes = NULL,
+ .children = &qux_text
+ },
+ .next = &quux
+ },
+ xml =
+ { .type = REXMPP_XML_ELEMENT,
+ .alt.elem =
+ { .qname = {"foo", NULL},
+ .attributes = &foo_attributes,
+ .children = &qux
+ },
+ .next = NULL
+ };
+
+ char *str_new = rexmpp_xml_serialize (&xml, 0);
+ if (str_new == NULL) {
+ ret = -1;
+ } else {
+ rexmpp_xml_t *xml_new = rexmpp_xml_parse (str_new, strlen(str_new));
+ if (xml_new == NULL) {
+ ret = -2;
+ } else {
+ /* Compare the XML structures. */
+ ret = (rexmpp_xml_eq(&xml, xml_new) == 0);
+ rexmpp_xml_free(xml_new);
+ }
+ free(str_new);
+ }
+ return ret;
+}