Implement SASL plain authentication
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
struct Capability
|
||||
{
|
||||
std::function<void()> on_ack;
|
||||
std::function<void()> on_nack;
|
||||
};
|
||||
+105
-13
@@ -5,6 +5,7 @@
|
||||
#include <irc/irc_client.hpp>
|
||||
#include <bridge/bridge.hpp>
|
||||
#include <irc/irc_user.hpp>
|
||||
#include <utils/base64.hpp>
|
||||
|
||||
#include <logger/logger.hpp>
|
||||
#include <config/config.hpp>
|
||||
@@ -81,6 +82,11 @@ static const std::unordered_map<std::string,
|
||||
{"PONG", {&IrcClient::on_pong, {0, 0}}},
|
||||
{"KICK", {&IrcClient::on_kick, {3, 0}}},
|
||||
{"INVITE", {&IrcClient::on_invite, {2, 0}}},
|
||||
{"CAP", {&IrcClient::on_cap, {3, 0}}},
|
||||
{"AUTHENTICATE", {&IrcClient::on_authenticate, {1, 0}}},
|
||||
{"903", {&IrcClient::on_sasl_success, {0, 0}}},
|
||||
{"900", {&IrcClient::on_sasl_login, {3, 0}}},
|
||||
|
||||
|
||||
{"401", {&IrcClient::on_generic_error, {2, 0}}},
|
||||
{"402", {&IrcClient::on_generic_error, {2, 0}}},
|
||||
@@ -272,18 +278,43 @@ void IrcClient::on_connected()
|
||||
}
|
||||
}
|
||||
|
||||
this->send_message({"CAP", {"REQ", "multi-prefix"}});
|
||||
this->send_message({"CAP", {"END"}});
|
||||
this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + ".");
|
||||
|
||||
this->capabilities["multi-prefix"] = {[]{}, []{}};
|
||||
|
||||
#ifdef USE_DATABASE
|
||||
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
|
||||
this->get_hostname());
|
||||
if (!options.col<Database::Pass>().empty())
|
||||
|
||||
const auto& sasl_password = options.col<Database::SaslPassword>();
|
||||
const auto& server_password = options.col<Database::Pass>();
|
||||
|
||||
if (!server_password.empty())
|
||||
this->send_pass_command(options.col<Database::Pass>());
|
||||
|
||||
if (!sasl_password.empty())
|
||||
{
|
||||
this->capabilities["sasl"] = {
|
||||
[this]
|
||||
{
|
||||
this->send_message({"AUTHENTICATE", {"PLAIN"}});
|
||||
log_warning("negociating SASL now...");
|
||||
},
|
||||
[]
|
||||
{
|
||||
log_warning("SASL not supported by the server, disconnecting.");
|
||||
}
|
||||
};
|
||||
this->sasl_state = SaslState::needed;
|
||||
}
|
||||
#endif
|
||||
|
||||
this->send_nick_command(this->current_nick);
|
||||
{
|
||||
for (const auto &pair : this->capabilities)
|
||||
this->send_message({ "CAP", {"REQ", pair.first}});
|
||||
}
|
||||
|
||||
this->send_nick_command(this->current_nick);
|
||||
#ifdef USE_DATABASE
|
||||
if (Config::get("realname_customization", "true") == "true")
|
||||
{
|
||||
@@ -298,9 +329,6 @@ void IrcClient::on_connected()
|
||||
#else
|
||||
this->send_user_command(this->username, this->realname);
|
||||
#endif
|
||||
this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + ".");
|
||||
this->send_pending_data();
|
||||
this->bridge.on_irc_client_connected(this->get_hostname());
|
||||
}
|
||||
|
||||
void IrcClient::on_connection_close(const std::string& error_msg)
|
||||
@@ -371,12 +399,14 @@ void IrcClient::parse_in_buffer(const size_t)
|
||||
{
|
||||
const auto& limits = it->second.second;
|
||||
// Check that the Message is well formed before actually calling
|
||||
// the callback. limits.first is the min number of arguments,
|
||||
// second is the max
|
||||
if (message.arguments.size() < limits.first ||
|
||||
(limits.second > 0 && message.arguments.size() > limits.second))
|
||||
// the callback.
|
||||
const auto args_size = message.arguments.size();
|
||||
const auto min = limits.first;
|
||||
const auto max = limits.second;
|
||||
if (args_size < min ||
|
||||
(max > 0 && args_size > max))
|
||||
log_warning("Invalid number of arguments for IRC command “", message.command,
|
||||
"”: ", message.arguments.size());
|
||||
"”: ", args_size);
|
||||
else
|
||||
{
|
||||
const auto& cb = it->second.first;
|
||||
@@ -1266,7 +1296,7 @@ void IrcClient::on_unknown_message(const IrcMessage& message)
|
||||
return ;
|
||||
std::string from = message.prefix;
|
||||
std::stringstream ss;
|
||||
for (auto it = message.arguments.begin() + 1; it != message.arguments.end(); ++it)
|
||||
for (auto it = std::next(message.arguments.begin()); it != message.arguments.end(); ++it)
|
||||
{
|
||||
ss << *it;
|
||||
if (it + 1 != message.arguments.end())
|
||||
@@ -1299,3 +1329,65 @@ long int IrcClient::get_throttle_limit() const
|
||||
return 10;
|
||||
#endif
|
||||
}
|
||||
|
||||
void IrcClient::on_cap(const IrcMessage &message)
|
||||
{
|
||||
const auto& sub_command = message.arguments[1];
|
||||
const auto& cap = message.arguments[2];
|
||||
auto it = this->capabilities.find(cap);
|
||||
if (it == this->capabilities.end())
|
||||
{
|
||||
log_warning("Received a CAP message for something we didn’t ask, or that we already handled.");
|
||||
return;
|
||||
}
|
||||
Capability& capability = it->second;
|
||||
if (sub_command == "ACK")
|
||||
capability.on_ack();
|
||||
else if (sub_command == "NACK")
|
||||
capability.on_nack();
|
||||
this->capabilities.erase(it);
|
||||
this->cap_end();
|
||||
}
|
||||
|
||||
void IrcClient::on_authenticate(const IrcMessage &)
|
||||
{
|
||||
if (this->sasl_state == SaslState::unneeded)
|
||||
{
|
||||
log_warning("Received an AUTHENTICATE command but we don’t intend to authenticate…");
|
||||
return;
|
||||
}
|
||||
#ifdef USE_DATABASE
|
||||
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
|
||||
this->get_hostname());
|
||||
const auto auth_string = '\0' + options.col<Database::Nick>() + '\0' + options.col<Database::SaslPassword>();
|
||||
const auto base64_auth_string = base64::encode(auth_string);
|
||||
this->send_message({"AUTHENTICATE", {base64_auth_string}});
|
||||
#endif
|
||||
}
|
||||
|
||||
void IrcClient::on_sasl_success(const IrcMessage &)
|
||||
{
|
||||
this->sasl_state = SaslState::success;
|
||||
this->cap_end();
|
||||
}
|
||||
|
||||
void IrcClient::on_sasl_login(const IrcMessage &message)
|
||||
{
|
||||
const auto& login = message.arguments[2];
|
||||
std::string text = "Your are now logged in as " + login;
|
||||
if (message.arguments.size() > 3)
|
||||
text = message.arguments[3];
|
||||
this->bridge.send_xmpp_message(this->hostname, message.prefix, text);
|
||||
}
|
||||
|
||||
void IrcClient::cap_end()
|
||||
{
|
||||
if (!this->capabilities.empty())
|
||||
return;
|
||||
// If we are currently authenticating through sasl, finish that before sending CAP END
|
||||
if (this->sasl_state == SaslState::needed)
|
||||
return;
|
||||
|
||||
this->send_message({"CAP", {"END"}});
|
||||
this->bridge.on_irc_client_connected(this->get_hostname());
|
||||
}
|
||||
|
||||
+19
-1
@@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <irc/irc_message.hpp>
|
||||
#include <irc/irc_channel.hpp>
|
||||
#include <irc/capability.hpp>
|
||||
#include <irc/sasl.hpp>
|
||||
#include <irc/iid.hpp>
|
||||
|
||||
#include <bridge/history_limit.hpp>
|
||||
@@ -231,6 +232,17 @@ public:
|
||||
* The IRC server is confirming that the invitation has been forwarded
|
||||
*/
|
||||
void on_invited(const IrcMessage& message);
|
||||
/**
|
||||
* The IRC server sends a CAP message, as part of capabilities negociation. It could be a ACK,
|
||||
* NACK, or something else
|
||||
*/
|
||||
void on_cap(const IrcMessage& message);
|
||||
private:
|
||||
void cap_end();
|
||||
public:
|
||||
void on_authenticate(const IrcMessage& message);
|
||||
void on_sasl_success(const IrcMessage& message);
|
||||
void on_sasl_login(const IrcMessage& message);
|
||||
/**
|
||||
* The channel has been completely joined (self presence, topic, all names
|
||||
* received etc), send the self presence and topic to the XMPP user.
|
||||
@@ -359,6 +371,12 @@ private:
|
||||
* has been established, we are authentified and we have a nick)
|
||||
*/
|
||||
bool welcomed;
|
||||
/**
|
||||
* Whether or not we are trying to authenticate using sasl. If this is true we need to wait for a
|
||||
* successful auth
|
||||
*/
|
||||
SaslState sasl_state{SaslState::unneeded};
|
||||
std::map<std::string, Capability> capabilities;
|
||||
/**
|
||||
* See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt section 3.3
|
||||
* We store the possible chanmodes in this object.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
enum class SaslState
|
||||
{
|
||||
unneeded,
|
||||
needed,
|
||||
failure,
|
||||
success,
|
||||
};
|
||||
Reference in New Issue
Block a user