diff options
author | James Booth <boothj5@gmail.com> | 2013-02-02 19:47:41 +0000 |
---|---|---|
committer | James Booth <boothj5@gmail.com> | 2013-02-02 19:47:41 +0000 |
commit | 1d3739bb79d0e56af83bd1a5f3a3606740cc3ade (patch) | |
tree | d997b37f1110d17c7898d134a3e962b3c52a6697 /src/xmpp | |
parent | 029a0161035778c3e2803f44cfda4b4cb801ceb5 (diff) | |
download | profani-tty-1d3739bb79d0e56af83bd1a5f3a3606740cc3ade.tar.gz |
Added xmpp subdir to source
Diffstat (limited to 'src/xmpp')
-rw-r--r-- | src/xmpp/capabilities.c | 245 | ||||
-rw-r--r-- | src/xmpp/connection.c | 513 | ||||
-rw-r--r-- | src/xmpp/iq.c | 337 | ||||
-rw-r--r-- | src/xmpp/message.c | 304 | ||||
-rw-r--r-- | src/xmpp/presence.c | 487 | ||||
-rw-r--r-- | src/xmpp/stanza.c | 608 | ||||
-rw-r--r-- | src/xmpp/xmpp.h | 235 |
7 files changed, 2729 insertions, 0 deletions
diff --git a/src/xmpp/capabilities.c b/src/xmpp/capabilities.c new file mode 100644 index 00000000..7b3e06e5 --- /dev/null +++ b/src/xmpp/capabilities.c @@ -0,0 +1,245 @@ +/* + * capabilities.c + * + * Copyright (C) 2012, 2013 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity 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. + * + * Profanity 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 Profanity. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <string.h> + +#include <glib.h> +#include <openssl/evp.h> +#include <strophe.h> + +#include "config.h" +#include "common.h" +#include "xmpp.h" + +static GHashTable *capabilities; + +static void _caps_destroy(Capabilities *caps); + +void +caps_init(void) +{ + capabilities = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify)_caps_destroy); +} + +void +caps_add(const char * const caps_str, const char * const client) +{ + Capabilities *new_caps = malloc(sizeof(struct capabilities_t)); + + if (client != NULL) { + new_caps->client = strdup(client); + } else { + new_caps->client = NULL; + } + + g_hash_table_insert(capabilities, strdup(caps_str), new_caps); +} + +gboolean +caps_contains(const char * const caps_str) +{ + return (g_hash_table_lookup(capabilities, caps_str) != NULL); +} + +Capabilities * +caps_get(const char * const caps_str) +{ + return g_hash_table_lookup(capabilities, caps_str); +} + +char * +caps_create_sha1_str(xmpp_stanza_t * const query) +{ + char *category = NULL; + char *type = NULL; + char *lang = NULL; + char *name = NULL; + char *feature_str = NULL; + GSList *identities = NULL; + GSList *features = NULL; + GSList *form_names = NULL; + DataForm *form = NULL; + FormField *field = NULL; + GHashTable *forms = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)stanza_destroy_form); + + GString *s = g_string_new(""); + + xmpp_stanza_t *child = xmpp_stanza_get_children(query); + while (child != NULL) { + if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_IDENTITY) == 0) { + category = xmpp_stanza_get_attribute(child, "category"); + type = xmpp_stanza_get_attribute(child, "type"); + lang = xmpp_stanza_get_attribute(child, "xml:lang"); + name = xmpp_stanza_get_attribute(child, "name"); + + GString *identity_str = g_string_new(g_strdup(category)); + g_string_append(identity_str, "/"); + if (type != NULL) { + g_string_append(identity_str, g_strdup(type)); + } + g_string_append(identity_str, "/"); + if (lang != NULL) { + g_string_append(identity_str, g_strdup(lang)); + } + g_string_append(identity_str, "/"); + if (name != NULL) { + g_string_append(identity_str, g_strdup(name)); + } + g_string_append(identity_str, "<"); + identities = g_slist_insert_sorted(identities, g_strdup(identity_str->str), (GCompareFunc)octet_compare); + g_string_free(identity_str, TRUE); + } else if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_FEATURE) == 0) { + feature_str = xmpp_stanza_get_attribute(child, "var"); + features = g_slist_insert_sorted(features, g_strdup(feature_str), (GCompareFunc)octet_compare); + } else if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_X) == 0) { + if (strcmp(xmpp_stanza_get_ns(child), STANZA_NS_DATA) == 0) { + form = stanza_create_form(child); + form_names = g_slist_insert_sorted(form_names, strdup(form->form_type), (GCompareFunc)octet_compare); + g_hash_table_insert(forms, strdup(form->form_type), form); + } + } + child = xmpp_stanza_get_next(child); + } + + GSList *curr = identities; + while (curr != NULL) { + g_string_append(s, strdup(curr->data)); + curr = g_slist_next(curr); + } + + curr = features; + while (curr != NULL) { + g_string_append(s, strdup(curr->data)); + g_string_append(s, "<"); + curr = g_slist_next(curr); + } + + curr = form_names; + while (curr != NULL) { + form = g_hash_table_lookup(forms, curr->data); + g_string_append(s, strdup(form->form_type)); + g_string_append(s, "<"); + + GSList *curr_field = form->fields; + while (curr_field != NULL) { + field = curr_field->data; + g_string_append(s, strdup(field->var)); + GSList *curr_value = field->values; + while (curr_value != NULL) { + g_string_append(s, strdup(curr_value->data)); + g_string_append(s, "<"); + curr_value = g_slist_next(curr_value); + } + curr_field = g_slist_next(curr_value); + } + } + + EVP_MD_CTX mdctx; + const EVP_MD *md; + + unsigned char md_value[EVP_MAX_MD_SIZE]; + unsigned int md_len; + OpenSSL_add_all_digests(); + md = EVP_get_digestbyname("SHA1"); + EVP_MD_CTX_init(&mdctx); + EVP_DigestInit_ex(&mdctx, md, NULL); + EVP_DigestUpdate(&mdctx, s->str, strlen(s->str)); + EVP_DigestFinal_ex(&mdctx, md_value, &md_len); + EVP_MD_CTX_cleanup(&mdctx); + + char *result = g_base64_encode(md_value, md_len); + + g_string_free(s, TRUE); + g_slist_free_full(identities, free); + g_slist_free_full(features, free); + g_slist_free_full(form_names, free); + g_hash_table_destroy(forms); + + return result; +} + +xmpp_stanza_t * +caps_create_query_response_stanza(xmpp_ctx_t * const ctx) +{ + xmpp_stanza_t *query = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(query, STANZA_NAME_QUERY); + xmpp_stanza_set_ns(query, XMPP_NS_DISCO_INFO); + + xmpp_stanza_t *identity = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(identity, "identity"); + xmpp_stanza_set_attribute(identity, "category", "client"); + xmpp_stanza_set_attribute(identity, "type", "pc"); + + GString *name_str = g_string_new("Profanity "); + g_string_append(name_str, PACKAGE_VERSION); + if (strcmp(PACKAGE_STATUS, "development") == 0) { + g_string_append(name_str, "dev"); + } + xmpp_stanza_set_attribute(identity, "name", name_str->str); + + xmpp_stanza_t *feature_caps = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(feature_caps, STANZA_NAME_FEATURE); + xmpp_stanza_set_attribute(feature_caps, STANZA_ATTR_VAR, STANZA_NS_CAPS); + + xmpp_stanza_t *feature_discoinfo = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(feature_discoinfo, STANZA_NAME_FEATURE); + xmpp_stanza_set_attribute(feature_discoinfo, STANZA_ATTR_VAR, XMPP_NS_DISCO_INFO); + + xmpp_stanza_t *feature_muc = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(feature_muc, STANZA_NAME_FEATURE); + xmpp_stanza_set_attribute(feature_muc, STANZA_ATTR_VAR, STANZA_NS_MUC); + + xmpp_stanza_t *feature_version = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(feature_version, STANZA_NAME_FEATURE); + xmpp_stanza_set_attribute(feature_version, STANZA_ATTR_VAR, STANZA_NS_VERSION); + + xmpp_stanza_add_child(query, identity); + xmpp_stanza_add_child(query, feature_muc); + xmpp_stanza_add_child(query, feature_discoinfo); + xmpp_stanza_add_child(query, feature_caps); + xmpp_stanza_add_child(query, feature_version); + + xmpp_stanza_release(identity); + xmpp_stanza_release(feature_muc); + xmpp_stanza_release(feature_discoinfo); + xmpp_stanza_release(feature_caps); + xmpp_stanza_release(feature_version); + + return query; +} + +void +caps_close(void) +{ + g_hash_table_destroy(capabilities); +} + +static void +_caps_destroy(Capabilities *caps) +{ + if (caps != NULL) { + FREE_SET_NULL(caps->client); + FREE_SET_NULL(caps); + } +} diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c new file mode 100644 index 00000000..3fdfd1c6 --- /dev/null +++ b/src/xmpp/connection.c @@ -0,0 +1,513 @@ +/* + * connection.c + * + * Copyright (C) 2012, 2013 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity 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. + * + * Profanity 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 Profanity. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +#include <strophe.h> + +#include "chat_session.h" +#include "common.h" +#include "contact_list.h" +#include "jid.h" +#include "log.h" +#include "preferences.h" +#include "profanity.h" +#include "muc.h" +#include "xmpp.h" + +static struct _jabber_conn_t { + xmpp_log_t *log; + xmpp_ctx_t *ctx; + xmpp_conn_t *conn; + jabber_conn_status_t conn_status; + jabber_presence_t presence_type; + char *presence_message; + int priority; + int tls_disabled; +} jabber_conn; + +// for auto reconnect +static struct { + char *name; + char *passwd; +} saved_account; + +static struct { + char *name; + char *jid; + char *passwd; + char *altdomain; +} saved_details; + +static GTimer *reconnect_timer; + +static log_level_t _get_log_level(xmpp_log_level_t xmpp_level); +static xmpp_log_level_t _get_xmpp_log_level(); +static void _xmpp_file_logger(void * const userdata, + const xmpp_log_level_t level, const char * const area, + const char * const msg); +static xmpp_log_t * _xmpp_get_file_logger(); + +static jabber_conn_status_t _jabber_connect(const char * const fulljid, + const char * const passwd, const char * const altdomain); +static void _jabber_reconnect(void); + +static void _connection_handler(xmpp_conn_t * const conn, + const xmpp_conn_event_t status, const int error, + xmpp_stream_error_t * const stream_error, void * const userdata); +static int _ping_timed_handler(xmpp_conn_t * const conn, void * const userdata); + +void +jabber_init(const int disable_tls) +{ + log_info("Initialising XMPP"); + jabber_conn.conn_status = JABBER_STARTED; + jabber_conn.presence_type = PRESENCE_OFFLINE; + jabber_conn.presence_message = NULL; + jabber_conn.tls_disabled = disable_tls; + presence_init(); +} + +void +jabber_restart(void) +{ + jabber_conn.conn_status = JABBER_STARTED; + jabber_conn.presence_type = PRESENCE_OFFLINE; + FREE_SET_NULL(jabber_conn.presence_message); +} + +jabber_conn_status_t +jabber_connect_with_account(ProfAccount *account, const char * const passwd) +{ + saved_account.name = strdup(account->name); + saved_account.passwd = strdup(passwd); + + log_info("Connecting using account: %s", account->name); + char *fulljid = create_fulljid(account->jid, account->resource); + jabber_conn_status_t result = _jabber_connect(fulljid, passwd, account->server); + + free(fulljid); + + return result; +} + +jabber_conn_status_t +jabber_connect_with_details(const char * const jid, + const char * const passwd, const char * const altdomain) +{ + saved_details.name = strdup(jid); + saved_details.passwd = strdup(passwd); + if (altdomain != NULL) { + saved_details.altdomain = strdup(altdomain); + } else { + saved_details.altdomain = NULL; + } + + Jid *jidp = jid_create(jid); + if (jidp->resourcepart == NULL) { + jid_destroy(jidp); + jidp = jid_create_from_bare_and_resource(jid, "profanity"); + saved_details.jid = strdup(jidp->fulljid); + } else { + saved_details.jid = strdup(jid); + } + jid_destroy(jidp); + + log_info("Connecting without account, JID: %s", saved_details.jid); + return _jabber_connect(saved_details.jid, passwd, saved_details.altdomain); +} + +void +jabber_disconnect(void) +{ + // if connected, send end stream and wait for response + if (jabber_conn.conn_status == JABBER_CONNECTED) { + log_info("Closing connection"); + jabber_conn.conn_status = JABBER_DISCONNECTING; + xmpp_disconnect(jabber_conn.conn); + + while (jabber_get_connection_status() == JABBER_DISCONNECTING) { + jabber_process_events(); + } + jabber_free_resources(); + } +} + +void +jabber_process_events(void) +{ + // run xmpp event loop if connected, connecting or disconnecting + if (jabber_conn.conn_status == JABBER_CONNECTED + || jabber_conn.conn_status == JABBER_CONNECTING + || jabber_conn.conn_status == JABBER_DISCONNECTING) { + xmpp_run_once(jabber_conn.ctx, 10); + + // check timer and reconnect if disconnected and timer set + } else if (prefs_get_reconnect() != 0) { + if ((jabber_conn.conn_status == JABBER_DISCONNECTED) && + (reconnect_timer != NULL)) { + if (g_timer_elapsed(reconnect_timer, NULL) > prefs_get_reconnect()) { + _jabber_reconnect(); + } + } + } + +} + +void +jabber_set_autoping(int seconds) +{ + if (jabber_conn.conn_status == JABBER_CONNECTED) { + xmpp_timed_handler_delete(jabber_conn.conn, _ping_timed_handler); + + if (seconds != 0) { + int millis = seconds * 1000; + xmpp_timed_handler_add(jabber_conn.conn, _ping_timed_handler, millis, + jabber_conn.ctx); + } + } +} + +jabber_conn_status_t +jabber_get_connection_status(void) +{ + return (jabber_conn.conn_status); +} + +xmpp_conn_t * +jabber_get_conn(void) +{ + return jabber_conn.conn; +} + +xmpp_ctx_t * +jabber_get_ctx(void) +{ + return jabber_conn.ctx; +} + +const char * +jabber_get_jid(void) +{ + return xmpp_conn_get_jid(jabber_conn.conn); +} + +jabber_presence_t +jabber_get_presence_type(void) +{ + return jabber_conn.presence_type; +} + +char * +jabber_get_presence_message(void) +{ + return jabber_conn.presence_message; +} + +char * +jabber_get_account_name(void) +{ + return saved_account.name; +} + +void +jabber_conn_set_presence_type(jabber_presence_t presence_type) +{ + jabber_conn.presence_type = presence_type; +} + +void +jabber_conn_set_presence_message(const char * const message) +{ + FREE_SET_NULL(jabber_conn.presence_message); + if (message != NULL) { + jabber_conn.presence_message = strdup(message); + } +} + +void +jabber_conn_set_priority(int priority) +{ + jabber_conn.priority = priority; +} + +void +jabber_free_resources(void) +{ + FREE_SET_NULL(saved_details.name); + FREE_SET_NULL(saved_details.jid); + FREE_SET_NULL(saved_details.passwd); + FREE_SET_NULL(saved_details.altdomain); + FREE_SET_NULL(saved_account.name); + FREE_SET_NULL(saved_account.passwd); + chat_sessions_clear(); + presence_free_sub_requests(); + xmpp_conn_release(jabber_conn.conn); + xmpp_ctx_free(jabber_conn.ctx); + xmpp_shutdown(); +} + +int +error_handler(xmpp_stanza_t * const stanza) +{ + gchar *err_msg = NULL; + gchar *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + xmpp_stanza_t *error_stanza = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_ERROR); + xmpp_stanza_t *text_stanza = + xmpp_stanza_get_child_by_name(error_stanza, STANZA_NAME_TEXT); + + if (error_stanza == NULL) { + log_debug("error message without <error/> received"); + } else { + + // check for text + if (text_stanza != NULL) { + err_msg = xmpp_stanza_get_text(text_stanza); + prof_handle_error_message(from, err_msg); + + // TODO : process 'type' attribute from <error/> [RFC6120, 8.3.2] + + // otherwise show defined-condition + } else { + xmpp_stanza_t *err_cond = xmpp_stanza_get_children(error_stanza); + + if (err_cond == NULL) { + log_debug("error message without <defined-condition/> or <text/> received"); + + } else { + err_msg = xmpp_stanza_get_name(err_cond); + prof_handle_error_message(from, err_msg); + + // TODO : process 'type' attribute from <error/> [RFC6120, 8.3.2] + } + } + } + + return 1; +} + +static jabber_conn_status_t +_jabber_connect(const char * const fulljid, const char * const passwd, + const char * const altdomain) +{ + Jid *jid = jid_create(fulljid); + + if (jid == NULL) { + log_error("Malformed JID not able to connect: %s", fulljid); + jabber_conn.conn_status = JABBER_DISCONNECTED; + return jabber_conn.conn_status; + } else if (jid->fulljid == NULL) { + log_error("Full JID required to connect, received: %s", fulljid); + jabber_conn.conn_status = JABBER_DISCONNECTED; + return jabber_conn.conn_status; + } + + jid_destroy(jid); + + log_info("Connecting as %s", fulljid); + xmpp_initialize(); + jabber_conn.log = _xmpp_get_file_logger(); + jabber_conn.ctx = xmpp_ctx_new(NULL, jabber_conn.log); + jabber_conn.conn = xmpp_conn_new(jabber_conn.ctx); + xmpp_conn_set_jid(jabber_conn.conn, fulljid); + xmpp_conn_set_pass(jabber_conn.conn, passwd); + + if (jabber_conn.tls_disabled) + xmpp_conn_disable_tls(jabber_conn.conn); + + int connect_status = xmpp_connect_client(jabber_conn.conn, altdomain, 0, + _connection_handler, jabber_conn.ctx); + + if (connect_status == 0) + jabber_conn.conn_status = JABBER_CONNECTING; + else + jabber_conn.conn_status = JABBER_DISCONNECTED; + + return jabber_conn.conn_status; +} + +static void +_jabber_reconnect(void) +{ + // reconnect with account. + ProfAccount *account = accounts_get_account(saved_account.name); + + if (account == NULL) { + log_error("Unable to reconnect, account no longer exists: %s", saved_account.name); + } else { + char *fulljid = create_fulljid(account->jid, account->resource); + log_debug("Attempting reconnect with account %s", account->name); + _jabber_connect(fulljid, saved_account.passwd, account->server); + free(fulljid); + g_timer_start(reconnect_timer); + } +} + +static void +_connection_handler(xmpp_conn_t * const conn, + const xmpp_conn_event_t status, const int error, + xmpp_stream_error_t * const stream_error, void * const userdata) +{ + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + + // login success + if (status == XMPP_CONN_CONNECT) { + + // logged in with account + if (saved_account.name != NULL) { + prof_handle_login_account_success(saved_account.name); + + // logged in without account, use details to create new account + } else { + accounts_add(saved_details.name, saved_details.altdomain); + accounts_set_jid(saved_details.name, saved_details.jid); + + prof_handle_login_account_success(saved_details.name); + saved_account.name = strdup(saved_details.name); + saved_account.passwd = strdup(saved_details.passwd); + + FREE_SET_NULL(saved_details.name); + FREE_SET_NULL(saved_details.jid); + FREE_SET_NULL(saved_details.passwd); + FREE_SET_NULL(saved_details.altdomain); + } + + chat_sessions_init(); + + message_add_handlers(); + presence_add_handlers(); + iq_add_handlers(); + + if (prefs_get_autoping() != 0) { + int millis = prefs_get_autoping() * 1000; + xmpp_timed_handler_add(conn, _ping_timed_handler, millis, ctx); + } + + iq_roster_request(); + jabber_conn.conn_status = JABBER_CONNECTED; + jabber_conn.presence_type = PRESENCE_ONLINE; + + if (prefs_get_reconnect() != 0) { + if (reconnect_timer != NULL) { + g_timer_destroy(reconnect_timer); + reconnect_timer = NULL; + } + } + + } else if (status == XMPP_CONN_DISCONNECT) { + + // lost connection for unkown reason + if (jabber_conn.conn_status == JABBER_CONNECTED) { + prof_handle_lost_connection(); + if (prefs_get_reconnect() != 0) { + assert(reconnect_timer == NULL); + reconnect_timer = g_timer_new(); + // TODO: free resources but leave saved_user untouched + } else { + jabber_free_resources(); + } + + // login attempt failed + } else if (jabber_conn.conn_status != JABBER_DISCONNECTING) { + if (reconnect_timer == NULL) { + prof_handle_failed_login(); + jabber_free_resources(); + } else { + if (prefs_get_reconnect() != 0) { + g_timer_start(reconnect_timer); + } + // TODO: free resources but leave saved_user untouched + } + } + + // close stream response from server after disconnect is handled too + jabber_conn.conn_status = JABBER_DISCONNECTED; + jabber_conn.presence_type = PRESENCE_OFFLINE; + } +} + +static int +_ping_timed_handler(xmpp_conn_t * const conn, void * const userdata) +{ + if (jabber_conn.conn_status == JABBER_CONNECTED) { + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + + xmpp_stanza_t *iq = stanza_create_ping_iq(ctx); + xmpp_send(conn, iq); + xmpp_stanza_release(iq); + } + + return 1; +} + +static log_level_t +_get_log_level(xmpp_log_level_t xmpp_level) +{ + if (xmpp_level == XMPP_LEVEL_DEBUG) { + return PROF_LEVEL_DEBUG; + } else if (xmpp_level == XMPP_LEVEL_INFO) { + return PROF_LEVEL_INFO; + } else if (xmpp_level == XMPP_LEVEL_WARN) { + return PROF_LEVEL_WARN; + } else { + return PROF_LEVEL_ERROR; + } +} + +static xmpp_log_level_t +_get_xmpp_log_level() +{ + log_level_t prof_level = log_get_filter(); + + if (prof_level == PROF_LEVEL_DEBUG) { + return XMPP_LEVEL_DEBUG; + } else if (prof_level == PROF_LEVEL_INFO) { + return XMPP_LEVEL_INFO; + } else if (prof_level == PROF_LEVEL_WARN) { + return XMPP_LEVEL_WARN; + } else { + return XMPP_LEVEL_ERROR; + } +} + +static void +_xmpp_file_logger(void * const userdata, const xmpp_log_level_t level, + const char * const area, const char * const msg) +{ + log_level_t prof_level = _get_log_level(level); + log_msg(prof_level, area, msg); +} + +static xmpp_log_t * +_xmpp_get_file_logger() +{ + xmpp_log_level_t level = _get_xmpp_log_level(); + xmpp_log_t *file_log = malloc(sizeof(xmpp_log_t)); + + file_log->handler = _xmpp_file_logger; + file_log->userdata = &level; + + return file_log; +} + diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c new file mode 100644 index 00000000..267a31c7 --- /dev/null +++ b/src/xmpp/iq.c @@ -0,0 +1,337 @@ +/* + * iq.c + * + * Copyright (C) 2012, 2013 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity 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. + * + * Profanity 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 Profanity. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <string.h> + +#include <strophe.h> + +#include "common.h" +#include "config.h" +#include "contact_list.h" +#include "log.h" +#include "xmpp.h" + +#define HANDLE(ns, type, func) xmpp_handler_add(conn, func, ns, STANZA_NAME_IQ, type, ctx) + +static int _iq_handle_error(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, void * const userdata); +static int _iq_handle_roster_set(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, void * const userdata); +static int _iq_handle_roster_result(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, void * const userdata); +static int _iq_handle_ping_get(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, void * const userdata); +static int _iq_handle_version_get(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, void * const userdata); +static int _iq_handle_discoinfo_get(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, void * const userdata); +static int _iq_handle_discoinfo_result(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, void * const userdata); + +void +iq_add_handlers(void) +{ + xmpp_conn_t * const conn = jabber_get_conn(); + xmpp_ctx_t * const ctx = jabber_get_ctx(); + HANDLE(NULL, STANZA_TYPE_ERROR, _iq_handle_error); + HANDLE(XMPP_NS_ROSTER, STANZA_TYPE_SET, _iq_handle_roster_set); + HANDLE(XMPP_NS_ROSTER, STANZA_TYPE_RESULT, _iq_handle_roster_result); + HANDLE(XMPP_NS_DISCO_INFO, STANZA_TYPE_GET, _iq_handle_discoinfo_get); + HANDLE(XMPP_NS_DISCO_INFO, STANZA_TYPE_RESULT, _iq_handle_discoinfo_result); + HANDLE(STANZA_NS_VERSION, STANZA_TYPE_GET, _iq_handle_version_get); + HANDLE(STANZA_NS_PING, STANZA_TYPE_GET, _iq_handle_ping_get); +} + +void +iq_roster_request(void) +{ + xmpp_conn_t * const conn = jabber_get_conn(); + xmpp_ctx_t * const ctx = jabber_get_ctx(); + xmpp_stanza_t *iq = stanza_create_roster_iq(ctx); + xmpp_send(conn, iq); + xmpp_stanza_release(iq); +} + +static int +_iq_handle_error(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) +{ + const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID); + + if (id != NULL) { + log_error("IQ error received, id: %s.", id); + } else { + log_error("IQ error recieved."); + } + + return 1; +} + +static int +_iq_handle_roster_set(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); + xmpp_stanza_t *item = + xmpp_stanza_get_child_by_name(query, STANZA_NAME_ITEM); + + if (item == NULL) { + return 1; + } + + const char *jid = xmpp_stanza_get_attribute(item, STANZA_ATTR_JID); + const char *sub = xmpp_stanza_get_attribute(item, STANZA_ATTR_SUBSCRIPTION); + if (g_strcmp0(sub, "remove") == 0) { + contact_list_remove(jid); + return 1; + } + + gboolean pending_out = FALSE; + const char *ask = xmpp_stanza_get_attribute(item, STANZA_ATTR_ASK); + if ((ask != NULL) && (strcmp(ask, "subscribe") == 0)) { + pending_out = TRUE; + } + + contact_list_update_subscription(jid, sub, pending_out); + + return 1; +} + +static int +_iq_handle_roster_result(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) +{ + const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID); + + // handle initial roster response + if (g_strcmp0(id, "roster") == 0) { + xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); + xmpp_stanza_t *item = xmpp_stanza_get_children(query); + + while (item != NULL) { + const char *jid = xmpp_stanza_get_attribute(item, STANZA_ATTR_JID); + const char *name = xmpp_stanza_get_attribute(item, STANZA_ATTR_NAME); + const char *sub = xmpp_stanza_get_attribute(item, STANZA_ATTR_SUBSCRIPTION); + + gboolean pending_out = FALSE; + const char *ask = xmpp_stanza_get_attribute(item, STANZA_ATTR_ASK); + if (g_strcmp0(ask, "subscribe") == 0) { + pending_out = TRUE; + } + + gboolean added = contact_list_add(jid, name, "offline", NULL, sub, + pending_out); + + if (!added) { + log_warning("Attempt to add contact twice: %s", jid); + } + + item = xmpp_stanza_get_next(item); + } + + jabber_presence_t connect_presence = accounts_get_login_presence(jabber_get_account_name()); + presence_update(connect_presence, NULL, 0); + } + + return 1; +} + +static int +_iq_handle_ping_get(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID); + const char *to = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TO); + const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + + if ((from == NULL) || (to == NULL)) { + return 1; + } + + xmpp_stanza_t *pong = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(pong, STANZA_NAME_IQ); + xmpp_stanza_set_attribute(pong, STANZA_ATTR_TO, from); + xmpp_stanza_set_attribute(pong, STANZA_ATTR_FROM, to); + xmpp_stanza_set_attribute(pong, STANZA_ATTR_TYPE, STANZA_TYPE_RESULT); + + if (id != NULL) { + xmpp_stanza_set_attribute(pong, STANZA_ATTR_ID, id); + } + + xmpp_send(conn, pong); + xmpp_stanza_release(pong); + + return 1; +} + +static int +_iq_handle_version_get(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID); + const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + + if (from != NULL) { + xmpp_stanza_t *response = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(response, STANZA_NAME_IQ); + if (id != NULL) { + xmpp_stanza_set_id(response, id); + } + xmpp_stanza_set_attribute(response, STANZA_ATTR_TO, from); + xmpp_stanza_set_type(response, STANZA_TYPE_RESULT); + + xmpp_stanza_t *query = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(query, STANZA_NAME_QUERY); + xmpp_stanza_set_ns(query, STANZA_NS_VERSION); + + xmpp_stanza_t *name = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(name, "name"); + xmpp_stanza_t *name_txt = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(name_txt, "Profanity"); + xmpp_stanza_add_child(name, name_txt); + + xmpp_stanza_t *version = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(version, "version"); + xmpp_stanza_t *version_txt = xmpp_stanza_new(ctx); + GString *version_str = g_string_new(PACKAGE_VERSION); + if (strcmp(PACKAGE_STATUS, "development") == 0) { + g_string_append(version_str, "dev"); + } + xmpp_stanza_set_text(version_txt, version_str->str); + xmpp_stanza_add_child(version, version_txt); + + xmpp_stanza_add_child(query, name); + xmpp_stanza_add_child(query, version); + xmpp_stanza_add_child(response, query); + + xmpp_send(conn, response); + + xmpp_stanza_release(response); + } + + return 1; +} + +static int +_iq_handle_discoinfo_get(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + + xmpp_stanza_t *incoming_query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); + const char *node_str = xmpp_stanza_get_attribute(incoming_query, STANZA_ATTR_NODE); + + if (from != NULL && node_str != NULL) { + xmpp_stanza_t *response = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(response, STANZA_NAME_IQ); + xmpp_stanza_set_id(response, xmpp_stanza_get_id(stanza)); + xmpp_stanza_set_attribute(response, STANZA_ATTR_TO, from); + xmpp_stanza_set_type(response, STANZA_TYPE_RESULT); + xmpp_stanza_t *query = caps_create_query_response_stanza(ctx); + xmpp_stanza_set_attribute(query, STANZA_ATTR_NODE, node_str); + xmpp_stanza_add_child(response, query); + xmpp_send(conn, response); + + xmpp_stanza_release(response); + } + + return 1; +} + +static int +_iq_handle_discoinfo_result(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) +{ + const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID); + + if ((id != NULL) && (g_str_has_prefix(id, "disco"))) { + xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); + char *node = xmpp_stanza_get_attribute(query, STANZA_ATTR_NODE); + if (node == NULL) { + return 1; + } + + char *caps_key = NULL; + + // xep-0115 + if (g_strcmp0(id, "disco") == 0) { + caps_key = strdup(node); + + // validate sha1 + gchar **split = g_strsplit(node, "#", -1); + char *given_sha1 = split[1]; + char *generated_sha1 = caps_create_sha1_str(query); + + if (g_strcmp0(given_sha1, generated_sha1) != 0) { + log_info("Invalid SHA1 recieved for caps."); + FREE_SET_NULL(generated_sha1); + g_strfreev(split); + + return 1; + } + FREE_SET_NULL(generated_sha1); + g_strfreev(split); + + // non supported hash, or legacy caps + } else { + caps_key = strdup(id + 6); + } + + // already cached + if (caps_contains(caps_key)) { + log_info("Client info already cached."); + return 1; + } + + xmpp_stanza_t *identity = xmpp_stanza_get_child_by_name(query, "identity"); + + if (identity == NULL) { + return 1; + } + + const char *category = xmpp_stanza_get_attribute(identity, "category"); + if (category == NULL) { + return 1; + } + + if (strcmp(category, "client") != 0) { + return 1; + } + + const char *name = xmpp_stanza_get_attribute(identity, "name"); + if (name == 0) { + return 1; + } + + caps_add(caps_key, name); + + free(caps_key); + + return 1; + } else { + return 1; + } +} diff --git a/src/xmpp/message.c b/src/xmpp/message.c new file mode 100644 index 00000000..cf5b552e --- /dev/null +++ b/src/xmpp/message.c @@ -0,0 +1,304 @@ +/* + * message.c + * + * Copyright (C) 2012, 2013 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity 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. + * + * Profanity 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 Profanity. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <string.h> + +#include <strophe.h> + +#include "chat_session.h" +#include "log.h" +#include "muc.h" +#include "preferences.h" +#include "profanity.h" +#include "xmpp.h" + +#define HANDLE(ns, type, func) xmpp_handler_add(conn, func, ns, STANZA_NAME_MESSAGE, type, ctx) + +static int _message_handler(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, void * const userdata); +static int _groupchat_message_handler(xmpp_stanza_t * const stanza); +static int _chat_message_handler(xmpp_stanza_t * const stanza); + +void +message_add_handlers(void) +{ + xmpp_conn_t * const conn = jabber_get_conn(); + xmpp_ctx_t * const ctx = jabber_get_ctx(); + HANDLE(NULL, NULL, _message_handler); +} + +void +message_send(const char * const msg, const char * const recipient) +{ + xmpp_conn_t * const conn = jabber_get_conn(); + xmpp_ctx_t * const ctx = jabber_get_ctx(); + if (prefs_get_states()) { + if (!chat_session_exists(recipient)) { + chat_session_start(recipient, TRUE); + } + } + + xmpp_stanza_t *message; + if (prefs_get_states() && chat_session_get_recipient_supports(recipient)) { + chat_session_set_active(recipient); + message = stanza_create_message(ctx, recipient, STANZA_TYPE_CHAT, + msg, STANZA_NAME_ACTIVE); + } else { + message = stanza_create_message(ctx, recipient, STANZA_TYPE_CHAT, + msg, NULL); + } + + xmpp_send(conn, message); + xmpp_stanza_release(message); +} + +void +message_send_groupchat(const char * const msg, const char * const recipient) +{ + xmpp_conn_t * const conn = jabber_get_conn(); + xmpp_ctx_t * const ctx = jabber_get_ctx(); + xmpp_stanza_t *message = stanza_create_message(ctx, recipient, + STANZA_TYPE_GROUPCHAT, msg, NULL); + + xmpp_send(conn, message); + xmpp_stanza_release(message); +} + +void +message_send_composing(const char * const recipient) +{ + xmpp_conn_t * const conn = jabber_get_conn(); + xmpp_ctx_t * const ctx = jabber_get_ctx(); + xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, recipient, + STANZA_NAME_COMPOSING); + + xmpp_send(conn, stanza); + xmpp_stanza_release(stanza); + chat_session_set_sent(recipient); +} + +void +message_send_paused(const char * const recipient) +{ + xmpp_conn_t * const conn = jabber_get_conn(); + xmpp_ctx_t * const ctx = jabber_get_ctx(); + xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, recipient, + STANZA_NAME_PAUSED); + + xmpp_send(conn, stanza); + xmpp_stanza_release(stanza); + chat_session_set_sent(recipient); +} + +void +message_send_inactive(const char * const recipient) +{ + xmpp_conn_t * const conn = jabber_get_conn(); + xmpp_ctx_t * const ctx = jabber_get_ctx(); + xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, recipient, + STANZA_NAME_INACTIVE); + + xmpp_send(conn, stanza); + xmpp_stanza_release(stanza); + chat_session_set_sent(recipient); +} + +void +message_send_gone(const char * const recipient) +{ + xmpp_conn_t * const conn = jabber_get_conn(); + xmpp_ctx_t * const ctx = jabber_get_ctx(); + xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, recipient, + STANZA_NAME_GONE); + + xmpp_send(conn, stanza); + xmpp_stanza_release(stanza); + chat_session_set_sent(recipient); +} + +static int +_message_handler(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, void * const userdata) +{ + gchar *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE); + + if (type == NULL) { + log_error("Message stanza received with no type attribute"); + return 1; + } else if (strcmp(type, STANZA_TYPE_ERROR) == 0) { + return error_handler(stanza); + } else if (strcmp(type, STANZA_TYPE_GROUPCHAT) == 0) { + return _groupchat_message_handler(stanza); + } else if (strcmp(type, STANZA_TYPE_CHAT) == 0) { + return _chat_message_handler(stanza); + } else { + log_error("Message stanza received with unknown type: %s", type); + return 1; + } +} + +static int +_groupchat_message_handler(xmpp_stanza_t * const stanza) +{ + char *message = NULL; + char *room_jid = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + Jid *jid = jid_create(room_jid); + + // handle room broadcasts + if (jid->resourcepart == NULL) { + xmpp_stanza_t *subject = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_SUBJECT); + + // handle subject + if (subject != NULL) { + message = xmpp_stanza_get_text(subject); + if (message != NULL) { + prof_handle_room_subject(jid->barejid, message); + } + + return 1; + + // handle other room broadcasts + } else { + xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BODY); + if (body != NULL) { + message = xmpp_stanza_get_text(body); + if (message != NULL) { + prof_handle_room_broadcast(room_jid, message); + } + } + + return 1; + } + } + + + if (!jid_is_valid_room_form(jid)) { + log_error("Invalid room JID: %s", jid->str); + return 1; + } + + // room not active in profanity + if (!muc_room_is_active(jid)) { + log_error("Message recieved for inactive chat room: %s", jid->str); + return 1; + } + + // determine if the notifications happened whilst offline + GTimeVal tv_stamp; + gboolean delayed = stanza_get_delay(stanza, &tv_stamp); + xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BODY); + + // check for and deal with message + if (body != NULL) { + char *message = xmpp_stanza_get_text(body); + if (delayed) { + prof_handle_room_history(jid->barejid, jid->resourcepart, tv_stamp, message); + } else { + prof_handle_room_message(jid->barejid, jid->resourcepart, message); + } + } + + jid_destroy(jid); + + return 1; +} + +static int +_chat_message_handler(xmpp_stanza_t * const stanza) +{ + gchar *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + Jid *jid = jid_create(from); + + // private message from chat room use full jid (room/nick) + if (muc_room_is_active(jid)) { + // determine if the notifications happened whilst offline + GTimeVal tv_stamp; + gboolean delayed = stanza_get_delay(stanza, &tv_stamp); + + // check for and deal with message + xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BODY); + if (body != NULL) { + char *message = xmpp_stanza_get_text(body); + if (delayed) { + prof_handle_delayed_message(jid->str, message, tv_stamp, TRUE); + } else { + prof_handle_incoming_message(jid->str, message, TRUE); + } + } + + free(jid); + return 1; + + // standard chat message, use jid without resource + } else { + // determine chatstate support of recipient + gboolean recipient_supports = FALSE; + if (stanza_contains_chat_state(stanza)) { + recipient_supports = TRUE; + } + + // create or update chat session + if (!chat_session_exists(jid->barejid)) { + chat_session_start(jid->barejid, recipient_supports); + } else { + chat_session_set_recipient_supports(jid->barejid, recipient_supports); + } + + // determine if the notifications happened whilst offline + GTimeVal tv_stamp; + gboolean delayed = stanza_get_delay(stanza, &tv_stamp); + + // deal with chat states if recipient supports them + if (recipient_supports && (!delayed)) { + if (xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_COMPOSING) != NULL) { + if (prefs_get_notify_typing() || prefs_get_intype()) { + prof_handle_typing(jid->barejid); + } + } else if (xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_GONE) != NULL) { + prof_handle_gone(jid->barejid); + } else if (xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_PAUSED) != NULL) { + // do something + } else if (xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_INACTIVE) != NULL) { + // do something + } else { // handle <active/> + // do something + } + } + + // check for and deal with message + xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BODY); + if (body != NULL) { + char *message = xmpp_stanza_get_text(body); + if (delayed) { + prof_handle_delayed_message(jid->barejid, message, tv_stamp, FALSE); + } else { + prof_handle_incoming_message(jid->barejid, message, FALSE); + } + } + + free(jid); + return 1; + } + +} + diff --git a/src/xmpp/presence.c b/src/xmpp/presence.c new file mode 100644 index 00000000..b29b9122 --- /dev/null +++ b/src/xmpp/presence.c @@ -0,0 +1,487 @@ +/* + * presence.c + * + * Copyright (C) 2012, 2013 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity 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. + * + * Profanity 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 Profanity. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <string.h> + +#include <glib.h> + +#include "common.h" +#include "log.h" +#include "muc.h" +#include "preferences.h" +#include "profanity.h" +#include "xmpp.h" + +static GHashTable *sub_requests; + +#define HANDLE(ns, type, func) xmpp_handler_add(conn, func, ns, STANZA_NAME_PRESENCE, type, ctx) + +static int _presence_handler(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, void * const userdata); +static char* _handle_presence_caps(xmpp_stanza_t * const stanza); +static int _room_presence_handler(const char * const jid, + xmpp_stanza_t * const stanza); + +void +presence_init(void) +{ + sub_requests = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); +} + +void +presence_add_handlers(void) +{ + xmpp_conn_t * const conn = jabber_get_conn(); + xmpp_ctx_t * const ctx = jabber_get_ctx(); + HANDLE(NULL, NULL, _presence_handler); +} + +void +presence_subscription(const char * const jid, const jabber_subscr_t action) +{ + xmpp_ctx_t *ctx = jabber_get_ctx(); + xmpp_conn_t *conn = jabber_get_conn(); + xmpp_stanza_t *presence; + char *type, *jid_cpy, *bare_jid; + + // jid must be a bare JID + jid_cpy = strdup(jid); + bare_jid = strtok(jid_cpy, "/"); + g_hash_table_remove(sub_requests, bare_jid); + + if (action == PRESENCE_SUBSCRIBE) + type = STANZA_TYPE_SUBSCRIBE; + else if (action == PRESENCE_SUBSCRIBED) + type = STANZA_TYPE_SUBSCRIBED; + else if (action == PRESENCE_UNSUBSCRIBED) + type = STANZA_TYPE_UNSUBSCRIBED; + else { // unknown action + free(jid_cpy); + return; + } + + presence = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(presence, STANZA_NAME_PRESENCE); + xmpp_stanza_set_type(presence, type); + xmpp_stanza_set_attribute(presence, STANZA_ATTR_TO, bare_jid); + xmpp_send(conn, presence); + xmpp_stanza_release(presence); + free(jid_cpy); +} + +GList * +presence_get_subscription_requests(void) +{ + return g_hash_table_get_keys(sub_requests); +} + +void +presence_free_sub_requests(void) +{ + if (sub_requests != NULL) + g_hash_table_remove_all(sub_requests); +} + +void +presence_join_room(Jid *jid) +{ + xmpp_ctx_t *ctx = jabber_get_ctx(); + xmpp_conn_t *conn = jabber_get_conn(); + xmpp_stanza_t *presence = stanza_create_room_join_presence(ctx, jid->fulljid); + xmpp_send(conn, presence); + xmpp_stanza_release(presence); + + muc_join_room(jid->barejid, jid->resourcepart); +} + +void +presence_change_room_nick(const char * const room, const char * const nick) +{ + xmpp_ctx_t *ctx = jabber_get_ctx(); + xmpp_conn_t *conn = jabber_get_conn(); + char *full_room_jid = create_fulljid(room, nick); + xmpp_stanza_t *presence = stanza_create_room_newnick_presence(ctx, full_room_jid); + xmpp_send(conn, presence); + xmpp_stanza_release(presence); + + free(full_room_jid); +} + +void +presence_leave_chat_room(const char * const room_jid) +{ + xmpp_ctx_t *ctx = jabber_get_ctx(); + xmpp_conn_t *conn = jabber_get_conn(); + char *nick = muc_get_room_nick(room_jid); + + xmpp_stanza_t *presence = stanza_create_room_leave_presence(ctx, room_jid, + nick); + xmpp_send(conn, presence); + xmpp_stanza_release(presence); +} + +void +presence_update(jabber_presence_t presence_type, const char * const msg, + int idle) +{ + xmpp_ctx_t *ctx = jabber_get_ctx(); + xmpp_conn_t *conn = jabber_get_conn(); + int pri; + char *show, *last; + + // don't send presence when disconnected + if (jabber_get_connection_status() != JABBER_CONNECTED) + return; + + pri = accounts_get_priority_for_presence_type(jabber_get_account_name(), + presence_type); + if (pri < JABBER_PRIORITY_MIN || pri > JABBER_PRIORITY_MAX) + pri = 0; + + jabber_conn_set_presence_type(presence_type); + jabber_conn_set_presence_message(msg); + jabber_conn_set_priority(pri); + + switch(presence_type) + { + case PRESENCE_AWAY: + show = STANZA_TEXT_AWAY; + last = STANZA_TEXT_AWAY; + break; + case PRESENCE_DND: + show = STANZA_TEXT_DND; + last = STANZA_TEXT_DND; + break; + case PRESENCE_CHAT: + show = STANZA_TEXT_CHAT; + last = STANZA_TEXT_CHAT; + break; + case PRESENCE_XA: + show = STANZA_TEXT_XA; + last = STANZA_TEXT_XA; + break; + default: // PRESENCE_ONLINE + show = NULL; + last = STANZA_TEXT_ONLINE; + break; + } + + + xmpp_stanza_t *presence = stanza_create_presence(ctx, show, msg); + + // servers must treat no priority as 0 + if (pri != 0) { + xmpp_stanza_t *priority, *value; + char pri_str[10]; + + snprintf(pri_str, sizeof(pri_str), "%d", pri); + priority = xmpp_stanza_new(ctx); + value = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(priority, STANZA_NAME_PRIORITY); + xmpp_stanza_set_text(value, pri_str); + xmpp_stanza_add_child(priority, value); + xmpp_stanza_add_child(presence, priority); + } + + if (idle > 0) { + xmpp_stanza_t *query = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(query, STANZA_NAME_QUERY); + xmpp_stanza_set_ns(query, STANZA_NS_LASTACTIVITY); + char idle_str[10]; + snprintf(idle_str, sizeof(idle_str), "%d", idle); + xmpp_stanza_set_attribute(query, STANZA_ATTR_SECONDS, idle_str); + xmpp_stanza_add_child(presence, query); + } + + // add caps + xmpp_stanza_t *caps = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(caps, STANZA_NAME_C); + xmpp_stanza_set_ns(caps, STANZA_NS_CAPS); + xmpp_stanza_t *query = caps_create_query_response_stanza(ctx); + + char *sha1 = caps_create_sha1_str(query); + xmpp_stanza_set_attribute(caps, STANZA_ATTR_HASH, "sha-1"); + xmpp_stanza_set_attribute(caps, STANZA_ATTR_NODE, "http://www.profanity.im"); + xmpp_stanza_set_attribute(caps, STANZA_ATTR_VER, sha1); + xmpp_stanza_add_child(presence, caps); + + xmpp_send(conn, presence); + + // send presence for each room + GList *rooms = muc_get_active_room_list(); + while (rooms != NULL) { + char *room = rooms->data; + char *nick = muc_get_room_nick(room); + char *full_room_jid = create_fulljid(room, nick); + + xmpp_stanza_set_attribute(presence, STANZA_ATTR_TO, full_room_jid); + xmpp_send(conn, presence); + + rooms = g_list_next(rooms); + } + g_list_free(rooms); + + xmpp_stanza_release(presence); + + FREE_SET_NULL(sha1); + + // set last presence for account + accounts_set_last_presence(jabber_get_account_name(), last); +} + + +static int +_presence_handler(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, void * const userdata) +{ + const char *jid = xmpp_conn_get_jid(conn); + char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE); + + Jid *my_jid = jid_create(jid); + Jid *from_jid = jid_create(from); + + if ((type != NULL) && (strcmp(type, STANZA_TYPE_ERROR) == 0)) { + return error_handler(stanza); + } + + // handle chat room presence + if (muc_room_is_active(from_jid)) { + return _room_presence_handler(from_jid->str, stanza); + + // handle regular presence + } else { + char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE); + char *show_str, *status_str; + int idle_seconds = stanza_get_idle_time(stanza); + GDateTime *last_activity = NULL; + + if (idle_seconds > 0) { + GDateTime *now = g_date_time_new_now_local(); + last_activity = g_date_time_add_seconds(now, 0 - idle_seconds); + g_date_time_unref(now); + } + + char *caps_key = _handle_presence_caps(stanza); + + xmpp_stanza_t *status = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_STATUS); + if (status != NULL) + status_str = xmpp_stanza_get_text(status); + else + status_str = NULL; + + if (type == NULL) { // available + xmpp_stanza_t *show = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_SHOW); + if (show != NULL) + show_str = xmpp_stanza_get_text(show); + else + show_str = "online"; + + if (strcmp(my_jid->barejid, from_jid->barejid) !=0) { + prof_handle_contact_online(from_jid->barejid, show_str, status_str, last_activity, caps_key); + } + } else if (strcmp(type, STANZA_TYPE_UNAVAILABLE) == 0) { + if (strcmp(my_jid->barejid, from_jid->barejid) !=0) { + prof_handle_contact_offline(from_jid->barejid, "offline", status_str); + } + + if (last_activity != NULL) { + g_date_time_unref(last_activity); + } + + // subscriptions + } else if (strcmp(type, STANZA_TYPE_SUBSCRIBE) == 0) { + prof_handle_subscription(from_jid->barejid, PRESENCE_SUBSCRIBE); + g_hash_table_insert(sub_requests, strdup(from_jid->barejid), strdup(from_jid->barejid)); + } else if (strcmp(type, STANZA_TYPE_SUBSCRIBED) == 0) { + prof_handle_subscription(from_jid->barejid, PRESENCE_SUBSCRIBED); + g_hash_table_remove(sub_requests, from_jid->barejid); + } else if (strcmp(type, STANZA_TYPE_UNSUBSCRIBED) == 0) { + prof_handle_subscription(from_jid->barejid, PRESENCE_UNSUBSCRIBED); + g_hash_table_remove(sub_requests, from_jid->barejid); + } else { /* unknown type */ + log_debug("Received presence with unknown type '%s'", type); + } + } + + return 1; +} + +static char * +_handle_presence_caps(xmpp_stanza_t * const stanza) +{ + xmpp_ctx_t *ctx = jabber_get_ctx(); + xmpp_conn_t *conn = jabber_get_conn(); + char *caps_key = NULL; + char *node = NULL; + char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + if (stanza_contains_caps(stanza)) { + char *hash_type = stanza_caps_get_hash(stanza); + + // xep-0115 + if (hash_type != NULL) { + + // supported hash + if (strcmp(hash_type, "sha-1") == 0) { + node = stanza_get_caps_str(stanza); + caps_key = node; + + if (node != NULL) { + if (!caps_contains(caps_key)) { + xmpp_stanza_t *iq = stanza_create_disco_iq(ctx, "disco", from, node); + xmpp_send(conn, iq); + xmpp_stanza_release(iq); + } + } + + // unsupported hash + } else { + node = stanza_get_caps_str(stanza); + caps_key = from; + + if (node != NULL) { + if (!caps_contains(caps_key)) { + GString *id = g_string_new("disco_"); + g_string_append(id, from); + xmpp_stanza_t *iq = stanza_create_disco_iq(ctx, id->str, from, node); + xmpp_send(conn, iq); + xmpp_stanza_release(iq); + g_string_free(id, TRUE); + } + } + } + + return strdup(caps_key); + + //ignore or handle legacy caps + } else { + node = stanza_get_caps_str(stanza); + caps_key = from; + + if (node != NULL) { + if (!caps_contains(caps_key)) { + GString *id = g_string_new("disco_"); + g_string_append(id, from); + xmpp_stanza_t *iq = stanza_create_disco_iq(ctx, id->str, from, node); + xmpp_send(conn, iq); + xmpp_stanza_release(iq); + g_string_free(id, TRUE); + } + } + + return caps_key; + } + } + return NULL; +} + +static int +_room_presence_handler(const char * const jid, xmpp_stanza_t * const stanza) +{ + char *room = NULL; + char *nick = NULL; + + if (!parse_room_jid(jid, &room, &nick)) { + log_error("Could not parse room jid: %s", room); + return 1; + } + + // handle self presence + if (stanza_is_muc_self_presence(stanza, jabber_get_jid())) { + char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE); + gboolean nick_change = stanza_is_room_nick_change(stanza); + + if ((type != NULL) && (strcmp(type, STANZA_TYPE_UNAVAILABLE) == 0)) { + + // leave room if not self nick change + if (nick_change) { + muc_set_room_pending_nick_change(room); + } else { + prof_handle_leave_room(room); + } + + // handle self nick change + } else if (muc_is_room_pending_nick_change(room)) { + muc_complete_room_nick_change(room, nick); + prof_handle_room_nick_change(room, nick); + + // handle roster complete + } else if (!muc_get_roster_received(room)) { + prof_handle_room_roster_complete(room); + + } + + // handle presence from room members + } else { + char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE); + char *show_str, *status_str; + char *caps_key = _handle_presence_caps(stanza); + + xmpp_stanza_t *status = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_STATUS); + if (status != NULL) { + status_str = xmpp_stanza_get_text(status); + } else { + status_str = NULL; + } + + if ((type != NULL) && (strcmp(type, STANZA_TYPE_UNAVAILABLE) == 0)) { + + // handle nickname change + if (stanza_is_room_nick_change(stanza)) { + char *new_nick = stanza_get_new_nick(stanza); + muc_set_roster_pending_nick_change(room, new_nick, nick); + } else { + prof_handle_room_member_offline(room, nick, "offline", status_str); + } + } else { + xmpp_stanza_t *show = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_SHOW); + if (show != NULL) { + show_str = xmpp_stanza_get_text(show); + } else { + show_str = "online"; + } + if (!muc_get_roster_received(room)) { + muc_add_to_roster(room, nick, show_str, status_str, caps_key); + } else { + char *old_nick = muc_complete_roster_nick_change(room, nick); + + if (old_nick != NULL) { + muc_add_to_roster(room, nick, show_str, status_str, caps_key); + prof_handle_room_member_nick_change(room, old_nick, nick); + } else { + if (!muc_nick_in_roster(room, nick)) { + prof_handle_room_member_online(room, nick, show_str, status_str, caps_key); + } else { + prof_handle_room_member_presence(room, nick, show_str, status_str, caps_key); + } + } + } + } + } + + free(room); + free(nick); + + return 1; +} + diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c new file mode 100644 index 00000000..90501ffe --- /dev/null +++ b/src/xmpp/stanza.c @@ -0,0 +1,608 @@ +/* + * stanza.c + * + * Copyright (C) 2012, 2013 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity 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. + * + * Profanity 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 Profanity. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <string.h> + +#include <glib.h> +#include <strophe.h> + +#include "common.h" +#include "xmpp.h" + +static int _field_compare(FormField *f1, FormField *f2); + +xmpp_stanza_t * +stanza_create_chat_state(xmpp_ctx_t *ctx, const char * const recipient, + const char * const state) +{ + xmpp_stanza_t *msg, *chat_state; + + msg = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(msg, STANZA_NAME_MESSAGE); + xmpp_stanza_set_type(msg, STANZA_TYPE_CHAT); + xmpp_stanza_set_attribute(msg, STANZA_ATTR_TO, recipient); + + chat_state = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(chat_state, state); + xmpp_stanza_set_ns(chat_state, STANZA_NS_CHATSTATES); + xmpp_stanza_add_child(msg, chat_state); + + return msg; +} + +xmpp_stanza_t * +stanza_create_message(xmpp_ctx_t *ctx, const char * const recipient, + const char * const type, const char * const message, + const char * const state) +{ + char *encoded_xml = encode_xml(message); + + xmpp_stanza_t *msg, *body, *text; + + msg = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(msg, STANZA_NAME_MESSAGE); + xmpp_stanza_set_type(msg, type); + xmpp_stanza_set_attribute(msg, STANZA_ATTR_TO, recipient); + + body = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(body, STANZA_NAME_BODY); + + text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(text, encoded_xml); + xmpp_stanza_add_child(body, text); + xmpp_stanza_add_child(msg, body); + + if (state != NULL) { + xmpp_stanza_t *chat_state = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(chat_state, state); + xmpp_stanza_set_ns(chat_state, STANZA_NS_CHATSTATES); + xmpp_stanza_add_child(msg, chat_state); + } + + g_free(encoded_xml); + + return msg; +} + +xmpp_stanza_t * +stanza_create_room_join_presence(xmpp_ctx_t *ctx, + const char * const full_room_jid) +{ + xmpp_stanza_t *presence = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(presence, STANZA_NAME_PRESENCE); + xmpp_stanza_set_attribute(presence, STANZA_ATTR_TO, full_room_jid); + + xmpp_stanza_t *x = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(x, STANZA_NAME_X); + xmpp_stanza_set_ns(x, STANZA_NS_MUC); + + xmpp_stanza_add_child(presence, x); + + return presence; +} + +xmpp_stanza_t * +stanza_create_room_newnick_presence(xmpp_ctx_t *ctx, + const char * const full_room_jid) +{ + xmpp_stanza_t *presence = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(presence, STANZA_NAME_PRESENCE); + xmpp_stanza_set_attribute(presence, STANZA_ATTR_TO, full_room_jid); + + return presence; +} + +xmpp_stanza_t * +stanza_create_room_leave_presence(xmpp_ctx_t *ctx, const char * const room, + const char * const nick) +{ + GString *full_jid = g_string_new(room); + g_string_append(full_jid, "/"); + g_string_append(full_jid, nick); + + xmpp_stanza_t *presence = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(presence, STANZA_NAME_PRESENCE); + xmpp_stanza_set_type(presence, STANZA_TYPE_UNAVAILABLE); + xmpp_stanza_set_attribute(presence, STANZA_ATTR_TO, full_jid->str); + + g_string_free(full_jid, TRUE); + + return presence; +} + +xmpp_stanza_t * +stanza_create_presence(xmpp_ctx_t *ctx, const char * const show, + const char * const status) +{ + xmpp_stanza_t *presence = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(presence, STANZA_NAME_PRESENCE); + + if (show != NULL) { + xmpp_stanza_t *show_stanza = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(show_stanza, STANZA_NAME_SHOW); + xmpp_stanza_t *text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(text, show); + xmpp_stanza_add_child(show_stanza, text); + xmpp_stanza_add_child(presence, show_stanza); + xmpp_stanza_release(text); + xmpp_stanza_release(show_stanza); + } + + if (status != NULL) { + xmpp_stanza_t *status_stanza = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(status_stanza, STANZA_NAME_STATUS); + xmpp_stanza_t *text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(text, status); + xmpp_stanza_add_child(status_stanza, text); + xmpp_stanza_add_child(presence, status_stanza); + xmpp_stanza_release(text); + xmpp_stanza_release(status_stanza); + } + + return presence; +} + +xmpp_stanza_t * +stanza_create_roster_iq(xmpp_ctx_t *ctx) +{ + xmpp_stanza_t *iq = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(iq, STANZA_NAME_IQ); + xmpp_stanza_set_type(iq, STANZA_TYPE_GET); + xmpp_stanza_set_id(iq, "roster"); + + xmpp_stanza_t *query = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(query, STANZA_NAME_QUERY); + xmpp_stanza_set_ns(query, XMPP_NS_ROSTER); + + xmpp_stanza_add_child(iq, query); + xmpp_stanza_release(query); + + return iq; +} + +xmpp_stanza_t * +stanza_create_disco_iq(xmpp_ctx_t *ctx, const char * const id, const char * const to, + const char * const node) +{ + xmpp_stanza_t *iq = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(iq, STANZA_NAME_IQ); + xmpp_stanza_set_type(iq, STANZA_TYPE_GET); + xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, to); + xmpp_stanza_set_id(iq, id); + + xmpp_stanza_t *query = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(query, STANZA_NAME_QUERY); + xmpp_stanza_set_ns(query, XMPP_NS_DISCO_INFO); + xmpp_stanza_set_attribute(query, STANZA_ATTR_NODE, node); + + xmpp_stanza_add_child(iq, query); + xmpp_stanza_release(query); + + return iq; +} + +gboolean +stanza_contains_chat_state(xmpp_stanza_t *stanza) +{ + return ((xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_ACTIVE) != NULL) || + (xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_COMPOSING) != NULL) || + (xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_PAUSED) != NULL) || + (xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_GONE) != NULL) || + (xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_INACTIVE) != NULL)); +} + +xmpp_stanza_t * +stanza_create_ping_iq(xmpp_ctx_t *ctx) +{ + xmpp_stanza_t *iq = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(iq, STANZA_NAME_IQ); + xmpp_stanza_set_type(iq, STANZA_TYPE_GET); + xmpp_stanza_set_id(iq, "c2s1"); + + xmpp_stanza_t *ping = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(ping, STANZA_NAME_PING); + + xmpp_stanza_set_ns(ping, STANZA_NS_PING); + + xmpp_stanza_add_child(iq, ping); + xmpp_stanza_release(ping); + + return iq; +} + +gboolean +stanza_get_delay(xmpp_stanza_t * const stanza, GTimeVal *tv_stamp) +{ + // first check for XEP-0203 delayed delivery + xmpp_stanza_t *delay = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_DELAY); + if (delay != NULL) { + char *xmlns = xmpp_stanza_get_attribute(delay, STANZA_ATTR_XMLNS); + if ((xmlns != NULL) && (strcmp(xmlns, "urn:xmpp:delay") == 0)) { + char *stamp = xmpp_stanza_get_attribute(delay, STANZA_ATTR_STAMP); + if ((stamp != NULL) && (g_time_val_from_iso8601(stamp, tv_stamp))) { + return TRUE; + } + } + } + + // otherwise check for XEP-0091 legacy delayed delivery + // stanp format : CCYYMMDDThh:mm:ss + xmpp_stanza_t *x = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_X); + if (x != NULL) { + char *xmlns = xmpp_stanza_get_attribute(x, STANZA_ATTR_XMLNS); + if ((xmlns != NULL) && (strcmp(xmlns, "jabber:x:delay") == 0)) { + char *stamp = xmpp_stanza_get_attribute(x, STANZA_ATTR_STAMP); + if ((stamp != NULL) && (g_time_val_from_iso8601(stamp, tv_stamp))) { + return TRUE; + } + } + } + + return FALSE; +} + +gboolean +stanza_is_muc_self_presence(xmpp_stanza_t * const stanza, + const char * const self_jid) +{ + if (stanza == NULL) { + return FALSE; + } + if (strcmp(xmpp_stanza_get_name(stanza), STANZA_NAME_PRESENCE) != 0) { + return FALSE; + } + + xmpp_stanza_t *x = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_X); + + if (x == NULL) { + return FALSE; + } + + char *ns = xmpp_stanza_get_ns(x); + if (ns == NULL) { + return FALSE; + } + if (strcmp(ns, STANZA_NS_MUC_USER) != 0) { + return FALSE; + } + + xmpp_stanza_t *x_children = xmpp_stanza_get_children(x); + if (x_children == NULL) { + return FALSE; + } + + while (x_children != NULL) { + if (strcmp(xmpp_stanza_get_name(x_children), STANZA_NAME_STATUS) == 0) { + char *code = xmpp_stanza_get_attribute(x_children, STANZA_ATTR_CODE); + if (strcmp(code, "110") == 0) { + return TRUE; + } + } + x_children = xmpp_stanza_get_next(x_children); + } + + // for older server that don't send status 110 + x_children = xmpp_stanza_get_children(x); + while (x_children != NULL) { + if (strcmp(xmpp_stanza_get_name(x_children), STANZA_NAME_ITEM) == 0) { + char *jid = xmpp_stanza_get_attribute(x_children, STANZA_ATTR_JID); + if (jid != NULL) { + if (g_str_has_prefix(jid, self_jid)) { + return TRUE; + } + } + } + x_children = xmpp_stanza_get_next(x_children); + } + + return FALSE; +} + +gboolean +stanza_is_room_nick_change(xmpp_stanza_t * const stanza) +{ + if (stanza == NULL) { + return FALSE; + } + if (strcmp(xmpp_stanza_get_name(stanza), STANZA_NAME_PRESENCE) != 0) { + return FALSE; + } + + xmpp_stanza_t *x = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_X); + + if (x == NULL) { + return FALSE; + } + + char *ns = xmpp_stanza_get_ns(x); + if (ns == NULL) { + return FALSE; + } + if (strcmp(ns, STANZA_NS_MUC_USER) != 0) { + return FALSE; + } + + xmpp_stanza_t *x_children = xmpp_stanza_get_children(x); + if (x_children == NULL) { + return FALSE; + } + + while (x_children != NULL) { + if (strcmp(xmpp_stanza_get_name(x_children), STANZA_NAME_STATUS) == 0) { + char *code = xmpp_stanza_get_attribute(x_children, STANZA_ATTR_CODE); + if (strcmp(code, "303") == 0) { + return TRUE; + } + } + x_children = xmpp_stanza_get_next(x_children); + } + + return FALSE; + +} + +char * +stanza_get_new_nick(xmpp_stanza_t * const stanza) +{ + if (!stanza_is_room_nick_change(stanza)) { + return NULL; + } else { + xmpp_stanza_t *x = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_X); + xmpp_stanza_t *x_children = xmpp_stanza_get_children(x); + + while (x_children != NULL) { + if (strcmp(xmpp_stanza_get_name(x_children), STANZA_NAME_ITEM) == 0) { + char *nick = xmpp_stanza_get_attribute(x_children, STANZA_ATTR_NICK); + if (nick != NULL) { + return strdup(nick); + } + } + x_children = xmpp_stanza_get_next(x_children); + } + + return NULL; + } +} + +int +stanza_get_idle_time(xmpp_stanza_t * const stanza) +{ + xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); + + if (query == NULL) { + return 0; + } + + char *ns = xmpp_stanza_get_ns(query); + if (ns == NULL) { + return 0; + } + + if (strcmp(ns, STANZA_NS_LASTACTIVITY) != 0) { + return 0; + } + + char *seconds_str = xmpp_stanza_get_attribute(query, STANZA_ATTR_SECONDS); + if (seconds_str == NULL) { + return 0; + } + + int result = atoi(seconds_str); + if (result < 1) { + return 0; + } else { + return result; + } +} + +gboolean +stanza_contains_caps(xmpp_stanza_t * const stanza) +{ + xmpp_stanza_t *caps = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_C); + + if (caps == NULL) { + return FALSE; + } + + if (strcmp(xmpp_stanza_get_ns(caps), STANZA_NS_CAPS) != 0) { + return FALSE; + } + + return TRUE; +} + +gboolean +stanza_is_version_request(xmpp_stanza_t * const stanza) +{ + char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE); + + if (g_strcmp0(type, STANZA_TYPE_GET) != 0) { + return FALSE; + } + + xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); + + if (query == NULL) { + return FALSE; + } + + char *ns = xmpp_stanza_get_ns(query); + + if (ns == NULL) { + return FALSE; + } + + if (strcmp(ns, STANZA_NS_VERSION) != 0) { + return FALSE; + } + + return TRUE; +} + +gboolean +stanza_is_caps_request(xmpp_stanza_t * const stanza) +{ + char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE); + + if (g_strcmp0(type, STANZA_TYPE_GET) != 0) { + return FALSE; + } + + xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); + + if (query == NULL) { + return FALSE; + } + + char *ns = xmpp_stanza_get_ns(query); + + if (ns == NULL) { + return FALSE; + } + + if (strcmp(ns, XMPP_NS_DISCO_INFO) != 0) { + return FALSE; + } + + return TRUE; +} + +char * +stanza_caps_get_hash(xmpp_stanza_t * const stanza) +{ + xmpp_stanza_t *caps = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_C); + + if (caps == NULL) { + return NULL; + } + + if (strcmp(xmpp_stanza_get_ns(caps), STANZA_NS_CAPS) != 0) { + return NULL; + } + + char *result = xmpp_stanza_get_attribute(caps, STANZA_ATTR_HASH); + + return result; + +} + +char * +stanza_get_caps_str(xmpp_stanza_t * const stanza) +{ + xmpp_stanza_t *caps = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_C); + + if (caps == NULL) { + return NULL; + } + + if (strcmp(xmpp_stanza_get_ns(caps), STANZA_NS_CAPS) != 0) { + return NULL; + } + + char *node = xmpp_stanza_get_attribute(caps, STANZA_ATTR_NODE); + char *ver = xmpp_stanza_get_attribute(caps, STANZA_ATTR_VER); + + if ((node == NULL) || (ver == NULL)) { + return NULL; + } + + GString *caps_gstr = g_string_new(node); + g_string_append(caps_gstr, "#"); + g_string_append(caps_gstr, ver); + char *caps_str = caps_gstr->str; + g_string_free(caps_gstr, FALSE); + + return caps_str; +} + +DataForm * +stanza_create_form(xmpp_stanza_t * const stanza) +{ + DataForm *result = NULL; + + xmpp_stanza_t *child = xmpp_stanza_get_children(stanza); + + if (child != NULL) { + result = malloc(sizeof(struct data_form_t)); + result->form_type = NULL; + result->fields = NULL; + } + + //handle fields + while (child != NULL) { + char *var = xmpp_stanza_get_attribute(child, "var"); + + // handle FORM_TYPE + if (g_strcmp0(var, "FORM_TYPE")) { + xmpp_stanza_t *value = xmpp_stanza_get_child_by_name(child, "value"); + char *value_text = xmpp_stanza_get_text(value); + result->form_type = strdup(value_text); + + // handle regular fields + } else { + FormField *field = malloc(sizeof(struct form_field_t)); + field->var = strdup(var); + field->values = NULL; + xmpp_stanza_t *value = xmpp_stanza_get_children(child); + + // handle values + while (value != NULL) { + char *text = xmpp_stanza_get_text(value); + field->values = g_slist_insert_sorted(field->values, strdup(text), (GCompareFunc)octet_compare); + value = xmpp_stanza_get_next(value); + } + + result->fields = g_slist_insert_sorted(result->fields, field, (GCompareFunc)_field_compare); + } + + child = xmpp_stanza_get_next(child); + } + + return result; +} + +void +stanza_destroy_form(DataForm *form) +{ + if (form != NULL) { + FREE_SET_NULL(form->form_type); + if (form->fields != NULL) { + GSList *curr_field = form->fields; + while (curr_field != NULL) { + FormField *field = curr_field->data; + FREE_SET_NULL(field->var); + if ((field->values) != NULL) { + g_slist_free_full(field->values, free); + } + } + } + + form = NULL; + } +} + +static int +_field_compare(FormField *f1, FormField *f2) +{ + return octet_compare((unsigned char *)f1->var, (unsigned char *)f2->var); +} diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h new file mode 100644 index 00000000..18fb9a3f --- /dev/null +++ b/src/xmpp/xmpp.h @@ -0,0 +1,235 @@ +/* + * xmpp.h + * + * Copyright (C) 2012, 2013 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity 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. + * + * Profanity 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 Profanity. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef XMPP_H +#define XMPP_H + +#include <strophe.h> + +#include "accounts.h" +#include "jid.h" + +#define JABBER_PRIORITY_MIN -128 +#define JABBER_PRIORITY_MAX 127 + +#define STANZA_NAME_ACTIVE "active" +#define STANZA_NAME_INACTIVE "inactive" +#define STANZA_NAME_COMPOSING "composing" +#define STANZA_NAME_PAUSED "paused" +#define STANZA_NAME_GONE "gone" + +#define STANZA_NAME_MESSAGE "message" +#define STANZA_NAME_BODY "body" +#define STANZA_NAME_PRESENCE "presence" +#define STANZA_NAME_PRIORITY "priority" +#define STANZA_NAME_X "x" +#define STANZA_NAME_SHOW "show" +#define STANZA_NAME_STATUS "status" +#define STANZA_NAME_IQ "iq" +#define STANZA_NAME_QUERY "query" +#define STANZA_NAME_DELAY "delay" +#define STANZA_NAME_ERROR "error" +#define STANZA_NAME_PING "ping" +#define STANZA_NAME_TEXT "text" +#define STANZA_NAME_SUBJECT "subject" +#define STANZA_NAME_ITEM "item" +#define STANZA_NAME_C "c" +#define STANZA_NAME_IDENTITY "identity" +#define STANZA_NAME_FEATURE "feature" + +#define STANZA_TYPE_CHAT "chat" +#define STANZA_TYPE_GROUPCHAT "groupchat" +#define STANZA_TYPE_UNAVAILABLE "unavailable" +#define STANZA_TYPE_SUBSCRIBE "subscribe" +#define STANZA_TYPE_SUBSCRIBED "subscribed" +#define STANZA_TYPE_UNSUBSCRIBED "unsubscribed" +#define STANZA_TYPE_GET "get" +#define STANZA_TYPE_SET "set" +#define STANZA_TYPE_ERROR "error" +#define STANZA_TYPE_RESULT "result" + +#define STANZA_ATTR_TO "to" +#define STANZA_ATTR_FROM "from" +#define STANZA_ATTR_STAMP "stamp" +#define STANZA_ATTR_TYPE "type" +#define STANZA_ATTR_CODE "code" +#define STANZA_ATTR_JID "jid" +#define STANZA_ATTR_NAME "name" +#define STANZA_ATTR_SUBSCRIPTION "subscription" +#define STANZA_ATTR_XMLNS "xmlns" +#define STANZA_ATTR_NICK "nick" +#define STANZA_ATTR_ASK "ask" +#define STANZA_ATTR_ID "id" +#define STANZA_ATTR_SECONDS "seconds" +#define STANZA_ATTR_NODE "node" +#define STANZA_ATTR_VER "ver" +#define STANZA_ATTR_VAR "var" +#define STANZA_ATTR_HASH "hash" + +#define STANZA_TEXT_AWAY "away" +#define STANZA_TEXT_DND "dnd" +#define STANZA_TEXT_CHAT "chat" +#define STANZA_TEXT_XA "xa" +#define STANZA_TEXT_ONLINE "online" + +#define STANZA_NS_CHATSTATES "http://jabber.org/protocol/chatstates" +#define STANZA_NS_MUC "http://jabber.org/protocol/muc" +#define STANZA_NS_MUC_USER "http://jabber.org/protocol/muc#user" +#define STANZA_NS_CAPS "http://jabber.org/protocol/caps" +#define STANZA_NS_PING "urn:xmpp:ping" +#define STANZA_NS_LASTACTIVITY "jabber:iq:last" +#define STANZA_NS_DATA "jabber:x:data" +#define STANZA_NS_VERSION "jabber:iq:version" + +typedef enum { + JABBER_UNDEFINED, + JABBER_STARTED, + JABBER_CONNECTING, + JABBER_CONNECTED, + JABBER_DISCONNECTING, + JABBER_DISCONNECTED +} jabber_conn_status_t; + +typedef enum { + PRESENCE_SUBSCRIBE, + PRESENCE_SUBSCRIBED, + PRESENCE_UNSUBSCRIBED +} jabber_subscr_t; + +typedef struct capabilities_t { + char *client; +} Capabilities; + +typedef struct form_field_t { + char *var; + GSList *values; +} FormField; + +typedef struct data_form_t { + char *form_type; + GSList *fields; +} DataForm; + +// connection functions +void jabber_init(const int disable_tls); +jabber_conn_status_t jabber_connect_with_details(const char * const jid, + const char * const passwd, const char * const altdomain); +jabber_conn_status_t jabber_connect_with_account(ProfAccount *account, + const char * const passwd); +void jabber_disconnect(void); +void jabber_process_events(void); +const char * jabber_get_jid(void); +jabber_conn_status_t jabber_get_connection_status(void); +jabber_presence_t jabber_get_presence_type(void); +char * jabber_get_presence_message(void); +void jabber_free_resources(void); +void jabber_restart(void); +void jabber_set_autoping(int seconds); +xmpp_conn_t *jabber_get_conn(void); +xmpp_ctx_t *jabber_get_ctx(void); +int error_handler(xmpp_stanza_t * const stanza); +void jabber_conn_set_presence_type(jabber_presence_t presence_type); +void jabber_conn_set_priority(int priority); +void jabber_conn_set_presence_message(const char * const message); +char* jabber_get_account_name(void); + +// message functions +void message_add_handlers(void); +void message_send(const char * const msg, const char * const recipient); +void message_send_groupchat(const char * const msg, const char * const recipient); +void message_send_inactive(const char * const recipient); +void message_send_composing(const char * const recipient); +void message_send_paused(const char * const recipient); +void message_send_gone(const char * const recipient); + +// iq functions +void iq_add_handlers(void); +void iq_roster_request(void); + +// presence functions +void presence_add_handlers(void); +void presence_init(void); +void presence_subscription(const char * const jid, const jabber_subscr_t action); +GList* presence_get_subscription_requests(void); +void presence_free_sub_requests(void); +void presence_join_room(Jid *jid); +void presence_change_room_nick(const char * const room, const char * const nick); +void presence_leave_chat_room(const char * const room_jid); +void presence_update(jabber_presence_t status, const char * const msg, + int idle); + +// caps functions +void caps_init(void); +void caps_add(const char * const caps_str, const char * const client); +gboolean caps_contains(const char * const caps_str); +Capabilities* caps_get(const char * const caps_str); +char* caps_create_sha1_str(xmpp_stanza_t * const query); +xmpp_stanza_t* caps_create_query_response_stanza(xmpp_ctx_t * const ctx); +void caps_close(void); + +// stanza related functions +xmpp_stanza_t* stanza_create_chat_state(xmpp_ctx_t *ctx, + const char * const recipient, const char * const state); + +xmpp_stanza_t* stanza_create_message(xmpp_ctx_t *ctx, + const char * const recipient, const char * const type, + const char * const message, const char * const state); + +xmpp_stanza_t* stanza_create_room_join_presence(xmpp_ctx_t *ctx, + const char * const full_room_jid); + +xmpp_stanza_t* stanza_create_room_newnick_presence(xmpp_ctx_t *ctx, + const char * const full_room_jid); + +xmpp_stanza_t* stanza_create_room_leave_presence(xmpp_ctx_t *ctx, + const char * const room, const char * const nick); + +xmpp_stanza_t* stanza_create_presence(xmpp_ctx_t *ctx, const char * const show, + const char * const status); + +xmpp_stanza_t* stanza_create_roster_iq(xmpp_ctx_t *ctx); +xmpp_stanza_t* stanza_create_ping_iq(xmpp_ctx_t *ctx); +xmpp_stanza_t* stanza_create_disco_iq(xmpp_ctx_t *ctx, const char * const id, + const char * const to, const char * const node); + +gboolean stanza_contains_chat_state(xmpp_stanza_t *stanza); + +gboolean stanza_get_delay(xmpp_stanza_t * const stanza, GTimeVal *tv_stamp); + +gboolean stanza_is_muc_self_presence(xmpp_stanza_t * const stanza, + const char * const self_jid); +gboolean stanza_is_room_nick_change(xmpp_stanza_t * const stanza); + +char * stanza_get_new_nick(xmpp_stanza_t * const stanza); + +int stanza_get_idle_time(xmpp_stanza_t * const stanza); +char * stanza_get_caps_str(xmpp_stanza_t * const stanza); +gboolean stanza_contains_caps(xmpp_stanza_t * const stanza); +char * stanza_caps_get_hash(xmpp_stanza_t * const stanza); +gboolean stanza_is_caps_request(xmpp_stanza_t * const stanza); + +gboolean stanza_is_version_request(xmpp_stanza_t * const stanza); + +DataForm * stanza_create_form(xmpp_stanza_t * const stanza); +void stanza_destroy_form(DataForm *form); + +#endif |