/* * message.c * vim: expandtab:ts=4:sts=4:sw=4 * * Copyright (C) 2012 - 2019 James Booth * * 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 . * * In addition, as a special exception, the copyright holders give permission to * link the code of portions of this program with the OpenSSL library under * certain conditions as described in each individual source file, and * distribute linked combinations including the two. * * You must obey the GNU General Public License in all respects for all of the * code used other than OpenSSL. If you modify file(s) with this exception, you * may extend this exception to your version of the file(s), but you are not * obligated to do so. If you do not wish to do so, delete this exception * statement from your version. If you delete this exception statement from all * source files in the program, then also delete it here. * */ #include "config.h" #include #include #ifdef HAVE_LIBMESODE #include #endif #ifdef HAVE_LIBSTROPHE #include #endif #include "profanity.h" #include "log.h" #include "config/preferences.h" #include "event/server_events.h" #include "pgp/gpg.h" #include "plugins/plugins.h" #include "ui/ui.h" #include "ui/window_list.h" #include "xmpp/chat_session.h" #include "xmpp/muc.h" #include "xmpp/session.h" #include "xmpp/message.h" #include "xmpp/roster.h" #include "xmpp/roster_list.h" #include "xmpp/stanza.h" #include "xmpp/connection.h" #include "xmpp/xmpp.h" #ifdef HAVE_OMEMO #include "xmpp/omemo.h" #include "omemo/omemo.h" #endif typedef struct p_message_handle_t { ProfMessageCallback func; ProfMessageFreeCallback free_func; void *userdata; } ProfMessageHandler; static int _message_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata); static void _handle_error(xmpp_stanza_t *const stanza); static void _handle_groupchat(xmpp_stanza_t *const stanza); static void _handle_muc_user(xmpp_stanza_t *const stanza); static void _handle_muc_private_message(xmpp_stanza_t *const stanza); static void _handle_conference(xmpp_stanza_t *const stanza); static void _handle_captcha(xmpp_stanza_t *const stanza); static void _handle_receipt_received(xmpp_stanza_t *const stanza); static void _handle_chat(xmpp_stanza_t *const stanza, gboolean is_mam); static gboolean _handle_mam(xmpp_stanza_t *const stanza); static void _send_message_stanza(xmpp_stanza_t *const stanza); static GHashTable *pubsub_event_handlers; static int _message_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata) { log_debug("Message stanza handler fired"); char *text; size_t text_size; xmpp_stanza_to_text(stanza, &text, &text_size); gboolean cont = plugins_on_message_stanza_receive(text); if (!cont) { xmpp_free(connection_get_ctx(), text); return 1; } const char *type = xmpp_stanza_get_type(stanza); if (type == NULL) { if (_handle_mam(stanza)) { xmpp_free(connection_get_ctx(), text); return 1; } log_info("Received without 'type': %s", text); xmpp_free(connection_get_ctx(), text); return 1; } xmpp_free(connection_get_ctx(), text); if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) { _handle_error(stanza); } // if muc if (g_strcmp0(type, STANZA_TYPE_GROUPCHAT) == 0) { _handle_groupchat(stanza); } xmpp_stanza_t *mucuser = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_MUC_USER); if (mucuser) { _handle_muc_user(stanza); } xmpp_stanza_t *conference = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_CONFERENCE); if (conference) { _handle_conference(stanza); } xmpp_stanza_t *captcha = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_CAPTCHA); if (captcha) { _handle_captcha(stanza); } xmpp_stanza_t *receipts = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_RECEIPTS); if (receipts) { _handle_receipt_received(stanza); } xmpp_stanza_t *event = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_PUBSUB_EVENT); if (event) { xmpp_stanza_t *child = xmpp_stanza_get_children(event); if (child) { const char *node = xmpp_stanza_get_attribute(child, STANZA_ATTR_NODE); if (node) { ProfMessageHandler *handler = g_hash_table_lookup(pubsub_event_handlers, node); if (handler) { int keep = handler->func(stanza, handler->userdata); if (!keep) { g_hash_table_remove(pubsub_event_handlers, node); } } } } } _handle_chat(stanza, FALSE); return 1; } void message_handlers_init(void) { xmpp_conn_t * const conn = connection_get_conn(); xmpp_ctx_t * const ctx = connection_get_ctx(); xmpp_handler_add(conn, _message_handler, NULL, STANZA_NAME_MESSAGE, NULL, ctx); if (pubsub_event_handlers) { GList *keys = g_hash_table_get_keys(pubsub_event_handlers); GList *curr = keys; while (curr) { ProfMessageHandler *handler = g_hash_table_lookup(pubsub_event_handlers, curr->data); if (handler->free_func && handler->userdata) { handler->free_func(handler->userdata); } curr = g_list_next(curr); } g_list_free(keys); g_hash_table_destroy(pubsub_event_handlers); } pubsub_event_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, free, free); } ProfMessage * message_init(void) { ProfMessage *message = malloc(sizeof(ProfMessage)); message->from_jid = NULL; message->to_jid = NULL; message->id = NULL; message->originid = NULL; message->replace_id = NULL; message->body = NULL; message->encrypted = NULL; message->plain = NULL; message->enc = PROF_MSG_ENC_NONE; message->timestamp = NULL; message->trusted = true; message->type = PROF_MSG_TYPE_UNINITIALIZED; return message; } void message_free(ProfMessage *message) { xmpp_ctx_t *ctx = connection_get_ctx(); if (message->from_jid) { jid_destroy(message->from_jid); } if (message->to_jid) { jid_destroy(message->to_jid); } if (message->id) { xmpp_free(ctx, message->id); } if (message->originid) { xmpp_free(ctx, message->originid); } if (message->replace_id) { xmpp_free(ctx, message->replace_id); } if (message->body) { xmpp_free(ctx, message->body); } if (message->encrypted) { xmpp_free(ctx, message->encrypted); } if (message->plain) { free(message->plain); } if (message->timestamp) { g_date_time_unref(message->timestamp); } free(message); } void message_handlers_clear(void) { if (pubsub_event_handlers) { g_hash_table_remove_all(pubsub_event_handlers); } } void message_pubsub_event_handler_add(const char *const node, ProfMessageCallback func, ProfMessageFreeCallback free_func, void *userdata) { ProfMessageHandler *handler = malloc(sizeof(ProfMessageHandler)); handler->func = func; handler->free_func = free_func; handler->userdata = userdata; g_hash_table_insert(pubsub_event_handlers, strdup(node), handler); } char* message_send_chat(const char *const barejid, const char *const msg, const char *const oob_url, gboolean request_receipt, const char *const replace_id) { xmpp_ctx_t * const ctx = connection_get_ctx(); char *state = chat_session_get_state(barejid); char *jid = chat_session_get_jid(barejid); char *id = connection_create_stanza_id(); xmpp_stanza_t *message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id); xmpp_message_set_body(message, msg); free(jid); if (state) { stanza_attach_state(ctx, message, state); } if (oob_url) { stanza_attach_x_oob_url(ctx, message, oob_url); } if (request_receipt) { stanza_attach_receipt_request(ctx, message); } if (replace_id) { stanza_attach_correction(ctx, message, replace_id); } _send_message_stanza(message); xmpp_stanza_release(message); return id; } char* message_send_chat_pgp(const char *const barejid, const char *const msg, gboolean request_receipt, const char *const replace_id) { xmpp_ctx_t * const ctx = connection_get_ctx(); char *state = chat_session_get_state(barejid); char *jid = chat_session_get_jid(barejid); char *id = connection_create_stanza_id(); xmpp_stanza_t *message = NULL; #ifdef HAVE_LIBGPGME char *account_name = session_get_account_name(); ProfAccount *account = accounts_get_account(account_name); if (account->pgp_keyid) { Jid *jidp = jid_create(jid); char *encrypted = p_gpg_encrypt(jidp->barejid, msg, account->pgp_keyid); if (encrypted) { message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id); xmpp_message_set_body(message, "This message is encrypted (XEP-0027)."); xmpp_stanza_t *x = xmpp_stanza_new(ctx); xmpp_stanza_set_name(x, STANZA_NAME_X); xmpp_stanza_set_ns(x, STANZA_NS_ENCRYPTED); xmpp_stanza_t *enc_st = xmpp_stanza_new(ctx); xmpp_stanza_set_text(enc_st, encrypted); xmpp_stanza_add_child(x, enc_st); xmpp_stanza_release(enc_st); xmpp_stanza_add_child(message, x); xmpp_stanza_release(x); free(encrypted); } else { message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id); xmpp_message_set_body(message, msg); } jid_destroy(jidp); } else { message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id); xmpp_message_set_body(message, msg); } account_free(account); #else message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id); xmpp_message_set_body(message, msg); #endif free(jid); if (state) { stanza_attach_state(ctx, message, state); } if (request_receipt) { stanza_attach_receipt_request(ctx, message); } if (replace_id) { stanza_attach_correction(ctx, message, replace_id); } _send_message_stanza(message); xmpp_stanza_release(message); return id; } char* message_send_chat_otr(const char *const barejid, const char *const msg, gboolean request_receipt, const char *const replace_id) { xmpp_ctx_t * const ctx = connection_get_ctx(); char *state = chat_session_get_state(barejid); char *jid = chat_session_get_jid(barejid); char *id = connection_create_stanza_id(); xmpp_stanza_t *message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, barejid, id); xmpp_message_set_body(message, msg); free(jid); if (state) { stanza_attach_state(ctx, message, state); } stanza_attach_carbons_private(ctx, message); stanza_attach_hints_no_copy(ctx, message); stanza_attach_hints_no_store(ctx, message); if (request_receipt) { stanza_attach_receipt_request(ctx, message); } if (replace_id) { stanza_attach_correction(ctx, message, replace_id); } _send_message_stanza(message); xmpp_stanza_release(message); return id; } #ifdef HAVE_OMEMO char* message_send_chat_omemo(const char *const jid, uint32_t sid, GList *keys, const unsigned char *const iv, size_t iv_len, const unsigned char *const ciphertext, size_t ciphertext_len, gboolean request_receipt, gboolean muc, const char *const replace_id) { char *state = chat_session_get_state(jid); xmpp_ctx_t * const ctx = connection_get_ctx(); char *id; xmpp_stanza_t *message; if (muc) { id = connection_create_stanza_id(); message = xmpp_message_new(ctx, STANZA_TYPE_GROUPCHAT, jid, id); stanza_attach_origin_id(ctx, message, id); } else { id = connection_create_stanza_id(); message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id); } xmpp_stanza_t *encrypted = xmpp_stanza_new(ctx); xmpp_stanza_set_name(encrypted, "encrypted"); xmpp_stanza_set_ns(encrypted, STANZA_NS_OMEMO); xmpp_stanza_t *header = xmpp_stanza_new(ctx); xmpp_stanza_set_name(header, "header"); char *sid_text = g_strdup_printf("%d", sid); xmpp_stanza_set_attribute(header, "sid", sid_text); g_free(sid_text); GList *key_iter; for (key_iter = keys; key_iter != NULL; key_iter = key_iter->next) { omemo_key_t *key = (omemo_key_t *)key_iter->data; xmpp_stanza_t *key_stanza = xmpp_stanza_new(ctx); xmpp_stanza_set_name(key_stanza, "key"); char *rid = g_strdup_printf("%d", key->device_id); xmpp_stanza_set_attribute(key_stanza, "rid", rid); g_free(rid); if (key->prekey) { xmpp_stanza_set_attribute(key_stanza, "prekey", "true"); } gchar *key_raw = g_base64_encode(key->data, key->length); xmpp_stanza_t *key_text = xmpp_stanza_new(ctx); xmpp_stanza_set_text(key_text, key_raw); g_free(key_raw); xmpp_stanza_add_child(key_stanza, key_text); xmpp_stanza_add_child(header, key_stanza); xmpp_stanza_release(key_text); xmpp_stanza_release(key_stanza); } xmpp_stanza_t *iv_stanza = xmpp_stanza_new(ctx); xmpp_stanza_set_name(iv_stanza, "iv"); gchar *iv_raw = g_base64_encode(iv, iv_len); xmpp_stanza_t *iv_text = xmpp_stanza_new(ctx); xmpp_stanza_set_text(iv_text, iv_raw); g_free(iv_raw); xmpp_stanza_add_child(iv_stanza, iv_text); xmpp_stanza_add_child(header, iv_stanza); xmpp_stanza_release(iv_text); xmpp_stanza_release(iv_stanza); xmpp_stanza_add_child(encrypted, header); xmpp_stanza_release(header); xmpp_stanza_t *payload = xmpp_stanza_new(ctx); xmpp_stanza_set_name(payload, "payload"); gchar *ciphertext_raw = g_base64_encode(ciphertext, ciphertext_len); xmpp_stanza_t *payload_text = xmpp_stanza_new(ctx); xmpp_stanza_set_text(payload_text, ciphertext_raw); g_free(ciphertext_raw); xmpp_stanza_add_child(payload, payload_text); xmpp_stanza_add_child(encrypted, payload); xmpp_stanza_release(payload_text); xmpp_stanza_release(payload); xmpp_stanza_add_child(message, encrypted); xmpp_stanza_release(encrypted); xmpp_stanza_t *body = xmpp_stanza_new(ctx); xmpp_stanza_set_name(body, "body"); xmpp_stanza_t *body_text = xmpp_stanza_new(ctx); xmpp_stanza_set_text(body_text, "You received a message encrypted with OMEMO but your client doesn't support OMEMO."); xmpp_stanza_add_child(body, body_text); xmpp_stanza_release(body_text); xmpp_stanza_add_child(message, body); xmpp_stanza_release(body); if (state) { stanza_attach_state(ctx, message, state); } stanza_attach_hints_store(ctx, message); if (request_receipt) { stanza_attach_receipt_request(ctx, message); } if (replace_id) { stanza_attach_correction(ctx, message, replace_id); } _send_message_stanza(message); xmpp_stanza_release(message); return id; } #endif char* message_send_private(const char *const fulljid, const char *const msg, const char *const oob_url) { xmpp_ctx_t * const ctx = connection_get_ctx(); char *id = connection_create_stanza_id(); xmpp_stanza_t *message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, fulljid, id); xmpp_message_set_body(message, msg); if (oob_url) { stanza_attach_x_oob_url(ctx, message, oob_url); } _send_message_stanza(message); xmpp_stanza_release(message); return id; } char* message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url, const char *const replace_id) { xmpp_ctx_t * const ctx = connection_get_ctx(); char *id = connection_create_stanza_id(); xmpp_stanza_t *message = xmpp_message_new(ctx, STANZA_TYPE_GROUPCHAT, roomjid, id); stanza_attach_origin_id(ctx, message, id); xmpp_message_set_body(message, msg); if (oob_url) { stanza_attach_x_oob_url(ctx, message, oob_url); } if (replace_id) { stanza_attach_correction(ctx, message, replace_id); } _send_message_stanza(message); xmpp_stanza_release(message); return id; } void message_send_groupchat_subject(const char *const roomjid, const char *const subject) { xmpp_ctx_t * const ctx = connection_get_ctx(); xmpp_stanza_t *message = stanza_create_room_subject_message(ctx, roomjid, subject); _send_message_stanza(message); xmpp_stanza_release(message); } void message_send_invite(const char *const roomjid, const char *const contact, const char *const reason) { xmpp_ctx_t * const ctx = connection_get_ctx(); xmpp_stanza_t *stanza; muc_member_type_t member_type = muc_member_type(roomjid); if (member_type == MUC_MEMBER_TYPE_PUBLIC) { log_debug("Sending direct invite to %s, for %s", contact, roomjid); char *password = muc_password(roomjid); stanza = stanza_create_invite(ctx, roomjid, contact, reason, password); } else { log_debug("Sending mediated invite to %s, for %s", contact, roomjid); stanza = stanza_create_mediated_invite(ctx, roomjid, contact, reason); } _send_message_stanza(stanza); xmpp_stanza_release(stanza); } void message_send_composing(const char *const jid) { xmpp_ctx_t * const ctx = connection_get_ctx(); xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, jid, STANZA_NAME_COMPOSING); _send_message_stanza(stanza); xmpp_stanza_release(stanza); } void message_send_paused(const char *const jid) { xmpp_ctx_t * const ctx = connection_get_ctx(); xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, jid, STANZA_NAME_PAUSED); _send_message_stanza(stanza); xmpp_stanza_release(stanza); } void message_send_inactive(const char *const jid) { xmpp_ctx_t * const ctx = connection_get_ctx(); xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, jid, STANZA_NAME_INACTIVE); _send_message_stanza(stanza); xmpp_stanza_release(stanza); } void message_send_gone(const char *const jid) { xmpp_ctx_t * const ctx = connection_get_ctx(); xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, jid, STANZA_NAME_GONE); _send_message_stanza(stanza); xmpp_stanza_release(stanza); } static void _handle_error(xmpp_stanza_t *const stanza) { const char *id = xmpp_stanza_get_id(stanza); const char *jid = xmpp_stanza_get_from(stanza); xmpp_stanza_t *error_stanza = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_ERROR); const char *type = NULL; if (error_stanza) { type = xmpp_stanza_get_type(error_stanza); } // stanza_get_error never returns NULL char *err_msg = stanza_get_error_message(stanza); GString *log_msg = g_string_new("message stanza error received"); if (id) { g_string_append(log_msg, " id="); g_string_append(log_msg, id); } if (jid) { g_string_append(log_msg, " from="); g_string_append(log_msg, jid); } if (type) { g_string_append(log_msg, " type="); g_string_append(log_msg, type); } g_string_append(log_msg, " error="); g_string_append(log_msg, err_msg); log_info(log_msg->str); g_string_free(log_msg, TRUE); if (!jid) { ui_handle_error(err_msg); } else if (type && (strcmp(type, "cancel") == 0)) { log_info("Recipient %s not found: %s", jid, err_msg); Jid *jidp = jid_create(jid); chat_session_remove(jidp->barejid); jid_destroy(jidp); } else { ui_handle_recipient_error(jid, err_msg); } free(err_msg); } static void _handle_muc_user(xmpp_stanza_t *const stanza) { xmpp_ctx_t *ctx = connection_get_ctx(); xmpp_stanza_t *xns_muc_user = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_MUC_USER); const char *room = xmpp_stanza_get_from(stanza); if (!room) { log_warning("Message received with no from attribute, ignoring"); return; } // XEP-0045 xmpp_stanza_t *invite = xmpp_stanza_get_child_by_name(xns_muc_user, STANZA_NAME_INVITE); if (!invite) { return; } const char *invitor_jid = xmpp_stanza_get_from(invite); if (!invitor_jid) { log_warning("Chat room invite received with no from attribute"); return; } Jid *jidp = jid_create(invitor_jid); if (!jidp) { return; } char *invitor = jidp->barejid; char *reason = NULL; xmpp_stanza_t *reason_st = xmpp_stanza_get_child_by_name(invite, STANZA_NAME_REASON); if (reason_st) { reason = xmpp_stanza_get_text(reason_st); } char *password = NULL; xmpp_stanza_t *password_st = xmpp_stanza_get_child_by_name(xns_muc_user, STANZA_NAME_PASSWORD); if (password_st) { password = xmpp_stanza_get_text(password_st); } sv_ev_room_invite(INVITE_MEDIATED, invitor, room, reason, password); jid_destroy(jidp); if (reason) { xmpp_free(ctx, reason); } if (password) { xmpp_free(ctx, password); } } static void _handle_conference(xmpp_stanza_t *const stanza) { xmpp_stanza_t *xns_conference = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_CONFERENCE); const char *from = xmpp_stanza_get_from(stanza); if (!from) { log_warning("Message received with no from attribute, ignoring"); return; } Jid *jidp = jid_create(from); if (!jidp) { return; } // XEP-0249 const char *room = xmpp_stanza_get_attribute(xns_conference, STANZA_ATTR_JID); if (!room) { jid_destroy(jidp); return; } const char *reason = xmpp_stanza_get_attribute(xns_conference, STANZA_ATTR_REASON); const char *password = xmpp_stanza_get_attribute(xns_conference, STANZA_ATTR_PASSWORD); sv_ev_room_invite(INVITE_DIRECT, jidp->barejid, room, reason, password); jid_destroy(jidp); } static void _handle_captcha(xmpp_stanza_t *const stanza) { xmpp_ctx_t *ctx = connection_get_ctx(); const char *from = xmpp_stanza_get_from(stanza); if (!from) { log_warning("Message received with no from attribute, ignoring"); return; } // XEP-0158 char *message = xmpp_message_get_body(stanza); if (!message) { return; } sv_ev_room_broadcast(from, message); xmpp_free(ctx, message); } static void _handle_groupchat(xmpp_stanza_t *const stanza) { xmpp_ctx_t *ctx = connection_get_ctx(); const char *id = xmpp_stanza_get_id(stanza); char *originid = NULL; xmpp_stanza_t *origin = stanza_get_child_by_name_and_ns(stanza, STANZA_NAME_ORIGIN_ID, STANZA_NS_STABLE_ID); if (origin) { originid = (char*)xmpp_stanza_get_attribute(origin, STANZA_ATTR_ID); } const char *room_jid = xmpp_stanza_get_from(stanza); Jid *from_jid = jid_create(room_jid); // handle room subject xmpp_stanza_t *subject = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_SUBJECT); if (subject) { char *subject_text; subject_text = xmpp_stanza_get_text(subject); sv_ev_room_subject(from_jid->barejid, from_jid->resourcepart, subject_text); xmpp_free(ctx, subject_text); jid_destroy(from_jid); return; } // handle room broadcasts if (!from_jid->resourcepart) { char *broadcast; broadcast = xmpp_message_get_body(stanza); if (!broadcast) { jid_destroy(from_jid); return; } sv_ev_room_broadcast(room_jid, broadcast); xmpp_free(ctx, broadcast); jid_destroy(from_jid); return; } if (!jid_is_valid_room_form(from_jid)) { log_error("Invalid room JID: %s", from_jid->str); jid_destroy(from_jid); return; } // room not active in profanity if (!muc_active(from_jid->barejid)) { log_error("Message received for inactive chat room: %s", from_jid->str); jid_destroy(from_jid); return; } ProfMessage *message = message_init(); message->from_jid = from_jid; message->type = PROF_MSG_TYPE_MUC; if (id) { message->id = strdup(id); } if (originid) { message->originid = strdup(originid); } xmpp_stanza_t *replace_id_stanza = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_LAST_MESSAGE_CORRECTION); if (replace_id_stanza) { const char *replace_id = xmpp_stanza_get_id(replace_id_stanza); if (replace_id) { message->replace_id = strdup(replace_id); } } message->body = xmpp_message_get_body(stanza); // check omemo encryption #ifdef HAVE_OMEMO message->plain = omemo_receive_message(stanza, &message->trusted); if (message->plain != NULL) { message->enc = PROF_MSG_ENC_OMEMO; } #endif if (!message->plain && !message->body) { log_error("Message received without body for room: %s", from_jid->str); goto out; } else if (!message->plain) { message->plain = strdup(message->body); } // determine if the notifications happened whilst offline (MUC history) message->timestamp = stanza_get_delay_from(stanza, from_jid->barejid); if (message->timestamp == NULL) { // checking the domainpart is a workaround for some prosody versions (gh#1190) message->timestamp = stanza_get_delay_from(stanza, from_jid->domainpart); } bool is_muc_history = FALSE; if (message->timestamp != NULL) { is_muc_history = TRUE; g_date_time_unref(message->timestamp); message->timestamp = NULL; } // we want to display the oldest delay message->timestamp = stanza_get_oldest_delay(stanza); // now this has nothing to do with MUC history // it's just setting the time to the received time so upon displaying we can use this time // for example in win_println_incoming_muc_msg() if (!message->timestamp) { message->timestamp = g_date_time_new_now_local(); } if (is_muc_history) { sv_ev_room_history(message); } else { sv_ev_room_message(message); } out: message_free(message); } void _message_send_receipt(const char *const fulljid, const char *const message_id) { xmpp_ctx_t * const ctx = connection_get_ctx(); char *id = connection_create_stanza_id(); xmpp_stanza_t *message = xmpp_message_new(ctx, NULL, fulljid, id); free(id); xmpp_stanza_t *receipt = xmpp_stanza_new(ctx); xmpp_stanza_set_name(receipt, "received"); xmpp_stanza_set_ns(receipt, STANZA_NS_RECEIPTS); xmpp_stanza_set_id(receipt, message_id); xmpp_stanza_add_child(message, receipt); xmpp_stanza_release(receipt); _send_message_stanza(message); xmpp_stanza_release(message); } static void _handle_receipt_received(xmpp_stanza_t *const stanza) { xmpp_stanza_t *receipt = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_RECEIPTS); const char *name = xmpp_stanza_get_name(receipt); if (g_strcmp0(name, "received") != 0) { return; } const char *id = xmpp_stanza_get_id(receipt); if (!id) { return; } const char *fulljid = xmpp_stanza_get_from(stanza); if (!fulljid) { return; } Jid *jidp = jid_create(fulljid); sv_ev_message_receipt(jidp->barejid, id); jid_destroy(jidp); } void _receipt_request_handler(xmpp_stanza_t *const stanza) { if (!prefs_get_boolean(PREF_RECEIPTS_SEND)) { return; } const char *id = xmpp_stanza_get_id(stanza); if (!id) { return; } xmpp_stanza_t *receipts = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_RECEIPTS); if (!receipts) { return; } const char *receipts_name = xmpp_stanza_get_name(receipts); if (g_strcmp0(receipts_name, "request") != 0) { return; } const gchar *from = xmpp_stanza_get_from(stanza); Jid *jid = jid_create(from); _message_send_receipt(jid->fulljid, id); jid_destroy(jid); } static void _handle_muc_private_message(xmpp_stanza_t *const stanza) { // standard chat message, use jid without resource ProfMessage *message = message_init(); message->type = PROF_MSG_TYPE_MUCPM; const gchar *from = xmpp_stanza_get_from(stanza); message->from_jid = jid_create(from); // message stanza id const char *id = xmpp_stanza_get_id(stanza); if (id) { message->id = strdup(id); } // check omemo encryption #ifdef HAVE_OMEMO message->plain = omemo_receive_message(stanza, &message->trusted); if (message->plain != NULL) { message->enc = PROF_MSG_ENC_OMEMO; } #endif message->timestamp = stanza_get_delay(stanza); message->body = xmpp_message_get_body(stanza); if (!message->plain && !message->body) { log_error("Message received without body from: %s", message->from_jid->str); goto out; } else if (!message->plain) { message->plain = strdup(message->body); } if (message->timestamp) { sv_ev_delayed_private_message(message); } else { message->timestamp = g_date_time_new_now_local(); sv_ev_incoming_private_message(message); } out: message_free(message); } static gboolean _handle_carbons(xmpp_stanza_t *const stanza) { xmpp_stanza_t *carbons = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_CARBONS); if (!carbons) { return FALSE; } const char *name = xmpp_stanza_get_name(carbons); if (!name) { log_error("Unable to retrieve stanza name for Carbon"); return TRUE; } if (g_strcmp0(name, "private") == 0) { log_info("Carbon received with private element."); return FALSE; } if ((g_strcmp0(name, "received") != 0) && (g_strcmp0(name, "sent") != 0)) { log_warning("Carbon received with unrecognised stanza name: %s", name); return TRUE; } xmpp_stanza_t *forwarded = xmpp_stanza_get_child_by_ns(carbons, STANZA_NS_FORWARD); if (!forwarded) { log_warning("Carbon received with no forwarded element"); return TRUE; } xmpp_stanza_t *message_stanza = xmpp_stanza_get_child_by_name(forwarded, STANZA_NAME_MESSAGE); if (!message_stanza) { log_warning("Carbon received with no message element"); return TRUE; } char *mybarejid = connection_get_barejid(); const char *const stanza_from = xmpp_stanza_get_from(stanza); if (g_strcmp0(mybarejid, stanza_from) != 0) { log_warning("Invalid carbon received, from: %s", stanza_from); return TRUE; } ProfMessage *message = message_init(); message->type = PROF_MSG_TYPE_CHAT; // check whether message was a MUC PM xmpp_stanza_t *mucuser = xmpp_stanza_get_child_by_ns(message_stanza, STANZA_NS_MUC_USER); if (mucuser) { message->type = PROF_MSG_TYPE_MUCPM; } // id const char *id = xmpp_stanza_get_id(message_stanza); if (id) { message->id = strdup(id); } // replace id xmpp_stanza_t *replace_id_stanza = xmpp_stanza_get_child_by_ns(message_stanza, STANZA_NS_LAST_MESSAGE_CORRECTION); if (replace_id_stanza) { const char *replace_id = xmpp_stanza_get_id(replace_id_stanza); if (replace_id) { message->replace_id = strdup(replace_id); } } // check omemo encryption #ifdef HAVE_OMEMO message->plain = omemo_receive_message(message_stanza, &message->trusted); if (message->plain != NULL) { message->enc = PROF_MSG_ENC_OMEMO; } #endif const gchar *to = xmpp_stanza_get_to(message_stanza); const gchar *from = xmpp_stanza_get_from(message_stanza); // happens when receive a carbon of a self sent message // really? maybe some servers do this, but it's not required. if (!to) to = from; Jid *jid_from = jid_create(from); Jid *jid_to = jid_create(to); message->body = xmpp_message_get_body(message_stanza); if (!message->plain && !message->body) { log_error("Message received without body from: %s", jid_from->fulljid); jid_destroy(jid_from); jid_destroy(jid_to); goto out; } else if (!message->plain) { message->plain = strdup(message->body); } // check for pgp encrypted message xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(message_stanza, STANZA_NS_ENCRYPTED); if (x) { message->encrypted = xmpp_stanza_get_text(x); } //TODO: maybe also add is_carbon maybe even an enum with outgoing/incoming //could be that then we can have sv_ev_carbon no incoming/outgoing if (message->plain || message->encrypted || message->body) { // if we are the recipient, treat as standard incoming message if (g_strcmp0(mybarejid, jid_to->barejid) == 0) { message->from_jid = jid_from; message->to_jid = jid_to; sv_ev_incoming_carbon(message); // else treat as a sent message } else { message->from_jid = jid_from; message->to_jid = jid_to; sv_ev_outgoing_carbon(message); } } out: message_free(message); free(mybarejid); return TRUE; } static void _handle_chat(xmpp_stanza_t *const stanza, gboolean is_mam) { // ignore if type not chat or absent const char *type = xmpp_stanza_get_type(stanza); if (!(g_strcmp0(type, "chat") == 0 || type == NULL)) { return; } // check if carbon message gboolean res = _handle_carbons(stanza); if (res) { return; } // ignore handled namespaces xmpp_stanza_t *conf = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_CONFERENCE); xmpp_stanza_t *captcha = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_CAPTCHA); if (conf || captcha) { return; } // some clients send the mucuser namespace with private messages // if the namespace exists, and the stanza contains a body element, assume its a private message // otherwise exit the handler xmpp_stanza_t *mucuser = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_MUC_USER); xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BODY); if (mucuser && body == NULL) { return; } const gchar *from = xmpp_stanza_get_from(stanza); if (!from) { return; } Jid *jid = jid_create(from); // private message from chat room use full jid (room/nick) if (muc_active(jid->barejid)) { _handle_muc_private_message(stanza); jid_destroy(jid); return; } // standard chat message, use jid without resource ProfMessage *message = message_init(); message->type = PROF_MSG_TYPE_CHAT; message->from_jid = jid; const gchar *to_text = xmpp_stanza_get_to(stanza); if (to_text) { message->to_jid = jid_create(to_text); } message->is_mam = is_mam; // message stanza id const char *id = xmpp_stanza_get_id(stanza); if (id) { message->id = strdup(id); } xmpp_stanza_t *replace_id_stanza = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_LAST_MESSAGE_CORRECTION); if (replace_id_stanza) { const char *replace_id = xmpp_stanza_get_id(replace_id_stanza); if (replace_id) { message->replace_id = strdup(replace_id); } } if (mucuser) { message->type = PROF_MSG_TYPE_MUCPM; } message->timestamp = stanza_get_delay(stanza); if (!message->timestamp) { message->timestamp = g_date_time_new_now_local(); } if (body) { message->body = xmpp_stanza_get_text(body); } // check omemo encryption #ifdef HAVE_OMEMO message->plain = omemo_receive_message(stanza, &message->trusted); if (message->plain != NULL) { message->enc = PROF_MSG_ENC_OMEMO; } #endif xmpp_stanza_t *encrypted = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_ENCRYPTED); if (encrypted) { message->encrypted = xmpp_stanza_get_text(encrypted); } if (message->plain || message->body || message->encrypted) { sv_ev_incoming_message(message); _receipt_request_handler(stanza); } // handle chat sessions and states if (jid->resourcepart) { gboolean gone = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_GONE) != NULL; gboolean typing = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_COMPOSING) != NULL; gboolean paused = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_PAUSED) != NULL; gboolean inactive = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_INACTIVE) != NULL; if (gone) { sv_ev_gone(jid->barejid, jid->resourcepart); } else if (typing) { sv_ev_typing(jid->barejid, jid->resourcepart); } else if (paused) { sv_ev_paused(jid->barejid, jid->resourcepart); } else if (inactive) { sv_ev_inactive(jid->barejid, jid->resourcepart); } else if (stanza_contains_chat_state(stanza)) { sv_ev_activity(jid->barejid, jid->resourcepart, TRUE); } else { sv_ev_activity(jid->barejid, jid->resourcepart, FALSE); } } message_free(message); } static gboolean _handle_mam(xmpp_stanza_t *const stanza) { xmpp_stanza_t *result = stanza_get_child_by_name_and_ns(stanza, "result", STANZA_NS_MAM2); if (!result) { return FALSE; } xmpp_stanza_t *forwarded = xmpp_stanza_get_child_by_ns(result, STANZA_NS_FORWARD); if (!forwarded) { log_warning("MAM received with no forwarded element"); return FALSE; } xmpp_stanza_t *message_stanza = xmpp_stanza_get_child_by_ns(forwarded, "jabber:client"); _handle_chat(message_stanza, TRUE); return TRUE; } static void _send_message_stanza(xmpp_stanza_t *const stanza) { char *text; size_t text_size; xmpp_stanza_to_text(stanza, &text, &text_size); xmpp_conn_t *conn = connection_get_conn(); char *plugin_text = plugins_on_message_stanza_send(text); if (plugin_text) { xmpp_send_raw_string(conn, "%s", plugin_text); free(plugin_text); } else { xmpp_send_raw_string(conn, "%s", text); } xmpp_free(connection_get_ctx(), text); } /* ckeckOID = true: check origin-id * checkOID = false: check regular id */ bool message_is_sent_by_us(const ProfMessage *const message, bool checkOID) { bool ret = FALSE; // we check the for this we calculate a hash into it so we can detect // whether this client sent it. See connection_create_stanza_id() if (message) { char *tmp_id = NULL; if (checkOID && message->originid != NULL) { tmp_id = message->originid; } else if (!checkOID && message->id != NULL) { tmp_id = message->id; } if (tmp_id != NULL) { gsize tmp_len; char *tmp = (char*)g_base64_decode(tmp_id, &tmp_len); // our client sents at least 36 (uuid) + identifier if (tmp_len > 36) { char *uuid = g_strndup(tmp, 36); const char *prof_identifier = connection_get_profanity_identifier(); gchar *hmac = g_compute_hmac_for_string(G_CHECKSUM_SHA256, (guchar*)prof_identifier, strlen(prof_identifier), uuid, strlen(uuid)); if (g_strcmp0(&tmp[36], hmac) == 0) { ret = TRUE; } g_free(uuid); g_free(hmac); } free(tmp); } } return ret; }