summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bridge/bridge.cpp112
-rw-r--r--src/bridge/bridge.hpp13
-rw-r--r--src/bridge/history_limit.hpp2
-rw-r--r--src/database/count_query.hpp4
-rw-r--r--src/database/database.cpp5
-rw-r--r--src/database/database.hpp9
-rw-r--r--src/database/postgresql_statement.hpp10
-rw-r--r--src/database/query.cpp5
-rw-r--r--src/database/query.hpp9
-rw-r--r--src/database/row.hpp2
-rw-r--r--src/database/select_query.hpp2
-rw-r--r--src/irc/irc_client.cpp105
-rw-r--r--src/irc/irc_client.hpp26
-rw-r--r--src/irc/irc_message.hpp4
-rw-r--r--src/main.cpp98
-rw-r--r--src/network/credentials_manager.cpp3
-rw-r--r--src/network/credentials_manager.hpp3
-rw-r--r--src/network/resolver.cpp6
-rw-r--r--src/network/tcp_client_socket_handler.cpp9
-rw-r--r--src/network/tcp_socket_handler.cpp21
-rw-r--r--src/network/tls_policy.cpp2
-rw-r--r--src/network/tls_policy.hpp1
-rw-r--r--src/utils/dirname.cpp2
-rw-r--r--src/utils/dirname.hpp4
-rw-r--r--src/utils/encoding.cpp26
-rw-r--r--src/utils/get_first_non_empty.cpp5
-rw-r--r--src/utils/get_first_non_empty.hpp7
-rw-r--r--src/utils/optional_bool.hpp2
-rw-r--r--src/utils/string.cpp4
-rw-r--r--src/utils/time.cpp8
-rw-r--r--src/utils/tokens_bucket.hpp60
-rw-r--r--src/xmpp/adhoc_commands_handler.cpp5
-rw-r--r--src/xmpp/biboumi_adhoc_commands.cpp104
-rw-r--r--src/xmpp/biboumi_component.cpp63
-rw-r--r--src/xmpp/biboumi_component.hpp3
-rw-r--r--src/xmpp/jid.cpp2
-rw-r--r--src/xmpp/xmpp_component.cpp16
-rw-r--r--src/xmpp/xmpp_component.hpp1
-rw-r--r--src/xmpp/xmpp_parser.cpp2
39 files changed, 526 insertions, 239 deletions
diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp
index 7a0157a..71c0ea4 100644
--- a/src/bridge/bridge.cpp
+++ b/src/bridge/bridge.cpp
@@ -63,7 +63,8 @@ void Bridge::shutdown(const std::string& exit_message)
{
for (auto& pair: this->irc_clients)
{
- pair.second->send_quit_command(exit_message);
+ std::unique_ptr<IrcClient>& irc = pair.second;
+ irc->send_quit_command(exit_message);
}
}
@@ -133,11 +134,11 @@ IrcClient* Bridge::make_irc_client(const std::string& hostname, const std::strin
realname = this->get_bare_jid();
}
this->irc_clients.emplace(hostname,
- std::make_shared<IrcClient>(this->poller, hostname,
+ std::make_unique<IrcClient>(this->poller, hostname,
nickname, username,
realname, jid.domain,
*this));
- std::shared_ptr<IrcClient> irc = this->irc_clients.at(hostname);
+ std::unique_ptr<IrcClient>& irc = this->irc_clients.at(hostname);
return irc.get();
}
}
@@ -223,6 +224,27 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body, std::
bool first = true;
for (const std::string& line: lines)
{
+ std::string uuid;
+#ifdef USE_DATABASE
+ const auto xmpp_body = this->make_xmpp_body(line);
+ if (this->record_history)
+ uuid = Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
+ std::get<0>(xmpp_body), irc->get_own_nick());
+#endif
+ if (!first || id.empty())
+ id = utils::gen_uuid();
+
+ MessageCallback mirror_to_all_resources = [this, iid, uuid, id](const IrcClient* irc, const IrcMessage& message) {
+ std::string line = message.arguments[1];
+ // “temporary” workaround for \01ACTION…\01 -> /me messages
+ if ((line.size() > strlen("\01ACTION\01")) &&
+ (line.substr(0, 7) == "\01ACTION") && line[line.size() - 1] == '\01')
+ line = "/me " + line.substr(8, line.size() - 9);
+ for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
+ this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line),
+ this->user_jid + "/" + resource, uuid, id);
+ };
+
if (line.substr(0, 5) == "/mode")
{
std::vector<std::string> args = utils::split(line.substr(5), ' ', false);
@@ -231,22 +253,11 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body, std::
// XMPP user, that’s not a textual message.
}
else if (line.substr(0, 4) == "/me ")
- irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01");
+ irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01",
+ std::move(mirror_to_all_resources));
else
- irc->send_channel_message(iid.get_local(), line);
+ irc->send_channel_message(iid.get_local(), line, std::move(mirror_to_all_resources));
- std::string uuid;
-#ifdef USE_DATABASE
- const auto xmpp_body = this->make_xmpp_body(line);
- if (this->record_history)
- uuid = Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
- std::get<0>(xmpp_body), irc->get_own_nick());
-#endif
- if (!first || id.empty())
- id = utils::gen_uuid();
- for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
- this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line),
- this->user_jid + "/" + resource, uuid, id);
first = false;
}
}
@@ -449,9 +460,8 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con
true, true, resource, irc);
this->remove_resource_from_chan(key, resource);
}
- if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0)
- this->remove_resource_from_server(iid.get_server(), resource);
-
+ if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0)
+ this->remove_resource_from_server(iid.get_server(), resource);
}
void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick, const std::string& requesting_resource)
@@ -737,10 +747,27 @@ void Bridge::send_irc_participant_ping_request(const Iid& iid, const std::string
IrcChannel* chan = irc->get_channel(iid.get_local());
if (!chan->joined || !this->is_resource_in_chan(iid.to_tuple(), from.resource))
{
- this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "not-allowed",
+ this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "not-acceptable",
"", true);
return;
}
+ if (chan->get_self()->nick == nick)
+ {
+ // XEP-0410 self-ping optimisation: always reply without going the full
+ // round-trip through IRC and possibly another XMPP client. See the XEP
+ // for details.
+ Jid iq_from(from_jid);
+ iq_from.local = std::to_string(iid);
+ iq_from.resource = nick;
+
+ Stanza iq("iq");
+ iq["from"] = iq_from.full();
+ iq["to"] = to_jid;
+ iq["id"] = iq_id;
+ iq["type"] = "result";
+ this->xmpp.send_stanza(iq);
+ return;
+ }
if (chan->get_self()->nick != nick && !chan->find_user(nick))
{
this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found",
@@ -816,7 +843,7 @@ void Bridge::send_irc_version_request(const std::string& irc_hostname, const std
this->add_waiting_irc(std::move(cb));
}
-void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc)
+void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc, const bool log)
{
const auto encoding = in_encoding_for(*this, iid);
std::string uuid{};
@@ -824,9 +851,11 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st
{
#ifdef USE_DATABASE
const auto xmpp_body = this->make_xmpp_body(body, encoding);
- if (!nick.empty() && this->record_history)
+ if (log && this->record_history)
uuid = Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
std::get<0>(xmpp_body), nick);
+#else
+ (void)log;
#endif
for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
{
@@ -891,9 +920,7 @@ void Bridge::send_muc_leave(const Iid& iid, const IrcUser& user,
for (const auto& r: resources_in_chan)
if (this->number_of_channels_the_resource_is_in(iid.get_server(), r) == 0)
this->remove_resource_from_server(iid.get_server(), r);
-
}
-
}
IrcClient* irc = this->find_irc_client(iid.get_server());
if (self && irc && irc->number_of_joined_channels() == 0)
@@ -967,8 +994,18 @@ void Bridge::send_user_join(const std::string& hostname, const std::string& chan
std::string encoded_chan_name(chan_name);
xep0106::encode(encoded_chan_name);
- this->xmpp.send_user_join(encoded_chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host,
- affiliation, role, this->user_jid + "/" + resource, self);
+ std::string encoded_nick_name(user->nick);
+ xep0106::encode(encoded_nick_name);
+
+ std::string full_jid =
+ encoded_nick_name + utils::empty_if_fixed_server("%" + hostname)
+ + "@" + this->xmpp.get_served_hostname();
+ if (!user->host.empty())
+ full_jid += "/" + user->host;
+
+ this->xmpp.send_user_join(encoded_chan_name + utils::empty_if_fixed_server("%" + hostname),
+ user->nick, full_jid, affiliation, role,
+ this->user_jid + "/" + resource, self);
}
void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic,
@@ -1001,11 +1038,13 @@ void Bridge::send_room_history(const std::string& hostname, const std::string& c
void Bridge::send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource, const HistoryLimit& history_limit)
{
#ifdef USE_DATABASE
- const auto coptions = Database::get_irc_channel_options_with_server_and_global_default(this->user_jid, hostname, chan_name);
- auto limit = coptions.col<Database::MaxHistoryLength>();
+ const auto goptions = Database::get_global_options(this->user_jid);
+ auto limit = goptions.col<Database::MaxHistoryLength>();
+ if (limit < 0)
+ limit = 20;
if (history_limit.stanzas >= 0 && history_limit.stanzas < limit)
limit = history_limit.stanzas;
- const auto result = Database::get_muc_logs(this->user_jid, chan_name, hostname, limit, history_limit.since, {}, Id::unset_value, Database::Paging::last);
+ const auto result = Database::get_muc_logs(this->user_jid, chan_name, hostname, static_cast<std::size_t>(limit), history_limit.since, {}, Id::unset_value, Database::Paging::last);
const auto& lines = std::get<1>(result);
chan_name.append(utils::empty_if_fixed_server("%" + hostname));
for (const auto& line: lines)
@@ -1142,12 +1181,12 @@ void Bridge::trigger_on_irc_message(const std::string& irc_hostname, const IrcMe
}
}
-std::unordered_map<std::string, std::shared_ptr<IrcClient>>& Bridge::get_irc_clients()
+std::unordered_map<std::string, std::unique_ptr<IrcClient>>& Bridge::get_irc_clients()
{
return this->irc_clients;
}
-const std::unordered_map<std::string, std::shared_ptr<IrcClient>>& Bridge::get_irc_clients() const
+const std::unordered_map<std::string, std::unique_ptr<IrcClient>>& Bridge::get_irc_clients() const
{
return this->irc_clients;
}
@@ -1214,15 +1253,6 @@ void Bridge::remove_resource_from_server(const Bridge::IrcHostname& irc_hostname
}
}
-bool Bridge::is_resource_in_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource) const
-{
- auto it = this->resources_in_server.find(irc_hostname);
- if (it != this->resources_in_server.end())
- if (it->second.count(resource) == 1)
- return true;
- return false;
-}
-
std::size_t Bridge::number_of_resources_in_chan(const Bridge::ChannelKey& channel) const
{
auto it = this->resources_in_chan.find(channel);
diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp
index 8e7d9d7..fa2a31f 100644
--- a/src/bridge/bridge.hpp
+++ b/src/bridge/bridge.hpp
@@ -164,9 +164,9 @@ public:
void send_room_history(const std::string& hostname, const std::string& chan_name, const HistoryLimit& history_limit);
void send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource, const HistoryLimit& history_limit);
/**
- * Send a MUC message from some participant
+ * Send a message from a MUC participant or a direct message
*/
- void send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc);
+ void send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc, const bool log=true);
/**
* Send a presence of type error, from a room.
*/
@@ -241,8 +241,8 @@ public:
* iq_responder_callback_t and remove the callback from the list.
*/
void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message);
- std::unordered_map<std::string, std::shared_ptr<IrcClient>>& get_irc_clients();
- const std::unordered_map<std::string, std::shared_ptr<IrcClient>>& get_irc_clients() const;
+ std::unordered_map<std::string, std::unique_ptr<IrcClient>>& get_irc_clients();
+ const std::unordered_map<std::string, std::unique_ptr<IrcClient>>& get_irc_clients() const;
std::set<char> get_chantypes(const std::string& hostname) const;
#ifdef USE_DATABASE
void set_record_history(const bool val);
@@ -275,7 +275,7 @@ private:
* One IrcClient for each IRC server we need to be connected to.
* The pointer is shared by the bridge and the poller.
*/
- std::unordered_map<std::string, std::shared_ptr<IrcClient>> irc_clients;
+ std::unordered_map<std::string, std::unique_ptr<IrcClient>> irc_clients;
/**
* To communicate back with the XMPP component
*/
@@ -316,13 +316,14 @@ private:
*/
void add_resource_to_chan(const ChannelKey& channel, const std::string& resource);
void remove_resource_from_chan(const ChannelKey& channel, const std::string& resource);
+public:
bool is_resource_in_chan(const ChannelKey& channel, const std::string& resource) const;
+private:
void remove_all_resources_from_chan(const ChannelKey& channel);
std::size_t number_of_resources_in_chan(const ChannelKey& channel) const;
void add_resource_to_server(const IrcHostname& irc_hostname, const std::string& resource);
void remove_resource_from_server(const IrcHostname& irc_hostname, const std::string& resource);
- bool is_resource_in_server(const IrcHostname& irc_hostname, const std::string& resource) const;
size_t number_of_channels_the_resource_is_in(const std::string& irc_hostname, const std::string& resource) const;
/**
diff --git a/src/bridge/history_limit.hpp b/src/bridge/history_limit.hpp
index 9c75256..93e36e1 100644
--- a/src/bridge/history_limit.hpp
+++ b/src/bridge/history_limit.hpp
@@ -1,5 +1,7 @@
#pragma once
+#include <string>
+
// Default values means no limit
struct HistoryLimit
{
diff --git a/src/database/count_query.hpp b/src/database/count_query.hpp
index 118ce44..3990eb9 100644
--- a/src/database/count_query.hpp
+++ b/src/database/count_query.hpp
@@ -8,10 +8,10 @@
struct CountQuery: public Query
{
- CountQuery(std::string name):
+ CountQuery(const std::string& name):
Query("SELECT count(*) FROM ")
{
- this->body += std::move(name);
+ this->body += name;
}
int64_t execute(DatabaseEngine& db)
diff --git a/src/database/database.cpp b/src/database/database.cpp
index 6e08ee1..861abcb 100644
--- a/src/database/database.cpp
+++ b/src/database/database.cpp
@@ -162,10 +162,6 @@ Database::IrcChannelOptions Database::get_irc_channel_options_with_server_and_gl
coptions.col<EncodingOut>() = get_first_non_empty(coptions.col<EncodingOut>(),
soptions.col<EncodingOut>());
- coptions.col<MaxHistoryLength>() = get_first_non_empty(coptions.col<MaxHistoryLength>(),
- soptions.col<MaxHistoryLength>(),
- goptions.col<MaxHistoryLength>());
-
return coptions;
}
@@ -338,7 +334,6 @@ Transaction::Transaction()
log_error("Failed to create SQL transaction: ", std::get<std::string>(result));
else
this->success = true;
-
}
Transaction::~Transaction()
diff --git a/src/database/database.hpp b/src/database/database.hpp
index 3e25b30..a53f87b 100644
--- a/src/database/database.hpp
+++ b/src/database/database.hpp
@@ -63,8 +63,8 @@ class Database
struct EncodingIn: Column<std::string> { static constexpr auto name = "encodingin_"; };
- struct MaxHistoryLength: Column<int> { static constexpr auto name = "maxhistorylength_";
- MaxHistoryLength(): Column<int>(20) {} };
+ struct MaxHistoryLength: Column<std::int64_t> { static constexpr auto name = "maxhistorylength_";
+ MaxHistoryLength(): Column<std::int64_t>(20) {} };
struct RecordHistory: Column<bool> { static constexpr auto name = "recordhistory_";
RecordHistory(): Column<bool>(true) {}};
@@ -86,13 +86,16 @@ class Database
struct Address: Column<std::string> { static constexpr auto name = "address_"; };
+ struct ThrottleLimit: Column<std::int64_t> { static constexpr auto name = "throttlelimit_";
+ ThrottleLimit(): Column<std::int64_t>(10) {} };
+
using MucLogLineTable = Table<Id, Uuid, Owner, IrcChanName, IrcServerName, Date, Body, Nick>;
using MucLogLine = MucLogLineTable::RowType;
using GlobalOptionsTable = Table<Id, Owner, MaxHistoryLength, RecordHistory, GlobalPersistent>;
using GlobalOptions = GlobalOptionsTable::RowType;
- using IrcServerOptionsTable = Table<Id, Owner, Server, Pass, TlsPorts, Ports, Username, Realname, VerifyCert, TrustedFingerprint, EncodingOut, EncodingIn, MaxHistoryLength, Address, Nick>;
+ using IrcServerOptionsTable = Table<Id, Owner, Server, Pass, TlsPorts, Ports, Username, Realname, VerifyCert, TrustedFingerprint, EncodingOut, EncodingIn, MaxHistoryLength, Address, Nick, ThrottleLimit>;
using IrcServerOptions = IrcServerOptionsTable::RowType;
using IrcChannelOptionsTable = Table<Id, Owner, Server, Channel, EncodingOut, EncodingIn, MaxHistoryLength, Persistent, RecordHistoryOptional>;
diff --git a/src/database/postgresql_statement.hpp b/src/database/postgresql_statement.hpp
index 37e8ea0..345a942 100644
--- a/src/database/postgresql_statement.hpp
+++ b/src/database/postgresql_statement.hpp
@@ -15,7 +15,7 @@ class PostgresqlStatement: public Statement
body(std::move(body)),
conn(conn)
{}
- ~PostgresqlStatement()
+ virtual ~PostgresqlStatement()
{
PQclear(this->result);
this->result = nullptr;
@@ -89,8 +89,6 @@ class PostgresqlStatement: public Statement
return true;
}
- private:
-
private:
bool execute(const bool second_attempt=false)
{
@@ -119,11 +117,7 @@ private:
PQreset(this->conn);
return this->execute(true);
}
- else
- {
- log_error("Givin up.");
- return false;
- }
+ return false;
}
return true;
}
diff --git a/src/database/query.cpp b/src/database/query.cpp
index d72066e..5ec8599 100644
--- a/src/database/query.cpp
+++ b/src/database/query.cpp
@@ -6,11 +6,6 @@ void actual_bind(Statement& statement, const std::string& value, int index)
statement.bind_text(index, value);
}
-void actual_bind(Statement& statement, const std::int64_t& value, int index)
-{
- statement.bind_int64(index, value);
-}
-
void actual_bind(Statement& statement, const OptionalBool& value, int index)
{
if (!value.is_set)
diff --git a/src/database/query.hpp b/src/database/query.hpp
index ba28b1a..c89371f 100644
--- a/src/database/query.hpp
+++ b/src/database/query.hpp
@@ -12,13 +12,14 @@
#include <string>
void actual_bind(Statement& statement, const std::string& value, int index);
-void actual_bind(Statement& statement, const std::int64_t& value, int index);
-template <typename T, typename std::enable_if_t<std::is_integral<T>::value>* = 0>
+void actual_bind(Statement& statement, const OptionalBool& value, int index);
+template <typename T>
void actual_bind(Statement& statement, const T& value, int index)
{
- actual_bind(statement, static_cast<std::int64_t>(value), index);
+ static_assert(std::is_integral<T>::value,
+ "Only a string, an optional-bool or an integer can be used.");
+ statement.bind_int64(index, static_cast<std::int64_t>(value));
}
-void actual_bind(Statement& statement, const OptionalBool& value, int index);
#ifdef DEBUG_SQL_QUERIES
#include <utils/scopetimer.hpp>
diff --git a/src/database/row.hpp b/src/database/row.hpp
index 1253f93..4004b5d 100644
--- a/src/database/row.hpp
+++ b/src/database/row.hpp
@@ -28,7 +28,7 @@ struct Row
this->clear_col<0>();
}
- std::tuple<T...> columns;
+ std::tuple<T...> columns{};
std::string table_name;
private:
diff --git a/src/database/select_query.hpp b/src/database/select_query.hpp
index b9fdc06..e372f2e 100644
--- a/src/database/select_query.hpp
+++ b/src/database/select_query.hpp
@@ -135,7 +135,7 @@ struct SelectQuery: public Query
};
template <typename... T>
-auto select(const Table<T...> table)
+auto select(const Table<T...>& table)
{
SelectQuery<T...> query(table.name);
return query;
diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp
index d7fa2cd..de38d42 100644
--- a/src/irc/irc_client.cpp
+++ b/src/irc/irc_client.cpp
@@ -135,7 +135,7 @@ IrcClient::IrcClient(std::shared_ptr<Poller>& poller, std::string hostname,
std::string realname, std::string user_hostname,
Bridge& bridge):
TCPClientSocketHandler(poller),
- hostname(std::move(hostname)),
+ hostname(hostname),
user_hostname(std::move(user_hostname)),
username(std::move(username)),
realname(std::move(realname)),
@@ -143,7 +143,14 @@ IrcClient::IrcClient(std::shared_ptr<Poller>& poller, std::string hostname,
bridge(bridge),
welcomed(false),
chanmodes({"", "", "", ""}),
- chantypes({'#', '&'})
+ chantypes({'#', '&'}),
+ tokens_bucket(this->get_throttle_limit(), 1s, [this]() {
+ if (message_queue.empty())
+ return true;
+ this->actual_send(std::move(this->message_queue.front()));
+ this->message_queue.pop_front();
+ return false;
+ }, "TokensBucket" + this->hostname + this->bridge.get_jid())
{
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
@@ -171,6 +178,7 @@ IrcClient::~IrcClient()
// This event may or may not exist (if we never got connected, it
// doesn't), but it's ok
TimedEventsManager::instance().cancel("PING" + this->hostname + this->bridge.get_jid());
+ TimedEventsManager::instance().cancel("TokensBucket" + this->hostname + this->bridge.get_jid());
}
void IrcClient::start()
@@ -317,9 +325,21 @@ IrcChannel* IrcClient::get_channel(const std::string& n)
}
catch (const std::out_of_range& exception)
{
- this->channels.emplace(name, std::make_unique<IrcChannel>());
+ return this->channels.emplace(name, std::make_unique<IrcChannel>()).first->second.get();
+ }
+}
+
+const IrcChannel* IrcClient::find_channel(const std::string& n) const
+{
+ const std::string name = utils::tolower(n);
+ try
+ {
return this->channels.at(name).get();
}
+ catch (const std::out_of_range& exception)
+ {
+ return nullptr;
+ }
}
bool IrcClient::is_channel_joined(const std::string& name)
@@ -378,25 +398,39 @@ void IrcClient::parse_in_buffer(const size_t)
}
}
-void IrcClient::send_message(IrcMessage&& message)
+void IrcClient::actual_send(std::pair<IrcMessage, MessageCallback>&& message_pair)
{
- log_debug("IRC SENDING: (", this->get_hostname(), ") ", message);
- std::string res;
- if (!message.prefix.empty())
- res += ":" + std::move(message.prefix) + " ";
- res += message.command;
- for (const std::string& arg: message.arguments)
- {
- if (arg.find(' ') != std::string::npos ||
- (!arg.empty() && arg[0] == ':'))
- {
- res += " :" + arg;
- break;
- }
- res += " " + arg;
- }
- res += "\r\n";
- this->send_data(std::move(res));
+ const IrcMessage& message = message_pair.first;
+ const MessageCallback& callback = message_pair.second;
+ log_debug("IRC SENDING: (", this->get_hostname(), ") ", message);
+ std::string res;
+ if (!message.prefix.empty())
+ res += ":" + message.prefix + " ";
+ res += message.command;
+ for (const std::string& arg: message.arguments)
+ {
+ if (arg.find(' ') != std::string::npos
+ || (!arg.empty() && arg[0] == ':'))
+ {
+ res += " :" + arg;
+ break;
+ }
+ res += " " + arg;
+ }
+ res += "\r\n";
+ this->send_data(std::move(res));
+
+ if (callback)
+ callback(this, message);
+ }
+
+void IrcClient::send_message(IrcMessage message, MessageCallback callback, bool throttle)
+{
+ auto message_pair = std::make_pair(std::move(message), std::move(callback));
+ if (this->tokens_bucket.use_token() || !throttle)
+ this->actual_send(std::move(message_pair));
+ else
+ message_queue.push_back(std::move(message_pair));
}
void IrcClient::send_raw(const std::string& txt)
@@ -447,12 +481,12 @@ void IrcClient::send_topic_command(const std::string& chan_name, const std::stri
void IrcClient::send_quit_command(const std::string& reason)
{
- this->send_message(IrcMessage("QUIT", {reason}));
+ this->send_message(IrcMessage("QUIT", {reason}), {}, false);
}
void IrcClient::send_join_command(const std::string& chan_name, const std::string& password)
{
- if (this->welcomed == false)
+ if (!this->welcomed)
{
const auto it = std::find_if(begin(this->channels_to_join), end(this->channels_to_join),
[&chan_name](const auto& pair) { return std::get<0>(pair) == chan_name; });
@@ -466,10 +500,11 @@ void IrcClient::send_join_command(const std::string& chan_name, const std::strin
this->start();
}
-bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body)
+bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body,
+ MessageCallback callback)
{
IrcChannel* channel = this->get_channel(chan_name);
- if (channel->joined == false)
+ if (!channel->joined)
{
log_warning("Cannot send message to channel ", chan_name, ", it is not joined");
return false;
@@ -489,7 +524,7 @@ bool IrcClient::send_channel_message(const std::string& chan_name, const std::st
::strlen(":!@ PRIVMSG ") - chan_name.length() - ::strlen(" :\r\n");
const auto lines = cut(body, line_size);
for (const auto& line: lines)
- this->send_message(IrcMessage("PRIVMSG", {chan_name, line}));
+ this->send_message(IrcMessage("PRIVMSG", {chan_name, line}), callback);
return true;
}
@@ -1123,8 +1158,6 @@ void IrcClient::on_channel_bad_key(const IrcMessage& message)
void IrcClient::on_channel_mode(const IrcMessage& message)
{
- // For now, just transmit the modes so the user can know what happens
- // TODO, actually interprete the mode.
Iid iid;
iid.set_local(message.arguments[0]);
iid.set_server(this->hostname);
@@ -1142,7 +1175,7 @@ void IrcClient::on_channel_mode(const IrcMessage& message)
}
this->bridge.send_message(iid, "", "Mode " + iid.get_local() +
" [" + mode_arguments + "] by " + user.nick,
- true);
+ true, this->is_channel_joined(iid.get_local()));
const IrcChannel* channel = this->get_channel(iid.get_local());
if (!channel)
return;
@@ -1215,6 +1248,11 @@ void IrcClient::on_channel_mode(const IrcMessage& message)
}
}
+void IrcClient::set_throttle_limit(long int limit)
+{
+ this->tokens_bucket.set_limit(limit);
+}
+
void IrcClient::on_user_mode(const IrcMessage& message)
{
this->bridge.send_xmpp_message(this->hostname, "",
@@ -1252,3 +1290,12 @@ bool IrcClient::abort_on_invalid_cert() const
return true;
}
#endif
+
+long int IrcClient::get_throttle_limit() const
+{
+#ifdef USE_DATABASE
+ return Database::get_irc_server_options(this->bridge.get_bare_jid(), this->hostname).col<Database::ThrottleLimit>();
+#else
+ return 10;
+#endif
+}
diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp
index fd97fe6..cfb3d21 100644
--- a/src/irc/irc_client.hpp
+++ b/src/irc/irc_client.hpp
@@ -16,8 +16,14 @@
#include <vector>
#include <string>
#include <stack>
+#include <deque>
#include <map>
#include <set>
+#include <utils/tokens_bucket.hpp>
+
+class IrcClient;
+
+using MessageCallback = std::function<void(const IrcClient*, const IrcMessage&)>;
class Bridge;
@@ -28,7 +34,7 @@ class Bridge;
class IrcClient: public TCPClientSocketHandler
{
public:
- explicit IrcClient(std::shared_ptr<Poller>& poller, std::string hostname,
+ explicit IrcClient(std::shared_ptr<Poller>& poller, std::string hostname,
std::string nickname, std::string username,
std::string realname, std::string user_hostname,
Bridge& bridge);
@@ -68,6 +74,10 @@ public:
*/
IrcChannel* get_channel(const std::string& name);
/**
+ * Return the channel with this name. Nullptr if it is not found
+ */
+ const IrcChannel* find_channel(const std::string& name) const;
+ /**
* Returns true if the channel is joined
*/
bool is_channel_joined(const std::string& name);
@@ -80,8 +90,9 @@ public:
* (actually, into our out_buf and signal the poller that we want to wach
* for send events to be ready)
*/
- void send_message(IrcMessage&& message);
+ void send_message(IrcMessage message, MessageCallback callback={}, bool throttle=true);
void send_raw(const std::string& txt);
+ void actual_send(std::pair<IrcMessage, MessageCallback>&& message_pair);
/**
* Send the PONG irc command
*/
@@ -110,7 +121,8 @@ public:
* Send a PRIVMSG command for a channel
* Return true if the message was actually sent
*/
- bool send_channel_message(const std::string& chan_name, const std::string& body);
+ bool send_channel_message(const std::string& chan_name, const std::string& body,
+ MessageCallback callback);
/**
* Send a PRIVMSG command for an user
*/
@@ -289,7 +301,7 @@ public:
const std::vector<char>& get_sorted_user_modes() const { return this->sorted_user_modes; }
std::set<char> get_chantypes() const { return this->chantypes; }
-
+ void set_throttle_limit(long int limit);
/**
* Store the history limit that the client asked when joining this room.
*/
@@ -327,6 +339,10 @@ private:
*/
Bridge& bridge;
/**
+ * Where messaged are stored when they are throttled.
+ */
+ std::deque<std::pair<IrcMessage, MessageCallback>> message_queue{};
+ /**
* The list of joined channels, indexed by name
*/
std::unordered_map<std::string, std::unique_ptr<IrcChannel>> channels;
@@ -385,6 +401,8 @@ private:
* the WebIRC protocole.
*/
Resolver dns_resolver;
+ TokensBucket tokens_bucket;
+ long int get_throttle_limit() const;
};
diff --git a/src/irc/irc_message.hpp b/src/irc/irc_message.hpp
index fe954e4..269a12a 100644
--- a/src/irc/irc_message.hpp
+++ b/src/irc/irc_message.hpp
@@ -14,9 +14,9 @@ public:
~IrcMessage() = default;
IrcMessage(const IrcMessage&) = delete;
- IrcMessage(IrcMessage&&) = delete;
+ IrcMessage(IrcMessage&&) = default;
IrcMessage& operator=(const IrcMessage&) = delete;
- IrcMessage& operator=(IrcMessage&&) = delete;
+ IrcMessage& operator=(IrcMessage&&) = default;
std::string prefix;
std::string command;
diff --git a/src/main.cpp b/src/main.cpp
index 59fda4e..2448197 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -55,45 +55,8 @@ static void sigusr_handler(int, siginfo_t*, void*)
reload.store(true);
}
-int main(int ac, char** av)
+static void setup_signals()
{
- if (ac > 1)
- {
- const std::string arg = av[1];
- if (arg.size() >= 2 && arg[0] == '-' && arg[1] == '-')
- {
- if (arg == "--help")
- return display_help();
- else
- {
- std::cerr << "Unknow command line option: " << arg << std::endl;
- return 1;
- }
- }
- }
- const std::string conf_filename = ac > 1 ? av[1] : xdg_config_path("biboumi.cfg");
- std::cout << "Using configuration file: " << conf_filename << std::endl;
-
- if (!Config::read_conf(conf_filename))
- return config_help("");
-
- const std::string password = Config::get("password", "");
- if (password.empty())
- return config_help("password");
- const std::string hostname = Config::get("hostname", "");
- if (hostname.empty())
- return config_help("hostname");
-
-
-#ifdef USE_DATABASE
- try {
- open_database();
- } catch (const std::exception& e) {
- log_error(e.what());
- return 1;
- }
-#endif
-
// Block the signals we want to manage. They will be unblocked only during
// the epoll_pwait or ppoll calls. This avoids some race conditions,
// explained in man 2 pselect on linux
@@ -103,6 +66,7 @@ int main(int ac, char** av)
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
+ sigaddset(&mask, SIGHUP);
sigprocmask(SIG_BLOCK, &mask, nullptr);
// Install the signals used to exit the process cleanly, or reload the
@@ -113,7 +77,7 @@ int main(int ac, char** av)
sigfillset(&on_sigint.sa_mask);
// we want to catch that signal only once.
// Sending SIGINT again will "force" an exit
- on_sigint.sa_flags = SA_RESETHAND;
+ on_sigint.sa_flags = 0 & SA_RESETHAND;
sigaction(SIGINT, &on_sigint, nullptr);
sigaction(SIGTERM, &on_sigint, nullptr);
@@ -124,7 +88,11 @@ int main(int ac, char** av)
on_sigusr.sa_flags = 0;
sigaction(SIGUSR1, &on_sigusr, nullptr);
sigaction(SIGUSR2, &on_sigusr, nullptr);
+ sigaction(SIGHUP, &on_sigusr, nullptr);
+}
+static int main_loop(std::string hostname, std::string password)
+{
auto p = std::make_shared<Poller>();
#ifdef UDNS_FOUND
@@ -161,7 +129,7 @@ int main(int ac, char** av)
dns_handler.destroy();
#endif
if (identd)
- identd->shutdown();
+ identd->shutdown();
// Cancel the timer for a potential reconnection
TimedEventsManager::instance().cancel("XMPP reconnection");
}
@@ -204,7 +172,7 @@ int main(int ac, char** av)
dns_handler.destroy();
#endif
if (identd)
- identd->shutdown();
+ identd->shutdown();
}
}
// If the only existing connection is the one to the XMPP component:
@@ -223,3 +191,51 @@ int main(int ac, char** av)
log_info("All connections cleanly closed, have a nice day.");
return 0;
}
+
+int main(int ac, char** av)
+{
+ if (ac > 1)
+ {
+ const std::string arg = av[1];
+ if (arg.size() >= 2 && arg[0] == '-' && arg[1] == '-')
+ {
+ if (arg == "--help")
+ return display_help();
+ else
+ {
+ std::cerr << "Unknow command line option: " << arg
+ << std::endl;
+ return 1;
+ }
+ }
+ }
+ const std::string conf_filename =
+ ac > 1 ? av[1]: xdg_config_path("biboumi.cfg");
+ std::cout << "Using configuration file: " << conf_filename << std::endl;
+
+ if (!Config::read_conf(conf_filename))
+ return config_help("");
+
+ const std::string password = Config::get("password", "");
+ if (password.empty())
+ return config_help("password");
+ const std::string hostname = Config::get("hostname", "");
+ if (hostname.empty())
+ return config_help("hostname");
+
+#ifdef USE_DATABASE
+ try
+ {
+ open_database();
+ }
+ catch (const std::exception& e)
+ {
+ log_error(e.what());
+ return 1;
+ }
+#endif
+
+ setup_signals();
+
+ return main_loop(std::move(hostname), std::move(password));
+}
diff --git a/src/network/credentials_manager.cpp b/src/network/credentials_manager.cpp
index b25f442..89c694c 100644
--- a/src/network/credentials_manager.cpp
+++ b/src/network/credentials_manager.cpp
@@ -21,9 +21,8 @@ static const std::vector<std::string> default_cert_files = {
Botan::Certificate_Store_In_Memory BasicCredentialsManager::certificate_store;
bool BasicCredentialsManager::certs_loaded = false;
-BasicCredentialsManager::BasicCredentialsManager(const TCPSocketHandler* const socket_handler):
+BasicCredentialsManager::BasicCredentialsManager():
Botan::Credentials_Manager(),
- socket_handler(socket_handler),
trusted_fingerprint{}
{
BasicCredentialsManager::load_certs();
diff --git a/src/network/credentials_manager.hpp b/src/network/credentials_manager.hpp
index 3a37bdc..210a628 100644
--- a/src/network/credentials_manager.hpp
+++ b/src/network/credentials_manager.hpp
@@ -25,7 +25,7 @@ void check_tls_certificate(const std::vector<Botan::X509_Certificate>& certs,
class BasicCredentialsManager: public Botan::Credentials_Manager
{
public:
- BasicCredentialsManager(const TCPSocketHandler* const socket_handler);
+ BasicCredentialsManager();
BasicCredentialsManager(BasicCredentialsManager&&) = delete;
BasicCredentialsManager(const BasicCredentialsManager&) = delete;
@@ -38,7 +38,6 @@ public:
const std::string& get_trusted_fingerprint() const;
private:
- const TCPSocketHandler* const socket_handler;
static bool try_to_open_one_ca_bundle(const std::vector<std::string>& paths);
static void load_certs();
diff --git a/src/network/resolver.cpp b/src/network/resolver.cpp
index ae5cecd..d9242e2 100644
--- a/src/network/resolver.cpp
+++ b/src/network/resolver.cpp
@@ -214,6 +214,12 @@ void Resolver::on_hostname6_resolved(dns_rr_a6 *result)
this->call_getaddrinfo(buf, this->port.data(), AI_NUMERICHOST);
}
}
+ else
+ {
+ const auto error = dns_error_messages.find(status);
+ if (error != end(dns_error_messages))
+ this->error_msg = error->second;
+ }
}
void Resolver::after_resolved()
diff --git a/src/network/tcp_client_socket_handler.cpp b/src/network/tcp_client_socket_handler.cpp
index dcf38f9..7d1029f 100644
--- a/src/network/tcp_client_socket_handler.cpp
+++ b/src/network/tcp_client_socket_handler.cpp
@@ -46,15 +46,14 @@ void TCPClientSocketHandler::init_socket(const struct addrinfo* rp)
else
{
utils::ScopeGuard sg([result](){ freeaddrinfo(result); });
- struct addrinfo* rp;
- for (rp = result; rp; rp = rp->ai_next)
+ for (; result; result = result->ai_next)
{
if ((::bind(this->socket,
- reinterpret_cast<const struct sockaddr*>(rp->ai_addr),
- rp->ai_addrlen)) == 0)
+ reinterpret_cast<const struct sockaddr*>(result->ai_addr),
+ result->ai_addrlen)) == 0)
break;
}
- if (!rp)
+ if (!result)
log_error("Failed to bind socket to ", this->bind_addr, ": ",
strerror(errno));
else
diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp
index 642cf03..e05caad 100644
--- a/src/network/tcp_socket_handler.cpp
+++ b/src/network/tcp_socket_handler.cpp
@@ -50,7 +50,7 @@ TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller>& poller):
SocketHandler(poller, -1),
use_tls(false)
#ifdef BOTAN_FOUND
- ,credential_manager(this)
+ ,credential_manager()
#endif
{}
@@ -84,10 +84,11 @@ void TCPSocketHandler::plain_recv()
if (recv_buf == nullptr)
recv_buf = buf;
- const ssize_t size = this->do_recv(recv_buf, buf_size);
+ const ssize_t ssize = this->do_recv(recv_buf, buf_size);
- if (size > 0)
+ if (ssize > 0)
{
+ auto size = static_cast<std::size_t>(ssize);
if (buf == recv_buf)
{
// data needs to be placed in the in_buf string, because no buffer
@@ -149,21 +150,22 @@ void TCPSocketHandler::on_send()
}
else
{
+ auto size = static_cast<std::size_t>(res);
// remove all the strings that were successfully sent.
auto it = this->out_buf.begin();
while (it != this->out_buf.end())
{
- if (static_cast<size_t>(res) >= it->size())
+ if (size >= it->size())
{
- res -= it->size();
+ size -= it->size();
++it;
}
else
{
// If one string has partially been sent, we use substr to
// crop it
- if (res > 0)
- *it = it->substr(res, std::string::npos);
+ if (size > 0)
+ *it = it->substr(size, std::string::npos);
break;
}
}
@@ -332,6 +334,11 @@ void TCPSocketHandler::tls_verify_cert_chain(const std::vector<Botan::X509_Certi
Botan::Usage_Type usage, const std::string& hostname,
const Botan::TLS::Policy& policy)
{
+ if (!this->policy.verify_certificate)
+ {
+ log_debug("Not verifying certificate due to domain policy ");
+ return;
+ }
log_debug("Checking remote certificate for hostname ", hostname);
try
{
diff --git a/src/network/tls_policy.cpp b/src/network/tls_policy.cpp
index b88eb88..f32557e 100644
--- a/src/network/tls_policy.cpp
+++ b/src/network/tls_policy.cpp
@@ -37,6 +37,8 @@ void BiboumiTLSPolicy::load(std::istream& is)
// Workaround for options that are not overridden in Botan::TLS::Text_Policy
if (pair.first == "require_cert_revocation_info")
this->req_cert_revocation_info = !(pair.second == "0" || utils::tolower(pair.second) == "false");
+ else if (pair.first == "verify_certificate")
+ this->verify_certificate = !(pair.second == "0" || utils::tolower(pair.second) == "false");
else
this->set(pair.first, pair.second);
}
diff --git a/src/network/tls_policy.hpp b/src/network/tls_policy.hpp
index 29fd2b3..e915646 100644
--- a/src/network/tls_policy.hpp
+++ b/src/network/tls_policy.hpp
@@ -21,6 +21,7 @@ public:
BiboumiTLSPolicy &operator=(BiboumiTLSPolicy &&) = delete;
bool require_cert_revocation_info() const override;
+ bool verify_certificate{true};
protected:
bool req_cert_revocation_info{true};
};
diff --git a/src/utils/dirname.cpp b/src/utils/dirname.cpp
index 71c9c38..a304117 100644
--- a/src/utils/dirname.cpp
+++ b/src/utils/dirname.cpp
@@ -2,7 +2,7 @@
namespace utils
{
- std::string dirname(const std::string filename)
+ std::string dirname(const std::string& filename)
{
if (filename.empty())
return "./";
diff --git a/src/utils/dirname.hpp b/src/utils/dirname.hpp
index c1df81b..c13393d 100644
--- a/src/utils/dirname.hpp
+++ b/src/utils/dirname.hpp
@@ -1,6 +1,8 @@
+#pragma once
+
#include <string>
namespace utils
{
-std::string dirname(const std::string filename);
+std::string dirname(const std::string& filename);
}
diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp
index cff0039..8532292 100644
--- a/src/utils/encoding.cpp
+++ b/src/utils/encoding.cpp
@@ -48,16 +48,16 @@ namespace utils
if (codepoint_size == 4)
{
if (!str[1] || !str[2] || !str[3]
- || ((str[1] & 0b11000000) != 0b10000000)
- || ((str[2] & 0b11000000) != 0b10000000)
- || ((str[3] & 0b11000000) != 0b10000000))
+ || ((str[1] & 0b11000000u) != 0b10000000u)
+ || ((str[2] & 0b11000000u) != 0b10000000u)
+ || ((str[3] & 0b11000000u) != 0b10000000u))
return false;
}
else if (codepoint_size == 3)
{
if (!str[1] || !str[2]
- || ((str[1] & 0b11000000) != 0b10000000)
- || ((str[2] & 0b11000000) != 0b10000000))
+ || ((str[1] & 0b11000000u) != 0b10000000u)
+ || ((str[2] & 0b11000000u) != 0b10000000u))
return false;
}
else if (codepoint_size == 2)
@@ -81,7 +81,7 @@ namespace utils
// pointer where we write valid chars
char* r = res.data();
- const char* str = original.c_str();
+ const unsigned char* str = reinterpret_cast<const unsigned char*>(original.c_str());
std::bitset<20> codepoint;
while (*str)
@@ -89,10 +89,10 @@ namespace utils
// 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
if ((str[0] & 0b11111000) == 0b11110000)
{
- codepoint = ((str[0] & 0b00000111) << 18);
- codepoint |= ((str[1] & 0b00111111) << 12);
- codepoint |= ((str[2] & 0b00111111) << 6 );
- codepoint |= ((str[3] & 0b00111111) << 0 );
+ codepoint = ((str[0] & 0b00000111u) << 18u);
+ codepoint |= ((str[1] & 0b00111111u) << 12u);
+ codepoint |= ((str[2] & 0b00111111u) << 6u );
+ codepoint |= ((str[3] & 0b00111111u) << 0u );
if (codepoint.to_ulong() <= 0x10FFFF)
{
::memcpy(r, str, 4);
@@ -103,9 +103,9 @@ namespace utils
// 3 bytes: 1110xxx 10xxxxxx 10xxxxxx
else if ((str[0] & 0b11110000) == 0b11100000)
{
- codepoint = ((str[0] & 0b00001111) << 12);
- codepoint |= ((str[1] & 0b00111111) << 6);
- codepoint |= ((str[2] & 0b00111111) << 0 );
+ codepoint = ((str[0] & 0b00001111u) << 12u);
+ codepoint |= ((str[1] & 0b00111111u) << 6u);
+ codepoint |= ((str[2] & 0b00111111u) << 0u );
if (codepoint.to_ulong() <= 0xD7FF ||
(codepoint.to_ulong() >= 0xE000 && codepoint.to_ulong() <= 0xFFFD))
{
diff --git a/src/utils/get_first_non_empty.cpp b/src/utils/get_first_non_empty.cpp
index 5b3bedb..17585b1 100644
--- a/src/utils/get_first_non_empty.cpp
+++ b/src/utils/get_first_non_empty.cpp
@@ -1,11 +1,8 @@
#include <utils/get_first_non_empty.hpp>
+template <>
bool is_empty(const std::string& val)
{
return val.empty();
}
-bool is_empty(const int& val)
-{
- return val == 0;
-}
diff --git a/src/utils/get_first_non_empty.hpp b/src/utils/get_first_non_empty.hpp
index a38f5fb..1877ee8 100644
--- a/src/utils/get_first_non_empty.hpp
+++ b/src/utils/get_first_non_empty.hpp
@@ -2,8 +2,13 @@
#include <string>
+template <typename T>
+bool is_empty(const T& val)
+{
+ return val == 0;
+}
+template <>
bool is_empty(const std::string& val);
-bool is_empty(const int& val);
template <typename T>
T get_first_non_empty(T&& last)
diff --git a/src/utils/optional_bool.hpp b/src/utils/optional_bool.hpp
index 867aca2..3d00d23 100644
--- a/src/utils/optional_bool.hpp
+++ b/src/utils/optional_bool.hpp
@@ -6,7 +6,7 @@ struct OptionalBool
{
OptionalBool() = default;
- OptionalBool(bool value):
+ explicit OptionalBool(bool value):
is_set(true), value(value) {}
void set_value(bool value)
diff --git a/src/utils/string.cpp b/src/utils/string.cpp
index 635e71a..366ec1f 100644
--- a/src/utils/string.cpp
+++ b/src/utils/string.cpp
@@ -15,11 +15,11 @@ std::vector<std::string> cut(const std::string& val, const std::size_t size)
// Get the number of chars, <= size, that contain only whole
// UTF-8 codepoints.
std::size_t s = 0;
- auto codepoint_size = utils::get_next_codepoint_size(val[pos + s]);
+ auto codepoint_size = utils::get_next_codepoint_size(static_cast<unsigned char>(val[pos + s]));
while (s + codepoint_size <= size && pos + s < val.size())
{
s += codepoint_size;
- codepoint_size = utils::get_next_codepoint_size(val[pos + s]);
+ codepoint_size = utils::get_next_codepoint_size(static_cast<unsigned char>(val[pos + s]));
}
res.emplace_back(val.substr(pos, s));
pos += s;
diff --git a/src/utils/time.cpp b/src/utils/time.cpp
index 71306fd..d848e70 100644
--- a/src/utils/time.cpp
+++ b/src/utils/time.cpp
@@ -1,9 +1,8 @@
#include <utils/time.hpp>
-#include <ctime>
+#include <time.h>
#include <sstream>
#include <iomanip>
-#include <locale>
#include "biboumi.h"
@@ -12,9 +11,10 @@ namespace utils
std::string to_string(const std::chrono::system_clock::time_point::rep& time)
{
constexpr std::size_t stamp_size = 21;
- const std::time_t timestamp = static_cast<std::time_t>(time);
+ const auto timestamp = static_cast<std::time_t>(time);
char date_buf[stamp_size];
- if (std::strftime(date_buf, stamp_size, "%FT%TZ", std::gmtime(&timestamp)) != stamp_size - 1)
+ struct tm tm;
+ if (std::strftime(date_buf, stamp_size, "%FT%TZ", gmtime_r(&timestamp, &tm)) != stamp_size - 1)
return "";
return {std::begin(date_buf), std::end(date_buf) - 1};
}
diff --git a/src/utils/tokens_bucket.hpp b/src/utils/tokens_bucket.hpp
new file mode 100644
index 0000000..263359a
--- /dev/null
+++ b/src/utils/tokens_bucket.hpp
@@ -0,0 +1,60 @@
+/**
+ * Implementation of the token bucket algorithm.
+ *
+ * It uses a repetitive TimedEvent, started at construction, to fill the
+ * bucket.
+ *
+ * Every n seconds, it executes the given callback. If the callback
+ * returns true, we add a token (if the limit is not yet reached).
+ *
+ */
+
+#pragma once
+
+#include <utils/timed_events.hpp>
+#include <logger/logger.hpp>
+
+class TokensBucket
+{
+public:
+ TokensBucket(long int max_size, std::chrono::milliseconds fill_duration, std::function<bool()> callback, std::string name):
+ limit(max_size),
+ tokens(static_cast<std::size_t>(limit)),
+ callback(std::move(callback))
+ {
+ log_debug("creating TokensBucket with max size: ", max_size);
+ TimedEvent event(std::move(fill_duration), [this]() { this->add_token(); }, std::move(name));
+ TimedEventsManager::instance().add_event(std::move(event));
+ }
+
+ bool use_token()
+ {
+ if (this->limit < 0)
+ return true;
+ if (this->tokens > 0)
+ {
+ this->tokens--;
+ return true;
+ }
+ else
+ return false;
+ }
+
+ void set_limit(long int limit)
+ {
+ this->limit = limit;
+ }
+
+private:
+ long int limit;
+ std::size_t tokens;
+ std::function<bool()> callback;
+
+ void add_token()
+ {
+ if (this->limit < 0)
+ return;
+ if (this->callback() && this->tokens != static_cast<decltype(this->tokens)>(this->limit))
+ this->tokens++;
+ }
+};
diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp
index bc4c108..ff4c1e5 100644
--- a/src/xmpp/adhoc_commands_handler.cpp
+++ b/src/xmpp/adhoc_commands_handler.cpp
@@ -80,7 +80,10 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, co
{
command_node["status"] = "executing";
XmlSubNode actions(command_node, "actions");
- XmlSubNode next(actions, "next");
+ if (session.remaining_steps() == 1)
+ XmlSubNode next(actions, "complete");
+ else
+ XmlSubNode next(actions, "next");
}
}
else if (session_it != this->sessions.end() && action == "cancel")
diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp
index 3bd2e5a..113943c 100644
--- a/src/xmpp/biboumi_adhoc_commands.cpp
+++ b/src/xmpp/biboumi_adhoc_commands.cpp
@@ -15,10 +15,17 @@
#ifdef USE_DATABASE
#include <database/database.hpp>
#include <database/save.hpp>
+
+static void set_desc(XmlSubNode& field, const char* text)
+{
+ XmlSubNode desc(field, "desc");
+ desc.set_inner(text);
+}
+
#endif
#ifndef HAS_PUT_TIME
-#include <ctime>
+# include <time.h>
#endif
using namespace std::string_literals;
@@ -116,6 +123,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman
auto options = Database::get_global_options(owner.bare());
+ command_node.delete_all_children();
XmlSubNode x(command_node, "jabber:x:data:x");
x["type"] = "form";
XmlSubNode title(x, "title");
@@ -128,7 +136,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman
max_histo_length["var"] = "max_history_length";
max_histo_length["type"] = "text-single";
max_histo_length["label"] = "Max history length";
- max_histo_length["desc"] = "The maximum number of lines in the history that the server sends when joining a channel";
+ set_desc(max_histo_length, "The maximum number of lines in the history that the server sends when joining a channel");
{
XmlSubNode value(max_histo_length, "value");
value.set_inner(std::to_string(options.col<Database::MaxHistoryLength>()));
@@ -140,7 +148,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman
record_history["var"] = "record_history";
record_history["type"] = "boolean";
record_history["label"] = "Record history";
- record_history["desc"] = "Whether to save the messages into the database, or not";
+ set_desc(record_history, "Whether to save the messages into the database, or not");
{
XmlSubNode value(record_history, "value");
value.set_name("value");
@@ -156,7 +164,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman
persistent["var"] = "persistent";
persistent["type"] = "boolean";
persistent["label"] = "Make all channels persistent";
- persistent["desc"] = "If true, all channels will be persistent";
+ set_desc(persistent, "If true, all channels will be persistent");
{
XmlSubNode value(persistent, "value");
value.set_name("value");
@@ -184,7 +192,13 @@ void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session,
if (field->get_tag("var") == "max_history_length" &&
value && !value->get_inner().empty())
- options.col<Database::MaxHistoryLength>() = atoi(value->get_inner().data());
+ {
+ try {
+ options.col<Database::MaxHistoryLength>() = std::stol(value->get_inner().data());
+ } catch (const std::logic_error&) {
+ options.col<Database::MaxHistoryLength>() = 20;
+ }
+ }
else if (field->get_tag("var") == "record_history" &&
value && !value->get_inner().empty())
{
@@ -223,6 +237,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
server_domain);
auto commands = Database::get_after_connection_commands(options);
+ command_node.delete_all_children();
XmlSubNode x(command_node, "jabber:x:data:x");
x["type"] = "form";
XmlSubNode title(x, "title");
@@ -236,7 +251,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
field["var"] = "address";
field["type"] = "text-single";
field["label"] = "Address";
- field["desc"] = "The address (hostname or IP) to connect to.";
+ set_desc(field, "The address (hostname or IP) to connect to.");
XmlSubNode value(field, "value");
if (options.col<Database::Address>().empty())
value.set_inner(server_domain);
@@ -249,7 +264,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
ports["var"] = "ports";
ports["type"] = "text-multi";
ports["label"] = "Ports";
- ports["desc"] = "List of ports to try, without TLS. Defaults: 6667.";
+ set_desc(ports, "List of ports to try, without TLS. Defaults: 6667.");
for (const auto& val: utils::split(options.col<Database::Ports>(), ';', false))
{
XmlSubNode ports_value(ports, "value");
@@ -263,7 +278,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
tls_ports["var"] = "tls_ports";
tls_ports["type"] = "text-multi";
tls_ports["label"] = "TLS ports";
- tls_ports["desc"] = "List of ports to try, with TLS. Defaults: 6697, 6670.";
+ set_desc(tls_ports, "List of ports to try, with TLS. Defaults: 6697, 6670.");
for (const auto& val: utils::split(options.col<Database::TlsPorts>(), ';', false))
{
XmlSubNode tls_ports_value(tls_ports, "value");
@@ -276,7 +291,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
verify_cert["var"] = "verify_cert";
verify_cert["type"] = "boolean";
verify_cert["label"] = "Verify certificate";
- verify_cert["desc"] = "Whether or not to abort the connection if the server’s TLS certificate is invalid";
+ set_desc(verify_cert, "Whether or not to abort the connection if the server’s TLS certificate is invalid");
XmlSubNode verify_cert_value(verify_cert, "value");
if (options.col<Database::VerifyCert>())
verify_cert_value.set_inner("true");
@@ -302,7 +317,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
field["var"] = "nick";
field["type"] = "text-single";
field["label"] = "Nickname";
- field["desc"] = "If set, will override the nickname provided in the initial presence sent to join the first server channel";
+ set_desc(field, "If set, will override the nickname provided in the initial presence sent to join the first server channel");
if (!options.col<Database::Nick>().empty())
{
XmlSubNode value(field, "value");
@@ -315,7 +330,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
pass["var"] = "pass";
pass["type"] = "text-private";
pass["label"] = "Server password";
- pass["desc"] = "Will be used in a PASS command when connecting";
+ set_desc(pass, "Will be used in a PASS command when connecting");
if (!options.col<Database::Pass>().empty())
{
XmlSubNode pass_value(pass, "value");
@@ -327,7 +342,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode after_cnt_cmd(x, "field");
after_cnt_cmd["var"] = "after_connect_commands";
after_cnt_cmd["type"] = "text-multi";
- after_cnt_cmd["desc"] = "Custom IRC commands sent after the connection is established with the server.";
+ set_desc(after_cnt_cmd, "Custom IRC commands sent after the connection is established with the server.");
after_cnt_cmd["label"] = "After-connection IRC commands";
for (const auto& command: commands)
{
@@ -364,10 +379,28 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
}
{
+ XmlSubNode throttle_limit(x, "field");
+ throttle_limit["var"] = "throttle_limit";
+ throttle_limit["type"] = "text-single";
+ throttle_limit["label"] = "Throttle limit";
+ XmlSubNode value(throttle_limit, "value");
+ value.set_inner(std::to_string(options.col<Database::ThrottleLimit>()));
+ }
+
+ {
+ XmlSubNode max_history_length(x, "field");
+ max_history_length["var"] = "max_history_length";
+ max_history_length["type"] = "text-single";
+ max_history_length["label"] = "Throttle limit";
+ XmlSubNode value(max_history_length, "value");
+ value.set_inner(std::to_string(options.col<Database::MaxHistoryLength>()));
+ }
+
+ {
XmlSubNode encoding_out(x, "field");
encoding_out["var"] = "encoding_out";
encoding_out["type"] = "text-single";
- encoding_out["desc"] = "The encoding used when sending messages to the IRC server.";
+ set_desc(encoding_out, "The encoding used when sending messages to the IRC server.");
encoding_out["label"] = "Out encoding";
if (!options.col<Database::EncodingOut>().empty())
{
@@ -380,7 +413,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode encoding_in(x, "field");
encoding_in["var"] = "encoding_in";
encoding_in["type"] = "text-single";
- encoding_in["desc"] = "The encoding used to decode message received from the IRC server.";
+ set_desc(encoding_in, "The encoding used to decode message received from the IRC server.");
encoding_in["label"] = "In encoding";
if (!options.col<Database::EncodingIn>().empty())
{
@@ -390,8 +423,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
}
}
-void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node)
+void ConfigureIrcServerStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node)
{
+ auto& biboumi_component = dynamic_cast<BiboumiComponent&>(xmpp_component);
+
const XmlNode* x = command_node.get_child("x", "jabber:x:data");
if (x)
{
@@ -472,6 +507,31 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com
else if (field->get_tag("var") == "realname" && value)
options.col<Database::Realname>() = value->get_inner();
+ else if (field->get_tag("var") == "throttle_limit" && value)
+ {
+ try {
+ options.col<Database::ThrottleLimit>() = std::stol(value->get_inner());
+ } catch (const std::logic_error&) {
+ options.col<Database::ThrottleLimit>() = 10;
+ }
+ Bridge* bridge = biboumi_component.find_user_bridge(session.get_owner_jid());
+ if (bridge)
+ {
+ IrcClient* client = bridge->find_irc_client(server_domain);
+ if (client)
+ client->set_throttle_limit(options.col<Database::ThrottleLimit>());
+ }
+ }
+
+ else if (field->get_tag("var") == "max_history_length" && value)
+ {
+ try {
+ options.col<Database::MaxHistoryLength>() = std::stol(value->get_inner());
+ } catch (const std::logic_error&) {
+ options.col<Database::MaxHistoryLength>() = 20;
+ }
+ }
+
else if (field->get_tag("var") == "encoding_out" && value)
options.col<Database::EncodingOut>() = value->get_inner();
@@ -509,6 +569,7 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester,
auto options = Database::get_irc_channel_options_with_server_default(requester.local + "@" + requester.domain,
iid.get_server(), iid.get_local());
+ node.delete_all_children();
XmlSubNode x(node, "jabber:x:data:x");
x["type"] = "form";
XmlSubNode title(x, "title");
@@ -521,7 +582,7 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester,
record_history["var"] = "record_history";
record_history["type"] = "list-single";
record_history["label"] = "Record history for this channel";
- record_history["desc"] = "If unset, the value is the one configured globally";
+ set_desc(record_history, "If unset, the value is the one configured globally");
{
// Value selected by default
XmlSubNode value(record_history, "value");
@@ -541,7 +602,7 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester,
XmlSubNode encoding_out(x, "field");
encoding_out["var"] = "encoding_out";
encoding_out["type"] = "text-single";
- encoding_out["desc"] = "The encoding used when sending messages to the IRC server. Defaults to the server's “out encoding” if unset for the channel";
+ set_desc(encoding_out, "The encoding used when sending messages to the IRC server. Defaults to the server's “out encoding” if unset for the channel");
encoding_out["label"] = "Out encoding";
if (!options.col<Database::EncodingOut>().empty())
{
@@ -554,7 +615,7 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester,
XmlSubNode encoding_in(x, "field");
encoding_in["var"] = "encoding_in";
encoding_in["type"] = "text-single";
- encoding_in["desc"] = "The encoding used to decode message received from the IRC server. Defaults to the server's “in encoding” if unset for the channel";
+ set_desc(encoding_in, "The encoding used to decode message received from the IRC server. Defaults to the server's “in encoding” if unset for the channel");
encoding_in["label"] = "In encoding";
if (!options.col<Database::EncodingIn>().empty())
{
@@ -567,7 +628,7 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester,
XmlSubNode persistent(x, "field");
persistent["var"] = "persistent";
persistent["type"] = "boolean";
- persistent["desc"] = "If set to true, when all XMPP clients have left this channel, biboumi will stay idle in it, without sending a PART command.";
+ set_desc(persistent, "If set to true, when all XMPP clients have left this channel, biboumi will stay idle in it, without sending a PART command.");
persistent["label"] = "Persistent";
{
XmlSubNode value(persistent, "value");
@@ -847,12 +908,13 @@ void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session,
if (irc->is_using_tls())
ss << " (using TLS)";
const std::time_t now_c = std::chrono::system_clock::to_time_t(irc->connection_date);
+ struct tm tm;
#ifdef HAS_PUT_TIME
- ss << " since " << std::put_time(std::localtime(&now_c), "%F %T");
+ ss << " since " << std::put_time(localtime_r(&now_c, &tm), "%F %T");
#else
constexpr std::size_t timestamp_size{10 + 1 + 8 + 1};
char buf[timestamp_size] = {};
- const auto res = std::strftime(buf, timestamp_size, "%F %T", std::localtime(&now_c));
+ const auto res = std::strftime(buf, timestamp_size, "%F %T", localtime(&now_c, &tm));
if (res > 0)
ss << " since " << buf;
#endif
diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp
index be34873..6fe6972 100644
--- a/src/xmpp/biboumi_component.cpp
+++ b/src/xmpp/biboumi_component.cpp
@@ -102,8 +102,8 @@ void BiboumiComponent::shutdown()
void BiboumiComponent::clean()
{
- auto it = this->bridges.begin();
- while (it != this->bridges.end())
+ auto it = std::begin(this->bridges);
+ while (it != std::end(this->bridges))
{
it->second->clean();
if (it->second->active_clients() == 0)
@@ -185,8 +185,13 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
}
bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "",
from.resource, history_limit, x != nullptr);
- if (!own_nick.empty() && own_nick != to.resource)
- bridge->send_irc_nick_change(iid, to.resource, from.resource);
+ const IrcClient* irc = bridge->find_irc_client(iid.get_server());
+ if (irc)
+ {
+ const auto chan = irc->find_channel(iid.get_local());
+ if (chan->joined)
+ bridge->send_irc_nick_change(iid, to.resource, from.resource);
+ }
}
else if (type == "unavailable")
{
@@ -273,9 +278,10 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
std::string error_type("cancel");
std::string error_name("internal-server-error");
- utils::ScopeGuard stanza_error([this, &from_str, &to_str, &id, &error_type, &error_name](){
+ std::string error_text{};
+ utils::ScopeGuard stanza_error([this, &from_str, &to_str, &id, &error_type, &error_name, &error_text](){
this->send_stanza_error("message", from_str, to_str, id,
- error_type, error_name, "");
+ error_type, error_name, error_text);
});
const XmlNode* body = stanza.get_child("body", COMPONENT_NS);
@@ -284,7 +290,15 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
{
if (body && !body->get_inner().empty())
{
- bridge->send_channel_message(iid, body->get_inner(), id);
+ if (bridge->is_resource_in_chan(iid.to_tuple(), from.resource))
+ bridge->send_channel_message(iid, body->get_inner(), id);
+ else
+ {
+ error_type = "modify";
+ error_name = "not-acceptable";
+ error_text = "You are not a participant in this room.";
+ return;
+ }
}
const XmlNode* subject = stanza.get_child("subject", COMPONENT_NS);
if (subject)
@@ -350,7 +364,6 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
this->send_invitation_from_fulljid(std::to_string(iid), invite_to, from_str);
}
}
-
}
} catch (const IRCNotConnected& ex)
{
@@ -514,7 +527,11 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
{
if (node.empty())
{
- this->send_irc_channel_disco_info(id, from, to_str);
+ const IrcClient* irc_client = bridge->find_irc_client(iid.get_server());
+ const IrcChannel* irc_channel{};
+ if (irc_client)
+ irc_channel = irc_client->find_channel(iid.get_local());
+ this->send_irc_channel_disco_info(id, from, to_str, irc_channel);
stanza_error.disable();
}
else if (node == MUC_TRAFFIC_NS)
@@ -592,7 +609,6 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
const XmlNode* max = set_node->get_child("max", RSM_NS);
if (max)
rs_info.max = std::atoi(max->get_inner().data());
-
}
if (rs_info.max == -1)
rs_info.max = 100;
@@ -751,7 +767,7 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza)
if (limit < 0 || limit > 100)
limit = 100;
auto result = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(),
- limit,
+ static_cast<std::size_t>(limit),
start, end,
reference_record_id, paging_order);
bool complete = std::get<bool>(result);
@@ -964,7 +980,8 @@ void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string& id,
this->send_stanza(iq);
}
-void BiboumiComponent::send_irc_channel_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from)
+void BiboumiComponent::send_irc_channel_disco_info(const std::string& id, const std::string& jid_to,
+ const std::string& jid_from, const IrcChannel* irc_channel)
{
Jid from(jid_from);
Iid iid(from.local, {});
@@ -980,11 +997,31 @@ void BiboumiComponent::send_irc_channel_disco_info(const std::string& id, const
identity["category"] = "conference";
identity["type"] = "irc";
identity["name"] = ""s + iid.get_local() + " on " + iid.get_server();
- for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS, STABLE_MUC_ID_NS})
+ for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS, STABLE_MUC_ID_NS, SELF_PING_FLAG, "muc_nonanonymous"})
{
XmlSubNode feature(query, "feature");
feature["var"] = ns;
}
+
+ XmlSubNode x(query, "x");
+ x["xmlns"] = DATAFORM_NS;
+ x["type"] = "result";
+ {
+ XmlSubNode field(x, "field");
+ field["var"] = "FORM_TYPE";
+ field["type"] = "hidden";
+ XmlSubNode value(field, "value");
+ value.set_inner("http://jabber.org/protocol/muc#roominfo");
+ }
+
+ if (irc_channel && irc_channel->joined)
+ {
+ XmlSubNode field(x, "field");
+ field["var"] = "muc#roominfo_occupants";
+ field["label"] = "Number of occupants";
+ XmlSubNode value(field, "value");
+ value.set_inner(std::to_string(irc_channel->get_users().size()));
+ }
}
this->send_stanza(iq);
}
diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp
index caf990e..f59ed9b 100644
--- a/src/xmpp/biboumi_component.hpp
+++ b/src/xmpp/biboumi_component.hpp
@@ -73,7 +73,8 @@ public:
* http://xmpp.org/extensions/xep-0045.html#impl-service-traffic
*/
void send_irc_channel_muc_traffic_info(const std::string& id, const std::string& jid_to, const std::string& jid_from);
- void send_irc_channel_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from);
+ void send_irc_channel_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from,
+ const IrcChannel* irc_channel);
/**
* Send a ping request
*/
diff --git a/src/xmpp/jid.cpp b/src/xmpp/jid.cpp
index 19d1b55..3c54fd4 100644
--- a/src/xmpp/jid.cpp
+++ b/src/xmpp/jid.cpp
@@ -106,7 +106,7 @@ std::string jidprep(const std::string& original)
--domain_end;
if (domain_end != domain && special_chars.count(domain[0]))
{
- std::memmove(domain, domain + 1, domain_end - domain + 1);
+ std::memmove(domain, domain + 1, static_cast<std::size_t>(domain_end - domain) + 1);
--domain_end;
}
// And if the final result is an empty string, return a dummy hostname
diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp
index b3d925e..f82f9ce 100644
--- a/src/xmpp/xmpp_component.cpp
+++ b/src/xmpp/xmpp_component.cpp
@@ -298,8 +298,8 @@ void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, con
{
XmlSubNode private_node(message, "private");
private_node["xmlns"] = "urn:xmpp:carbons:2";
- XmlSubNode nocopy(message, "no-copy");
- nocopy["xmlns"] = "urn:xmpp:hints";
+ XmlSubNode nocopy_node(message, "no-copy");
+ nocopy_node["xmlns"] = "urn:xmpp:hints";
}
if (muc_private)
{
@@ -340,8 +340,12 @@ void XmppComponent::send_user_join(const std::string& from,
if (self)
{
- XmlSubNode status(x, "status");
- status["code"] = "110";
+ XmlSubNode status_self(x, "status");
+ status_self["code"] = "110";
+ XmlSubNode status_nick_modified(x, "status");
+ status_nick_modified["code"] = "210";
+ XmlSubNode status_nonanonymous(x, "status");
+ status_nonanonymous["code"] = "100";
}
}
this->send_stanza(presence);
@@ -588,8 +592,8 @@ void XmppComponent::send_version(const std::string& id, const std::string& jid_t
name.set_inner("biboumi");
}
{
- XmlSubNode version(query, "version");
- version.set_inner(SOFTWARE_VERSION);
+ XmlSubNode version_node(query, "version");
+ version_node.set_inner(SOFTWARE_VERSION);
}
{
XmlSubNode os(query, "os");
diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp
index e18da40..156e286 100644
--- a/src/xmpp/xmpp_component.hpp
+++ b/src/xmpp/xmpp_component.hpp
@@ -38,6 +38,7 @@
#define MUC_TRAFFIC_NS "http://jabber.org/protocol/muc#traffic"
#define STABLE_ID_NS "urn:xmpp:sid:0"
#define STABLE_MUC_ID_NS "http://jabber.org/protocol/muc#stable_id"
+#define SELF_PING_FLAG MUC_NS"#self-ping-optimization"
/**
* An XMPP component, communicating with an XMPP server using the protocole
diff --git a/src/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp
index 0488be9..781fe4c 100644
--- a/src/xmpp/xmpp_parser.cpp
+++ b/src/xmpp/xmpp_parser.cpp
@@ -20,7 +20,7 @@ static void end_element_handler(void* user_data, const XML_Char* name)
static void character_data_handler(void *user_data, const XML_Char *s, int len)
{
- static_cast<XmppParser*>(user_data)->char_data(s, len);
+ static_cast<XmppParser*>(user_data)->char_data(s, static_cast<std::size_t>(len));
}
/**