1
0
Fork 0
mirror of https://gitlab.com/niansa/asbots.git synced 2025-03-06 20:48:25 +01:00

First basic NickServ implementation has arrived!!!

This commit is contained in:
Nils 2021-06-23 15:35:11 +02:00
parent def0dc6715
commit 773369dab7
8 changed files with 108 additions and 37 deletions

View file

@ -20,7 +20,8 @@ find_package(PkgConfig REQUIRED)
pkg_check_modules(libasync-uv REQUIRED IMPORTED_TARGET libasync-uv) pkg_check_modules(libasync-uv REQUIRED IMPORTED_TARGET libasync-uv)
pkg_check_modules(fmt REQUIRED IMPORTED_TARGET fmt) pkg_check_modules(fmt REQUIRED IMPORTED_TARGET fmt)
pkg_check_modules(sqlite3 REQUIRED IMPORTED_TARGET sqlite3) pkg_check_modules(sqlite3 REQUIRED IMPORTED_TARGET sqlite3)
pkg_check_modules(cryptopp REQUIRED IMPORTED_TARGET cryptopp)
target_link_libraries(asbots PRIVATE PkgConfig::libasync-uv PkgConfig::fmt PkgConfig::sqlite3) target_link_libraries(asbots PRIVATE PkgConfig::libasync-uv PkgConfig::fmt PkgConfig::sqlite3 PkgConfig::cryptopp)
configure_file(config.inil config.inil COPYONLY) configure_file(config.inil config.inil COPYONLY)

View file

@ -14,6 +14,6 @@ description: A test service!!
uid: 23X uid: 23X
end end
[testservice] [NickServ]
channel: #testservice salt: changeme!!!
end end

View file

