diff options
Diffstat (limited to 'examples/basic.c')
-rw-r--r-- | examples/basic.c | 232 |
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; } |