From 773369dab7850f82115853e82df7075b0cb1c9f7 Mon Sep 17 00:00:00 2001 From: Nils Date: Wed, 23 Jun 2021 15:35:11 +0200 Subject: [PATCH] First basic NickServ implementation has arrived!!! --- CMakeLists.txt | 3 +- config.inil | 4 +- instance.cpp | 2 +- instance.hpp | 4 +- serviceBase.cpp | 12 +++-- serviceBase.hpp | 4 +- services/nickserv.cpp | 107 +++++++++++++++++++++++++++++++++--------- services/nickserv.hpp | 9 ++-- 8 files changed, 108 insertions(+), 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cb6ceb..4d3c0f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,8 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(libasync-uv REQUIRED IMPORTED_TARGET libasync-uv) pkg_check_modules(fmt REQUIRED IMPORTED_TARGET fmt) 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) diff --git a/config.inil b/config.inil index 63804d3..9a03428 100644 --- a/config.inil +++ b/config.inil @@ -14,6 +14,6 @@ description: A test service!! uid: 23X end -[testservice] -channel: #testservice +[NickServ] +salt: changeme!!! end diff --git a/instance.cpp b/instance.cpp index 15f003f..f8ccce1 100644 --- a/instance.cpp +++ b/instance.cpp @@ -425,7 +425,7 @@ async::result Instance::process(Event event) { // Update login name if (event.args.size() > 3) { auto loginName = event.args[3]; - if (loginName == "*" || loginName == "0") { + if (loginName.empty() || loginName == "*" || loginName == "0") { res->get()->loginName.reset(); } else { res->get()->loginName = event.args[3]; diff --git a/instance.hpp b/instance.hpp index 31061b3..3dd07ea 100644 --- a/instance.hpp +++ b/instance.hpp @@ -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) { //: ENCAP * SU return { - .sender = server, + .sender = my_sid, .name = "ENCAP", .raw_args = fmt::format("* SU {} {}", uid.str(), login_name) }; diff --git a/serviceBase.cpp b/serviceBase.cpp index 3aceeaf..1f28c15 100644 --- a/serviceBase.cpp +++ b/serviceBase.cpp @@ -59,11 +59,17 @@ async::result ServiceBase::on_direct_privmsg(std::string_view msg, u_User& co_await i->send_event(user.get_notice("Error: Command not found", author->uid)); } else { split.erase(split.begin()); - auto& [fnc, usage, minArgs] = res->second; - if (split.size() < minArgs) { - co_await i->send_event(user.get_notice("Error: Usage: {}"_format(usage), author->uid)); + auto& [fnc, usage, minArgs, requiresAuth] = res->second; + if (requiresAuth && !author->loginName.has_value()) { + 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 { co_await fnc(author, split); } } } + +async::result ServiceBase::on_event(const Event& event) { + co_return; +} diff --git a/serviceBase.hpp b/serviceBase.hpp index 0ebd7bc..53a095c 100644 --- a/serviceBase.hpp +++ b/serviceBase.hpp @@ -38,10 +38,10 @@ public: UUID uuid; Instance *i; bool ready = false; - std::unordered_map (u_User& sender, const std::vector& args)>, std::string_view, size_t>> commands; + std::unordered_map (u_User& sender, const std::vector& args)>, std::string_view, size_t, bool>> commands; virtual async::result intitialize() = 0; - virtual async::result on_event(const Event& event) = 0; + virtual async::result on_event(const Event& event); virtual async::result on_direct_privmsg(std::string_view msg, u_User& author); async::result mark_ready(const User& user); diff --git a/services/nickserv.cpp b/services/nickserv.cpp index 2ebb10d..4042cd0 100644 --- a/services/nickserv.cpp +++ b/services/nickserv.cpp @@ -2,6 +2,9 @@ #include "../instance.hpp" #include +#include +#include +#include #include #include #include @@ -26,35 +29,95 @@ async::result NickServ::intitialize() { make_table("users", make_column("id", &Account::id, autoincrement(), primary_key()), make_column("name", &Account::name), + make_column("email", &Account::email), make_column("password_hash", &Account::password_hash)) )); storage->sync_schema(); commands = { - {"test", {[this] (u_User& sender, std::vector args) -> async::result { - co_await i->send_event(user.get_notice("Hello world! Works", sender->uid)); - }, "", 0} - }, {"identify", {[this] (u_User& sender, const std::vector& args) -> async::result { - // Get user from storage - auto accounts_found = storage->get_all(where(c(&Account::name) == sender->nick)); - if (accounts_found.empty()) { - co_await i->send_event(user.get_notice("Error: Your nick is not registered!", sender->uid)); - co_return; - } - auto& account = accounts_found[0]; - // Check password - if (account.password_hash != args[0]) { - 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)); - }, "", 1} - } + // Check that user is not already identified + if (sender->loginName.has_value()) { + co_await i->send_event(user.get_notice("Error: You've already been identified!", sender->uid)); + co_return; + } + // Get account name and password to use + std::string_view accountName, accountPwd; + if (args.size() > 1) { + accountName = args[0]; + accountPwd = args[1]; + } else { + accountName = sender->nick; + accountPwd = args[0]; + } + // Get account from storage + auto accounts_found = storage->get_all(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] ", 1, false} + }, + {"deidentify", {[this] (u_User& sender, const std::vector& args) -> async::result { + 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 args) -> async::result { + // 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(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)); + }, " ", 2, false} + }, }; } -async::result NickServ::on_event(const Event& event) { - co_return; +std::string NickServ::pwdHash(std::string_view pwd) { + 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 pbkdf2; + pbkdf2.DeriveKey(derived, sizeof(derived), 0, reinterpret_cast(pwd.data()), pwd.size(), reinterpret_cast(salt.data()), salt.size(), c); + + std::string fres; + HexEncoder encoder(new StringSink(fres)); + + encoder.Put(derived, sizeof(derived)); + encoder.MessageEnd(); + + return fres; } diff --git a/services/nickserv.hpp b/services/nickserv.hpp index c5625a9..cdd5028 100644 --- a/services/nickserv.hpp +++ b/services/nickserv.hpp @@ -29,11 +29,11 @@ struct Account { - size_t id; - std::string name, password_hash; + int id = -1; + std::string name, email, password_hash; }; -using Storage = sqlite_orm::internal::storage_t >, sqlite_orm::internal::column_t, std::allocator >, const std::__cxx11::basic_string, std::allocator >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string, std::allocator >)>, sqlite_orm::internal::column_t, std::allocator >, const std::__cxx11::basic_string, std::allocator >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string, std::allocator >)> > >; +using Storage = sqlite_orm::internal::storage_t >, sqlite_orm::internal::column_t, std::allocator >, const std::__cxx11::basic_string, std::allocator >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string, std::allocator >)>, sqlite_orm::internal::column_t, std::allocator >, const std::__cxx11::basic_string, std::allocator >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string, std::allocator >)>, sqlite_orm::internal::column_t, std::allocator >, const std::__cxx11::basic_string, std::allocator >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string, std::allocator >)> > >; class NickServ : public ServiceBase { std::unique_ptr storage; @@ -41,6 +41,7 @@ class NickServ : public ServiceBase { User user; virtual async::result intitialize() override; - virtual async::result on_event(const Event& event) override; + + std::string pwdHash(std::string_view pwd); }; #endif