You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

468 lines
14 KiB
C++

// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <strophe.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <fmt/core.h>
#include <libxml/xmlwriter.h>
#include <weechat/weechat-plugin.h>
#include "plugin.hh"
#include "xmpp/stanza.hh"
#include "config.hh"
#include "input.hh"
#include "omemo.hh"
#include "account.hh"
#include "connection.hh"
#include "user.hh"
#include "channel.hh"
#include "buffer.hh"
std::unordered_map<std::string, weechat::account> weechat::accounts;
void weechat::log_emit(void *const userdata, const xmpp_log_level_t level,
const char *const area, const char *const msg)
{
auto account = static_cast<weechat::account*>(userdata);
static const char *log_level_name[4] = {"debug", "info", "warn", "error"};
const char *tags = level > XMPP_LEVEL_DEBUG ? "no_log" : NULL;
char *xml;
if ((level == XMPP_LEVEL_DEBUG) && ((xml = const_cast<char*>(strchr(msg, '<'))) != NULL))
{
FILE *nullfd = fopen("/dev/null", "w+");
xmlGenericErrorContext = nullfd;
const char *header = strndup(msg, xml - msg);
xmlDocPtr doc = xmlRecoverMemory(xml, strlen(xml));
if (doc == NULL) {
weechat_printf(
account ? account->buffer : NULL,
"xml: Error parsing the xml document");
fclose(nullfd);
return;
}
xmlNodePtr root = xmlDocGetRootElement(doc);
std::string tag = root ? (const char*)root->name : "";
const char *colour = weechat_color("red");
if (tag == "message")
{
colour = weechat_color("yellow");
}
else if (tag == "presence")
{
colour = weechat_color("green");
}
else if (tag == "iq")
{
colour = weechat_color("blue");
}
xmlChar *buf = (xmlChar*)malloc(strlen(xml) * 2);
if (buf == NULL) {
weechat_printf(
account ? account->buffer : NULL,
"xml: Error allocating the xml buffer");
fclose(nullfd);
return;
}
int size = -1;
xmlDocDumpFormatMemory(doc, &buf, &size, 1);
if (size <= 0) {
weechat_printf(
account ? account->buffer : NULL,
"xml: Error formatting the xml document");
fclose(nullfd);
return;
}
char **lines = weechat_string_split((char*)buf, "\r\n", NULL,
0, 0, &size);
if (lines[size-1][0] == 0)
lines[--size] = 0;
weechat_printf_date_tags(
account ? account->buffer : NULL,
0, tags,
_("%s%s (%s): %s"),
weechat_prefix("network"), area,
log_level_name[level], header);
for (int i = 1; i < size; i++)
weechat_printf_date_tags(
account ? account->buffer : NULL,
0, tags,
_("%s%s"), colour, lines[i]);
weechat_string_free_split(lines);
fclose(nullfd);
}
else
{
weechat_printf_date_tags(
account ? account->buffer : NULL,
0, tags,
_("%s%s (%s): %s"),
weechat_prefix("network"), area,
log_level_name[level], msg);
}
}
bool weechat::account::search(weechat::account* &out,
const std::string name, bool casesensitive)
{
if (name.empty())
return false;
if (casesensitive)
{
for (auto& account : weechat::accounts)
{
if (weechat_strcasecmp(account.second.name.data(), name.data()) == 0)
{
out = &account.second;
return true;
}
}
}
else if (auto account = accounts.find(name); account != accounts.end())
{
out = &account->second;
return true;
}
(void) out;
return false;
}
bool weechat::account::search_device(weechat::account::device* out, std::uint32_t id)
{
if (id == 0)
return false;
if (auto device = devices.find(id); device != devices.end())
{
out = &device->second;
return true;
}
(void) out;
return false;
}
void weechat::account::add_device(weechat::account::device *device)
{
if (!devices.contains(device->id))
{
devices[device->id].id = device->id;
devices[device->id].name = device->name;
devices[device->id].label = device->label;
}
}
void weechat::account::device_free_all()
{
devices.clear();
}
xmpp_stanza_t *weechat::account::get_devicelist()
{
int i = 0;
account::device device;
device.id = omemo.device_id;
device.name = fmt::format("%u", device.id);
device.label = "weechat";
auto children = new xmpp_stanza_t*[128];
children[i++] = stanza__iq_pubsub_publish_item_list_device(
context, NULL, with_noop(device.name.data()), NULL);
for (auto& device : devices)
{
if (device.first != omemo.device_id)
children[i++] = stanza__iq_pubsub_publish_item_list_device(
context, NULL, with_noop(device.second.name.data()), NULL);
}
children[i] = NULL;
const char *node = "eu.siacs.conversations.axolotl";
children[0] = stanza__iq_pubsub_publish_item_list(
context, NULL, children, with_noop(node));
children[1] = NULL;
children[0] = stanza__iq_pubsub_publish_item(
context, NULL, children, with_noop("current"));
node = "eu.siacs.conversations.axolotl.devicelist";
children[0] = stanza__iq_pubsub_publish(context, NULL, children, with_noop(node));
const char *ns = "http://jabber.org/protocol/pubsub";
children[0] = stanza__iq_pubsub(context, NULL, children, with_noop(ns));
xmpp_stanza_t * parent = stanza__iq(context, NULL,
children, NULL, strdup("announce1"),
NULL, NULL, strdup("set"));
delete[] children;
return parent;
}
void weechat::account::add_mam_query(const std::string id, const std::string with,
std::optional<time_t> start, std::optional<time_t> end)
{
if (!mam_queries.contains(id))
{
mam_queries[id].id = id;
mam_queries[id].with = with;
mam_queries[id].start = start;
mam_queries[id].end = end;
}
}
bool weechat::account::mam_query_search(weechat::account::mam_query* out,
const std::string id)
{
if (id.empty())
return false;
if (auto mam_query = mam_queries.find(id); mam_query != mam_queries.end())
{
out = &mam_query->second;
return true;
}
(void) out;
return false;
}
void weechat::account::mam_query_remove(const std::string id)
{
mam_queries.erase(id);
}
void weechat::account::mam_query_free_all()
{
mam_queries.clear();
}
xmpp_log_t make_logger(void *userdata)
{
xmpp_log_t logger = { nullptr };
logger.handler = &weechat::log_emit;
logger.userdata = userdata;
return logger;
}
xmpp_mem_t make_memory(void *userdata)
{
xmpp_mem_t memory = { nullptr };
memory.alloc = [](const size_t size, void *const) {
return calloc(1, size);
};
memory.free = [](void *ptr, void *const) {
free(ptr);
};
memory.realloc = [](void *ptr, const size_t size, void *const) {
return realloc(ptr, size);
};
memory.userdata = userdata;
return memory;
}
weechat::account::account(config_file& config_file, const std::string name)
: name(name), memory(make_memory(this)), logger(make_logger(this))
, context(&memory, &logger), connection(*this, context)
, config_account(config_file, config_file.configuration.section_account, name.data())
{
if (account* result = nullptr; account::search(result, name))
throw std::invalid_argument("account already exists");
this->jid(config_file.configuration.account_default.option_jid.string().data());
this->password(config_file.configuration.account_default.option_password.string().data());
this->tls(config_file.configuration.account_default.option_tls.string().data());
this->nickname(config_file.configuration.account_default.option_nickname.string().data());
this->autoconnect(config_file.configuration.account_default.option_autoconnect.string().data());
this->resource(config_file.configuration.account_default.option_resource.string().data());
this->status(config_file.configuration.account_default.option_status.string().data());
this->pgp_path(config_file.configuration.account_default.option_pgp_path.string().data());
this->pgp_keyid(config_file.configuration.account_default.option_pgp_keyid.string().data());
}
weechat::account::~account()
{
/*
* close account buffer (and all channels/privates)
* (only if we are not in a /upgrade, because during upgrade we want to
* keep connections and closing account buffer would disconnect from account)
*/
if (buffer)
weechat_buffer_close(buffer);
}
void weechat::account::disconnect(int reconnect)
{
if (is_connected)
{
/*
* remove all nicks and write disconnection message on each
* channel/private buffer
*/
//user::free_all(this); // TOFIX
weechat_nicklist_remove_all(buffer);
for (auto& ptr_channel : channels)
{
weechat_nicklist_remove_all(ptr_channel.second.buffer);
weechat_printf(
ptr_channel.second.buffer,
_("%s%s: disconnected from account"),
weechat_prefix("network"), WEECHAT_XMPP_PLUGIN_NAME);
}
/* remove away status on account buffer */
//weechat_buffer_set(buffer, "localvar_del_away", "");
}
reset();
if (buffer)
{
weechat_printf(
buffer,
_("%s%s: disconnected from account"),
weechat_prefix ("network"), WEECHAT_XMPP_PLUGIN_NAME);
}
if (reconnect)
{
if (current_retry++ == 0)
{
reconnect_delay = 5;
reconnect_start = time(NULL) + reconnect_delay;
}
current_retry %= 5;
}
else
{
current_retry = 0;
reconnect_delay = 0;
reconnect_start = 0;
}
/*
lag = 0;
lag_displayed = -1;
lag_check_time.tv_sec = 0;
lag_check_time.tv_usec = 0;
lag_next_check = time(NULL) +
weechat_config_integer(xmpp_config_network_lag_check);
lag_last_refresh = 0;
account__set_lag(account);
*/ // lag based on xmpp ping
disconnected = !reconnect;
/* send signal "account_disconnected" with account name */
(void) weechat_hook_signal_send("xmpp_account_disconnected",
WEECHAT_HOOK_SIGNAL_STRING, name.data());
}
void weechat::account::disconnect_all()
{
for (auto& account : accounts)
{
account.second.disconnect(0);
}
}
struct t_gui_buffer *weechat::account::create_buffer()
{
buffer = weechat_buffer_new(fmt::format("account.{}", name).data(),
&input__data_cb, NULL, NULL,
&buffer__close_cb, NULL, NULL);
if (!buffer)
return NULL;
weechat_printf(buffer, "xmpp: %s", name.data());
if (!weechat_buffer_get_integer(buffer, "short_name_is_set"))
weechat_buffer_set(buffer, "short_name", name.data());
weechat_buffer_set(buffer, "localvar_set_type", "server");
weechat_buffer_set(buffer, "localvar_set_account", name.data());
weechat_buffer_set(buffer, "localvar_set_charset_modifier",
fmt::format("account.{}", name).data());
weechat_buffer_set(buffer, "title", name.data());
weechat_buffer_set(buffer, "nicklist", "1");
weechat_buffer_set(buffer, "nicklist_display_groups", "0");
weechat_buffer_set_pointer(buffer, "nicklist_callback",
(void*)&buffer__nickcmp_cb);
weechat_buffer_set_pointer(buffer, "nicklist_callback_pointer",
this);
return buffer;
}
void weechat::account::reset()
{
if (connection)
{
if (xmpp_conn_is_connected(connection))
xmpp_disconnect(connection);
}
is_connected = 0;
}
int weechat::account::connect()
{
if (!buffer)
{
if (!create_buffer())
return 0;
weechat_buffer_set(buffer, "display", "auto");
}
reset();
is_connected = connection.connect(std::string(jid()), std::string(password()), tls());
(void) weechat_hook_signal_send("xmpp_account_connecting",
WEECHAT_HOOK_SIGNAL_STRING, name.data());
return is_connected;
}
int weechat::account::timer_cb(const void *pointer, void *data, int remaining_calls)
{
(void) pointer;
(void) data;
(void) remaining_calls;
//try
{
if (accounts.empty()) return WEECHAT_RC_ERROR;
for (auto& ptr_account : accounts)
{
if (ptr_account.second.is_connected
&& (xmpp_conn_is_connecting(ptr_account.second.connection)
|| xmpp_conn_is_connected(ptr_account.second.connection)))
ptr_account.second.connection.process(ptr_account.second.context, 10);
else if (ptr_account.second.disconnected);
else if (ptr_account.second.reconnect_start > 0
&& ptr_account.second.reconnect_start < time(NULL))
{
ptr_account.second.connect();
}
}
return WEECHAT_RC_OK;
}
//catch (const std::exception& ex)
//{
// auto what = ex.what();
// __asm__("int3");
// return WEECHAT_RC_ERROR;
//}
}