summaryrefslogtreecommitdiff
path: root/examples/basic.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/basic.c')
-rw-r--r--examples/basic.c232
1 files changed, 155 insertions, 77 deletions
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;
}