@ -425,7 +425,7 @@ async::result<void> Instance::process(Event event) {
// Update login name // Update login name
if (event.args.size() > 3) { if (event.args.size() > 3) {
auto loginName = event.args[3]; auto loginName = event.args[3];
if (loginName == "*" || loginName == "0") { if (loginName.empty() || loginName == "*" || loginName == "0") {
res->get()->loginName.reset(); res->get()->loginName.reset();
} else { } else {
res->get()->loginName = event.args[3]; res->get()->loginName = event.args[3];

View file

@ -156,10 +156,10 @@ struct User {
}; };
} }
Event get_encap_su(std::string_view login_name) { Event get_encap_su(std::string_view login_name, const SUID& my_sid) {
//:<my_sid> ENCAP * SU <user_uid> <new_account_name> //:<my_sid> ENCAP * SU <user_uid> <new_account_name>
return { return {
.sender = server, .sender = my_sid,
.name = "ENCAP", .name = "ENCAP",
.raw_args = fmt::format("* SU {} {}", uid.str(), login_name) .raw_args = fmt::format("* SU {} {}", uid.str(), login_name)
}; };

View file

@ -59,11 +59,17 @@ async::result<void> ServiceBase::on_direct_privmsg(std::string_view msg, u_User&
co_await i->send_event(user.get_notice("Error: Command not found", author->uid)); co_await i->send_event(user.get_notice("Error: Command not found", author->uid));
} else { } else {
split.erase(split.begin()); split.erase(split.begin());
auto& [fnc, usage, minArgs] = res->second; auto& [fnc, usage, minArgs, requiresAuth] = res->second;
if (split.size() < minArgs) { if (requiresAuth && !author->loginName.has_value()) {
co_await i->send_event(user.get_notice("Error: Usage: {}"_format(usage), author->uid)); co_await i->send_event(user.get_notice("Error: You need to identify first", author->uid));
} else if (split.size() < minArgs) {
co_await i->send_event(user.get_notice("Usage: {}"_format(usage), author->uid));
} else { } else {
co_await fnc(author, split); co_await fnc(author, split);
} }
} }
} }
async::result<void> ServiceBase::on_event(const Event& event) {
co_return;
}

View file

@ -38,10 +38,10 @@ public:
UUID uuid; UUID uuid;
Instance *i; Instance *i;
bool ready = false; bool ready = false;
std::unordered_map<std::string_view, std::tuple<std::function<async::result<void> (u_User& sender, const std::vector<std::string_view>& args)>, std::string_view, size_t>> commands; std::unordered_map<std::string_view, std::tuple<std::function<async::result<void> (u_User& sender, const std::vector<std::string_view>& args)>, std::string_view, size_t, bool>> commands;
virtual async::result<void> intitialize() = 0; virtual async::result<void> intitialize() = 0;
virtual async::result<void> on_event(const Event& event) = 0; virtual async::result<void> on_event(const Event& event);
virtual async::result<void> on_direct_privmsg(std::string_view msg, u_User& author); virtual async::result<void> on_direct_privmsg(std::string_view msg, u_User& author);
async::result<void> mark_ready(const User& user); async::result<void> mark_ready(const User& user);

View file

@ -2,6 +2,9 @@
#include "../instance.hpp" #include "../instance.hpp"
#include <async/result.hpp> #include <async/result.hpp>
#include <cryptopp/hex.h>
#include <cryptopp/sha.h>
#include <cryptopp/pwdbased.h>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@ -26,35 +29,95 @@ async::result<void> NickServ::intitialize() {
make_table("users", make_table("users",
make_column("id", &Account::id, autoincrement(), primary_key()), make_column("id", &Account::id, autoincrement(), primary_key()),
make_column("name", &Account::name), make_column("name", &Account::name),
make_column("email", &Account::email),
make_column("password_hash", &Account::password_hash)) make_column("password_hash", &Account::password_hash))
)); ));
storage->sync_schema(); storage->sync_schema();
commands = { commands = {
{"test", {[this] (u_User& sender, std::vector<std::string_view> args) -> async::result<void> {
co_await i->send_event(user.get_notice("Hello world! Works", sender->uid));
}, "", 0}
},
{"identify", {[this] (u_User& sender, const std::vector<std::string_view>& args) -> async::result<void> { {"identify", {[this] (u_User& sender, const std::vector<std::string_view>& args) -> async::result<void> {
// Get user from storage // Check that user is not already identified
auto accounts_found = storage->get_all<Account>(where(c(&Account::name) == sender->nick)); if (sender->loginName.has_value()) {
if (accounts_found.empty()) { co_await i->send_event(user.get_notice("Error: You've already been identified!", sender->uid));
co_await i->send_event(user.get_notice("Error: Your nick is not registered!", sender->uid)); co_return;
co_return; }
} // Get account name and password to use
auto& account = accounts_found[0]; std::string_view accountName, accountPwd;
// Check password if (args.size() > 1) {
if (account.password_hash != args[0]) { accountName = args[0];
co_await i->send_event(user.get_notice("Error: Invalid password!", sender->uid)); accountPwd = args[1];
co_return; } else {
} accountName = sender->nick;
// Login accountPwd = args[0];
co_await i->send_event(sender->get_encap_su(account.name)); }
}, "<password>", 1} // Get account from storage
} auto accounts_found = storage->get_all<Account>(where(c(&Account::name) == std::string(accountName)));
if (accounts_found.empty()) {
co_await i->send_event(user.get_notice("Error: '"+std::string(accountName)+"' is not registered!", sender->uid));
co_return;
}
auto& account = accounts_found[0];
// Check password
if (account.password_hash != pwdHash(accountPwd)) {
co_await i->send_event(user.get_notice("Error: Invalid password!", sender->uid));
co_return;
}
// Login
co_await i->send_event(sender->get_encap_su(account.name, i->config.server.uid));
// Report success
co_await i->send_event(user.get_notice("You have been identified!", sender->uid));
}, "[username] <password>", 1, false}
},
{"deidentify", {[this] (u_User& sender, const std::vector<std::string_view>& args) -> async::result<void> {
co_await i->send_event(sender->get_encap_su("", i->config.server.uid));
co_await i->send_event(user.get_notice("You have been deidentified!", sender->uid));
}, "", 0, true}
},
{"register", {[this] (u_User& sender, std::vector<std::string_view> args) -> async::result<void> {
// Check args
if (args.size() != 2) {
co_await i->send_event(user.get_notice("Error: Passwords may not contain spaces!", sender->uid));
co_return;
}
// Check that user does not exist already
{
auto accounts_found = storage->get_all<Account>(where(c(&Account::name) == sender->nick));
if (!accounts_found.empty()) {
co_await i->send_event(user.get_notice("Error: This nick has already been registered!", sender->uid));
co_return;
}
}
// Insert new account into database
storage->insert(Account{
.name = sender->nick,
.email = std::string(args[1]),
.password_hash = pwdHash(args[0])
});
// Report success
co_await i->send_event(user.get_notice("Your nickname has been registered!", sender->uid));
}, "<password> <email>", 2, false}
},
}; };
} }
async::result<void> NickServ::on_event(const Event& event) { std::string NickServ::pwdHash(std::string_view pwd) {
co_return; using namespace CryptoPP;
auto salt = getConfig("NickServ", "salt").value_or("defaultsalt");
static_assert(sizeof(pwd[0]) == 1, "std::string_view character width must be 1 for this to work... You might have a bad STL implementation.");
int c = 1;
byte derived[20];
PKCS5_PBKDF2_HMAC<SHA1> pbkdf2;
pbkdf2.DeriveKey(derived, sizeof(derived), 0, reinterpret_cast<const byte*>(pwd.data()), pwd.size(), reinterpret_cast<const byte*>(salt.data()), salt.size(), c);
std::string fres;
HexEncoder encoder(new StringSink(fres));
encoder.Put(derived, sizeof(derived));
encoder.MessageEnd();
return fres;
} }

View file

@ -29,11 +29,11 @@
struct Account { struct Account {
size_t id; int id = -1;
std::string name, password_hash; std::string name, email, password_hash;
}; };
using Storage = sqlite_orm::internal::storage_t<sqlite_orm::internal::table_t<Account, sqlite_orm::internal::column_t<Account, long unsigned int, const long unsigned int& (Account::*)() const, void (Account::*)(long unsigned int), sqlite_orm::constraints::autoincrement_t, sqlite_orm::constraints::primary_key_t<> >, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)> > >; using Storage = sqlite_orm::internal::storage_t<sqlite_orm::internal::table_t<Account, sqlite_orm::internal::column_t<Account, int, const int& (Account::*)() const, void (Account::*)(int), sqlite_orm::constraints::autoincrement_t, sqlite_orm::constraints::primary_key_t<> >, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)> > >;
class NickServ : public ServiceBase { class NickServ : public ServiceBase {
std::unique_ptr<Storage> storage; std::unique_ptr<Storage> storage;
@ -41,6 +41,7 @@ class NickServ : public ServiceBase {
User user; User user;
virtual async::result<void> intitialize() override; virtual async::result<void> intitialize() override;
virtual async::result<void> on_event(const Event& event) override;
std::string pwdHash(std::string_view pwd);
}; };
#endif #endif