From 6302f980fe6dad83aab7d2dc1046dadea5ffd939 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 14 Dec 2015 16:52:24 -0500 Subject: New source tree structure for modules. From commands/cs_access => chanserv/access etc. --- modules/extra/ldap.cpp | 623 ++++++++++++++++++++++++++++++ modules/extra/ldap_authentication.cpp | 302 +++++++++++++++ modules/extra/ldap_oper.cpp | 134 +++++++ modules/extra/m_ldap.cpp | 623 ------------------------------ modules/extra/m_ldap_authentication.cpp | 302 --------------- modules/extra/m_ldap_oper.cpp | 134 ------- modules/extra/m_mysql.cpp | 614 ----------------------------- modules/extra/m_sasl_dh-aes.cpp | 184 --------- modules/extra/m_sasl_dh-blowfish.cpp | 194 ---------- modules/extra/m_sql_authentication.cpp | 143 ------- modules/extra/m_sql_log.cpp | 110 ------ modules/extra/m_sql_oper.cpp | 148 ------- modules/extra/m_sqlite.cpp | 380 ------------------ modules/extra/m_ssl_gnutls.cpp | 658 -------------------------------- modules/extra/m_ssl_openssl.cpp | 416 -------------------- modules/extra/mysql.cpp | 614 +++++++++++++++++++++++++++++ modules/extra/sasl_dh-aes.cpp | 184 +++++++++ modules/extra/sasl_dh-blowfish.cpp | 194 ++++++++++ modules/extra/sql_authentication.cpp | 143 +++++++ modules/extra/sql_log.cpp | 110 ++++++ modules/extra/sql_oper.cpp | 148 +++++++ modules/extra/sqlite.cpp | 380 ++++++++++++++++++ modules/extra/ssl_gnutls.cpp | 658 ++++++++++++++++++++++++++++++++ modules/extra/ssl_openssl.cpp | 416 ++++++++++++++++++++ modules/extra/stats/chanstats.cpp | 648 +++++++++++++++++++++++++++++++ modules/extra/stats/m_chanstats.cpp | 648 ------------------------------- 26 files changed, 4554 insertions(+), 4554 deletions(-) create mode 100644 modules/extra/ldap.cpp create mode 100644 modules/extra/ldap_authentication.cpp create mode 100644 modules/extra/ldap_oper.cpp delete mode 100644 modules/extra/m_ldap.cpp delete mode 100644 modules/extra/m_ldap_authentication.cpp delete mode 100644 modules/extra/m_ldap_oper.cpp delete mode 100644 modules/extra/m_mysql.cpp delete mode 100644 modules/extra/m_sasl_dh-aes.cpp delete mode 100644 modules/extra/m_sasl_dh-blowfish.cpp delete mode 100644 modules/extra/m_sql_authentication.cpp delete mode 100644 modules/extra/m_sql_log.cpp delete mode 100644 modules/extra/m_sql_oper.cpp delete mode 100644 modules/extra/m_sqlite.cpp delete mode 100644 modules/extra/m_ssl_gnutls.cpp delete mode 100644 modules/extra/m_ssl_openssl.cpp create mode 100644 modules/extra/mysql.cpp create mode 100644 modules/extra/sasl_dh-aes.cpp create mode 100644 modules/extra/sasl_dh-blowfish.cpp create mode 100644 modules/extra/sql_authentication.cpp create mode 100644 modules/extra/sql_log.cpp create mode 100644 modules/extra/sql_oper.cpp create mode 100644 modules/extra/sqlite.cpp create mode 100644 modules/extra/ssl_gnutls.cpp create mode 100644 modules/extra/ssl_openssl.cpp create mode 100644 modules/extra/stats/chanstats.cpp delete mode 100644 modules/extra/stats/m_chanstats.cpp (limited to 'modules/extra') diff --git a/modules/extra/ldap.cpp b/modules/extra/ldap.cpp new file mode 100644 index 000000000..71c31b9ac --- /dev/null +++ b/modules/extra/ldap.cpp @@ -0,0 +1,623 @@ +/* + * + * (C) 2011-2015 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +/* RequiredLibraries: ldap_r,lber */ +/* RequiredWindowsLibraries: libldap_r,liblber */ + +#include "module.h" +#include "modules/ldap.h" +#include + +class LDAPService; +static Pipe *me; + +class LDAPRequest +{ + public: + LDAPService *service; + LDAPInterface *inter; + LDAPMessage *message; /* message returned by ldap_ */ + LDAPResult *result; /* final result */ + struct timeval tv; + QueryType type; + + LDAPRequest(LDAPService *s, LDAPInterface *i) + : service(s) + , inter(i) + , message(NULL) + , result(NULL) + { + type = QUERY_UNKNOWN; + tv.tv_sec = 0; + tv.tv_usec = 100000; + } + + virtual ~LDAPRequest() + { + delete result; + if (inter != NULL) + inter->OnDelete(); + if (message != NULL) + ldap_msgfree(message); + } + + virtual int run() = 0; +}; + +class LDAPBind : public LDAPRequest +{ + Anope::string who, pass; + + public: + LDAPBind(LDAPService *s, LDAPInterface *i, const Anope::string &w, const Anope::string &p) + : LDAPRequest(s, i) + , who(w) + , pass(p) + { + type = QUERY_BIND; + } + + int run() anope_override; +}; + +class LDAPSearch : public LDAPRequest +{ + Anope::string base; + Anope::string filter; + + public: + LDAPSearch(LDAPService *s, LDAPInterface *i, const Anope::string &b, const Anope::string &f) + : LDAPRequest(s, i) + , base(b) + , filter(f) + { + type = QUERY_SEARCH; + } + + int run() anope_override; +}; + +class LDAPAdd : public LDAPRequest +{ + Anope::string dn; + LDAPMods attributes; + + public: + LDAPAdd(LDAPService *s, LDAPInterface *i, const Anope::string &d, const LDAPMods &attr) + : LDAPRequest(s, i) + , dn(d) + , attributes(attr) + { + type = QUERY_ADD; + } + + int run() anope_override; +}; + +class LDAPDel : public LDAPRequest +{ + Anope::string dn; + + public: + LDAPDel(LDAPService *s, LDAPInterface *i, const Anope::string &d) + : LDAPRequest(s, i) + , dn(d) + { + type = QUERY_DELETE; + } + + int run() anope_override; +}; + +class LDAPModify : public LDAPRequest +{ + Anope::string base; + LDAPMods attributes; + + public: + LDAPModify(LDAPService *s, LDAPInterface *i, const Anope::string &b, const LDAPMods &attr) + : LDAPRequest(s, i) + , base(b) + , attributes(attr) + { + type = QUERY_MODIFY; + } + + int run() anope_override; +}; + +class LDAPService : public LDAPProvider, public Thread, public Condition +{ + Anope::string server; + Anope::string admin_binddn; + Anope::string admin_pass; + + LDAP *con; + + time_t last_connect; + + public: + static LDAPMod **BuildMods(const LDAPMods &attributes) + { + LDAPMod **mods = new LDAPMod*[attributes.size() + 1]; + memset(mods, 0, sizeof(LDAPMod*) * (attributes.size() + 1)); + for (unsigned x = 0; x < attributes.size(); ++x) + { + const LDAPModification &l = attributes[x]; + mods[x] = new LDAPMod(); + + if (l.op == LDAPModification::LDAP_ADD) + mods[x]->mod_op = LDAP_MOD_ADD; + else if (l.op == LDAPModification::LDAP_DEL) + mods[x]->mod_op = LDAP_MOD_DELETE; + else if (l.op == LDAPModification::LDAP_REPLACE) + mods[x]->mod_op = LDAP_MOD_REPLACE; + else if (l.op != 0) + throw LDAPException("Unknown LDAP operation"); + mods[x]->mod_type = strdup(l.name.c_str()); + mods[x]->mod_values = new char*[l.values.size() + 1]; + memset(mods[x]->mod_values, 0, sizeof(char *) * (l.values.size() + 1)); + for (unsigned j = 0, c = 0; j < l.values.size(); ++j) + if (!l.values[j].empty()) + mods[x]->mod_values[c++] = strdup(l.values[j].c_str()); + } + return mods; + } + + static void FreeMods(LDAPMod **mods) + { + for (int i = 0; mods[i] != NULL; ++i) + { + free(mods[i]->mod_type); + for (int j = 0; mods[i]->mod_values[j] != NULL; ++j) + free(mods[i]->mod_values[j]); + delete [] mods[i]->mod_values; + } + delete [] mods; + } + + private: + void Connect() + { + int i = ldap_initialize(&this->con, this->server.c_str()); + if (i != LDAP_SUCCESS) + throw LDAPException("Unable to connect to LDAP service " + this->name + ": " + ldap_err2string(i)); + + const int version = LDAP_VERSION3; + i = ldap_set_option(this->con, LDAP_OPT_PROTOCOL_VERSION, &version); + if (i != LDAP_OPT_SUCCESS) + throw LDAPException("Unable to set protocol version for " + this->name + ": " + ldap_err2string(i)); + + const struct timeval tv = { 0, 0 }; + i = ldap_set_option(this->con, LDAP_OPT_NETWORK_TIMEOUT, &tv); + if (i != LDAP_OPT_SUCCESS) + throw LDAPException("Unable to set timeout for " + this->name + ": " + ldap_err2string(i)); + } + + void Reconnect() + { + /* Only try one connect a minute. It is an expensive blocking operation */ + if (last_connect > Anope::CurTime - 60) + throw LDAPException("Unable to connect to LDAP service " + this->name + ": reconnecting too fast"); + last_connect = Anope::CurTime; + + ldap_unbind_ext(this->con, NULL, NULL); + + Connect(); + } + + void QueueRequest(LDAPRequest *r) + { + this->Lock(); + this->queries.push_back(r); + this->Wakeup(); + this->Unlock(); + } + + public: + typedef std::vector query_queue; + query_queue queries, results; + Mutex process_mutex; /* held when processing requests not in either queue */ + + LDAPService(Module *o, const Anope::string &n, const Anope::string &s, const Anope::string &b, const Anope::string &p) : LDAPProvider(o, n), server(s), admin_binddn(b), admin_pass(p), last_connect(0) + { + Connect(); + } + + ~LDAPService() + { + /* At this point the thread has stopped so we don't need to hold process_mutex */ + + this->Lock(); + + for (unsigned int i = 0; i < this->queries.size(); ++i) + { + LDAPRequest *req = this->queries[i]; + + /* queries have no results yet */ + req->result = new LDAPResult(); + req->result->type = req->type; + req->result->error = "LDAP Interface is going away"; + if (req->inter) + req->inter->OnError(*req->result); + + delete req; + } + this->queries.clear(); + + for (unsigned int i = 0; i < this->results.size(); ++i) + { + LDAPRequest *req = this->results[i]; + + /* even though this may have already finished successfully we return that it didn't */ + req->result->error = "LDAP Interface is going away"; + if (req->inter) + req->inter->OnError(*req->result); + + delete req; + } + + this->Unlock(); + + ldap_unbind_ext(this->con, NULL, NULL); + } + + void BindAsAdmin(LDAPInterface *i) override + { + this->Bind(i, this->admin_binddn, this->admin_pass); + } + + void Bind(LDAPInterface *i, const Anope::string &who, const Anope::string &pass) override + { + LDAPBind *b = new LDAPBind(this, i, who, pass); + QueueRequest(b); + } + + void Search(LDAPInterface *i, const Anope::string &base, const Anope::string &filter) override + { + if (i == NULL) + throw LDAPException("No interface"); + + LDAPSearch *s = new LDAPSearch(this, i, base, filter); + QueueRequest(s); + } + + void Add(LDAPInterface *i, const Anope::string &dn, LDAPMods &attributes) override + { + LDAPAdd *add = new LDAPAdd(this, i, dn, attributes); + QueueRequest(add); + } + + void Del(LDAPInterface *i, const Anope::string &dn) anope_override + { + LDAPDel *del = new LDAPDel(this, i, dn); + QueueRequest(del); + } + + void Modify(LDAPInterface *i, const Anope::string &base, LDAPMods &attributes) anope_override + { + LDAPModify *mod = new LDAPModify(this, i, base, attributes); + QueueRequest(mod); + } + + private: + void BuildReply(int res, LDAPRequest *req) + { + LDAPResult *ldap_result = req->result = new LDAPResult(); + req->result->type = req->type; + + if (res != LDAP_SUCCESS) + { + ldap_result->error = ldap_err2string(res); + return; + } + + if (req->message == NULL) + { + return; + } + + /* a search result */ + + for (LDAPMessage *cur = ldap_first_message(this->con, req->message); cur; cur = ldap_next_message(this->con, cur)) + { + LDAPAttributes attributes; + + char *dn = ldap_get_dn(this->con, cur); + if (dn != NULL) + { + attributes["dn"].push_back(dn); + ldap_memfree(dn); + dn = NULL; + } + + BerElement *ber = NULL; + + for (char *attr = ldap_first_attribute(this->con, cur, &ber); attr; attr = ldap_next_attribute(this->con, cur, ber)) + { + berval **vals = ldap_get_values_len(this->con, cur, attr); + int count = ldap_count_values_len(vals); + + std::vector attrs; + for (int j = 0; j < count; ++j) + attrs.push_back(vals[j]->bv_val); + attributes[attr] = attrs; + + ldap_value_free_len(vals); + ldap_memfree(attr); + } + + if (ber != NULL) + ber_free(ber, 0); + + ldap_result->messages.push_back(attributes); + } + } + + void SendRequests() + { + process_mutex.Lock(); + + query_queue q; + this->Lock(); + queries.swap(q); + this->Unlock(); + + if (q.empty()) + { + process_mutex.Unlock(); + return; + } + + for (unsigned int i = 0; i < q.size(); ++i) + { + LDAPRequest *req = q[i]; + int ret = req->run(); + + if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT) + { + /* try again */ + try + { + Reconnect(); + } + catch (const LDAPException &) + { + } + + ret = req->run(); + } + + BuildReply(ret, req); + + this->Lock(); + results.push_back(req); + this->Unlock(); + } + + me->Notify(); + + process_mutex.Unlock(); + } + + public: + void Run() anope_override + { + while (!this->GetExitState()) + { + this->Lock(); + /* Queries can be non empty if one is pushed during SendRequests() */ + if (queries.empty()) + this->Wait(); + this->Unlock(); + + SendRequests(); + } + } + + LDAP* GetConnection() + { + return con; + } +}; + +class ModuleLDAP : public Module, public Pipe + , public EventHook +{ + std::map LDAPServices; + + public: + + ModuleLDAP(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + { + me = this; + } + + ~ModuleLDAP() + { + for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it) + { + it->second->SetExitState(); + it->second->Wakeup(); + it->second->Join(); + delete it->second; + } + LDAPServices.clear(); + } + + void OnReload(Configuration::Conf *config) override + { + Configuration::Block *conf = config->GetModule(this); + + for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end();) + { + const Anope::string &cname = it->first; + LDAPService *s = it->second; + int i; + + ++it; + + for (i = 0; i < conf->CountBlock("ldap"); ++i) + if (conf->GetBlock("ldap", i)->Get("name", "ldap/main") == cname) + break; + + if (i == conf->CountBlock("ldap")) + { + Log(LOG_NORMAL, "ldap") << "LDAP: Removing server connection " << cname; + + s->SetExitState(); + s->Wakeup(); + s->Join(); + delete s; + this->LDAPServices.erase(cname); + } + } + + for (int i = 0; i < conf->CountBlock("ldap"); ++i) + { + Configuration::Block *ldap = conf->GetBlock("ldap", i); + + const Anope::string &connname = ldap->Get("name", "ldap/main"); + + if (this->LDAPServices.find(connname) == this->LDAPServices.end()) + { + const Anope::string &server = ldap->Get("server", "127.0.0.1"); + const Anope::string &admin_binddn = ldap->Get("admin_binddn"); + const Anope::string &admin_password = ldap->GetAnope::string>("admin_password"); + + try + { + LDAPService *ss = new LDAPService(this, connname, server, admin_binddn, admin_password); + ss->Start(); + this->LDAPServices.insert(std::make_pair(connname, ss)); + + Log(LOG_NORMAL, "ldap") << "LDAP: Successfully initialized server " << connname << " (" << server << ")"; + } + catch (const LDAPException &ex) + { + Log(LOG_NORMAL, "ldap") << "LDAP: " << ex.GetReason(); + } + } + } + } + + void OnModuleUnload(User *, Module *m) override + { + for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it) + { + LDAPService *s = it->second; + + s->process_mutex.Lock(); + s->Lock(); + + for (unsigned int i = s->queries.size(); i > 0; --i) + { + LDAPRequest *req = s->queries[i - 1]; + LDAPInterface *li = req->inter; + + if (li && li->owner == m) + { + s->queries.erase(s->queries.begin() + i - 1); + delete req; + } + } + for (unsigned int i = s->results.size(); i > 0; --i) + { + LDAPRequest *req = s->results[i - 1]; + LDAPInterface *li = req->inter; + + if (li && li->owner == m) + { + s->results.erase(s->results.begin() + i - 1); + delete req; + } + } + + s->Unlock(); + s->process_mutex.Unlock(); + } + } + + void OnNotify() override + { + for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it) + { + LDAPService *s = it->second; + + LDAPService::query_queue results; + s->Lock(); + results.swap(s->results); + s->Unlock(); + + for (unsigned int i = 0; i < results.size(); ++i) + { + LDAPRequest *req = results[i]; + LDAPInterface *li = req->inter; + LDAPResult *r = req->result; + + if (li != NULL) + { + if (!r->getError().empty()) + { + Log(this) << "Error running LDAP query: " << r->getError(); + li->OnError(*r); + } + else + li->OnResult(*r); + } + + delete req; + } + } + } +}; + +int LDAPBind::run() +{ + berval cred; + cred.bv_val = strdup(pass.c_str()); + cred.bv_len = pass.length(); + + int i = ldap_sasl_bind_s(service->GetConnection(), who.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); + + free(cred.bv_val); + + return i; +} + +int LDAPSearch::run() +{ + return ldap_search_ext_s(service->GetConnection(), base.c_str(), LDAP_SCOPE_SUBTREE, filter.c_str(), NULL, 0, NULL, NULL, &tv, 0, &message); +} + +int LDAPAdd::run() +{ + LDAPMod **mods = LDAPService::BuildMods(attributes); + int i = ldap_add_ext_s(service->GetConnection(), dn.c_str(), mods, NULL, NULL); + LDAPService::FreeMods(mods); + return i; +} + +int LDAPDel::run() +{ + return ldap_delete_ext_s(service->GetConnection(), dn.c_str(), NULL, NULL); +} + +int LDAPModify::run() +{ + LDAPMod **mods = LDAPService::BuildMods(attributes); + int i = ldap_modify_ext_s(service->GetConnection(), base.c_str(), mods, NULL, NULL); + LDAPService::FreeMods(mods); + return i; +} + +MODULE_INIT(ModuleLDAP) + diff --git a/modules/extra/ldap_authentication.cpp b/modules/extra/ldap_authentication.cpp new file mode 100644 index 000000000..dd63fd3fb --- /dev/null +++ b/modules/extra/ldap_authentication.cpp @@ -0,0 +1,302 @@ +#include "module.h" +#include "modules/ldap.h" +#include "modules/nickserv.h" + +static Module *me; + +static Anope::string basedn; +static Anope::string search_filter; +static Anope::string object_class; +static Anope::string email_attribute; +static Anope::string username_attribute; + +struct IdentifyInfo +{ + Reference user; + IdentifyRequest *req; + ServiceReference lprov; + bool admin_bind; + Anope::string dn; + + IdentifyInfo(User *u, IdentifyRequest *r, ServiceReference &lp) : user(u), req(r), lprov(lp), admin_bind(true) + { + req->Hold(me); + } + + ~IdentifyInfo() + { + req->Release(me); + } +}; + +class IdentifyInterface : public LDAPInterface +{ + IdentifyInfo *ii; + + public: + IdentifyInterface(Module *m, IdentifyInfo *i) : LDAPInterface(m), ii(i) { } + + ~IdentifyInterface() + { + delete ii; + } + + void OnDelete() anope_override + { + delete this; + } + + void OnResult(const LDAPResult &r) override + { + if (!ii->lprov) + return; + + switch (r.type) + { + case QUERY_SEARCH: + { + if (!r.empty()) + { + try + { + const LDAPAttributes &attr = r.get(0); + ii->dn = attr.get("dn"); + Log(LOG_DEBUG) << "m_ldap_authenticationn: binding as " << ii->dn; + + ii->lprov->Bind(new IdentifyInterface(this->owner, ii), ii->dn, ii->req->GetPassword()); + ii = NULL; + } + catch (const LDAPException &ex) + { + Log(this->owner) << "Error binding after search: " << ex.GetReason(); + } + } + break; + } + case QUERY_BIND: + { + if (ii->admin_bind) + { + Anope::string sf = search_filter.replace_all_cs("%account", ii->req->GetAccount()).replace_all_cs("%object_class", object_class); + try + { + Log(LOG_DEBUG) << "m_ldap_authentication: searching for " << sf; + ii->lprov->Search(new IdentifyInterface(this->owner, ii), basedn, sf); + ii->admin_bind = false; + ii = NULL; + } + catch (const LDAPException &ex) + { + Log(this->owner) << "Unable to search for " << sf << ": " << ex.GetReason(); + } + } + else + { + NickServ::Nick *na = NickServ::FindNick(ii->req->GetAccount()); + if (na == NULL) + { + na = new NickServ::Nick(ii->req->GetAccount(), new NickServ::Account(ii->req->GetAccount())); + na->SetLastRealname(ii->user ? ii->user->realname : ii->req->GetAccount()); + NickServ::Event::OnNickRegister(&NickServ::Event::NickRegister::OnNickRegister, ii->user, na, ii->req->GetPassword());; + ServiceBot *NickServ = Config->GetClient("NickServ"); + if (ii->user && NickServ) + ii->user->SendMessage(NickServ, _("Your account \002%s\002 has been successfully created."), na->GetNick().c_str()); + } + // encrypt and store the password in the nickcore + Anope::Encrypt(ii->req->GetPassword(), na->GetAccount()->pass); + + na->GetAccount()->Extend("m_ldap_authentication_dn", ii->dn); + ii->req->Success(me); + } + break; + } + default: + break; + } + } + + void OnError(const LDAPResult &r) override + { + } +}; + +class OnIdentifyInterface : public LDAPInterface +{ + Anope::string uid; + + public: + OnIdentifyInterface(Module *m, const Anope::string &i) : LDAPInterface(m), uid(i) { } + + void OnDelete() anope_override + { + delete this; + } + + void OnResult(const LDAPResult &r) override + { + User *u = User::Find(uid); + + if (!u || !u->Account() || r.empty()) + return; + + try + { + const LDAPAttributes &attr = r.get(0); + Anope::string email = attr.get(email_attribute); + + if (!email.equals_ci(u->Account()->GetEmail())) + { + u->Account()->GetEmail() = email; + ServiceBot *NickServ = Config->GetClient("NickServ"); + if (NickServ) + u->SendMessage(NickServ, _("Your email has been updated to \002%s\002"), email.c_str()); + Log(this->owner) << "Updated email address for " << u->nick << " (" << u->Account()->GetDisplay() << ") to " << email; + } + } + catch (const LDAPException &ex) + { + Log(this->owner) << ex.GetReason(); + } + } + + void OnError(const LDAPResult &r) override + { + Log(this->owner) << r.error; + } +}; + +class OnRegisterInterface : public LDAPInterface +{ + public: + OnRegisterInterface(Module *m) : LDAPInterface(m) { } + + void OnResult(const LDAPResult &r) override + { + Log(this->owner) << "Successfully added newly created account to LDAP"; + } + + void OnError(const LDAPResult &r) override + { + Log(this->owner) << "Error adding newly created account to LDAP: " << r.getError(); + } +}; + +class ModuleLDAPAuthentication : public Module + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + ServiceReference ldap; + OnRegisterInterface orinterface; + + PrimitiveExtensibleItem dn; + + Anope::string password_attribute; + Anope::string disable_register_reason; + Anope::string disable_email_reason; + + public: + ModuleLDAPAuthentication(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + , EventHook("OnPreCommand", EventHook::Priority::FIRST) + , EventHook("OnCheckAuthentication", EventHook::Priority::FIRST) + , EventHook("OnNickIdentify", EventHook::Priority::FIRST) + , EventHook("OnNickRegister", EventHook::Priority::FIRST) + , ldap("LDAPProvider", "ldap/main") + , orinterface(this) + , dn(this, "m_ldap_authentication_dn") + { + me = this; + } + + void OnReload(Configuration::Conf *config) override + { + Configuration::Block *conf = Config->GetModule(this); + + basedn = conf->Get("basedn"); + search_filter = conf->Get("search_filter"); + object_class = conf->Get("object_class"); + username_attribute = conf->Get("username_attribute"); + this->password_attribute = conf->Get("password_attribute"); + email_attribute = conf->Get("email_attribute"); + this->disable_register_reason = conf->Get("disable_register_reason"); + this->disable_email_reason = conf->Get("disable_email_reason"); + + if (!email_attribute.empty()) + /* Don't complain to users about how they need to update their email, we will do it for them */ + config->GetModule("nickserv")->Set("forceemail", "false"); + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override + { + if (!this->disable_register_reason.empty()) + { + if (command->name == "nickserv/register" || command->name == "nickserv/group") + { + source.Reply(this->disable_register_reason); + return EVENT_STOP; + } + } + + if (!email_attribute.empty() && !this->disable_email_reason.empty() && command->name == "nickserv/set/email") + { + source.Reply(this->disable_email_reason); + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + void OnCheckAuthentication(User *u, IdentifyRequest *req) override + { + if (!this->ldap) + return; + + IdentifyInfo *ii = new IdentifyInfo(u, req, this->ldap); + this->ldap->BindAsAdmin(new IdentifyInterface(this, ii)); + } + + void OnNickIdentify(User *u) override + { + if (email_attribute.empty() || !this->ldap) + return; + + Anope::string *d = dn.Get(u->Account()); + if (!d || d->empty()) + return; + + this->ldap->Search(new OnIdentifyInterface(this, u->GetUID()), *d, "(" + email_attribute + "=*)"); + } + + void OnNickRegister(User *, NickServ::Nick *na, const Anope::string &pass) override + { + if (!this->disable_register_reason.empty() || !this->ldap) + return; + + this->ldap->BindAsAdmin(NULL); + + LDAPMods attributes; + attributes.resize(4); + + attributes[0].name = "objectClass"; + attributes[0].values.push_back("top"); + attributes[0].values.push_back(object_class); + + attributes[1].name = username_attribute; + attributes[1].values.push_back(na->GetNick()); + + if (!na->GetAccount()->GetEmail().empty()) + { + attributes[2].name = email_attribute; + attributes[2].values.push_back(na->GetAccount()->GetEmail()); + } + + attributes[3].name = this->password_attribute; + attributes[3].values.push_back(pass); + + Anope::string new_dn = username_attribute + "=" + na->GetNick() + "," + basedn; + this->ldap->Add(&this->orinterface, new_dn, attributes); + } +}; + +MODULE_INIT(ModuleLDAPAuthentication) diff --git a/modules/extra/ldap_oper.cpp b/modules/extra/ldap_oper.cpp new file mode 100644 index 000000000..31653544e --- /dev/null +++ b/modules/extra/ldap_oper.cpp @@ -0,0 +1,134 @@ +#include "module.h" +#include "modules/ldap.h" + +static std::set my_opers; +static Anope::string opertype_attribute; + +class IdentifyInterface : public LDAPInterface +{ + Reference u; + + public: + IdentifyInterface(Module *m, User *user) : LDAPInterface(m), u(user) + { + } + + void OnResult(const LDAPResult &r) override + { + if (!u || !u->Account()) + return; + + NickServ::Account *nc = u->Account(); + + try + { + const LDAPAttributes &attr = r.get(0); + + const Anope::string &opertype = attr.get(opertype_attribute); + + OperType *ot = OperType::Find(opertype); + if (ot != NULL && (nc->o == NULL || ot != nc->o->ot)) + { + Oper *o = nc->o; + if (o != NULL && my_opers.count(o) > 0) + { + my_opers.erase(o); + delete o; + } + o = new Oper(u->nick, ot); + my_opers.insert(o); + nc->o = o; + + Log(this->owner) << "Tied " << u->nick << " (" << nc->GetDisplay() << ") to opertype " << ot->GetName(); + } + } + catch (const LDAPException &ex) + { + if (nc->o != NULL) + { + if (my_opers.count(nc->o) > 0) + { + my_opers.erase(nc->o); + delete nc->o; + } + nc->o = NULL; + + Log(this->owner) << "Removed services operator from " << u->nick << " (" << nc->GetDisplay() << ")"; + } + } + } + + void OnError(const LDAPResult &r) override + { + } + + void OnDelete() anope_override + { + delete this; + } +}; + +class LDAPOper : public Module + , public EventHook + , public EventHook +{ + ServiceReference ldap; + + Anope::string binddn; + Anope::string password; + Anope::string basedn; + Anope::string filter; + public: + LDAPOper(const Anope::string &modname, const Anope::string &creator) + : Module(modname, creator, EXTRA | VENDOR) + , ldap("LDAPProvider", "ldap/main") + { + + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = Config->GetModule(this); + + this->binddn = config->Get("binddn"); + this->password = config->Get("password"); + this->basedn = config->Get("basedn"); + this->filter = config->Get("filter"); + opertype_attribute = config->Get("opertype_attribute"); + + for (std::set::iterator it = my_opers.begin(), it_end = my_opers.end(); it != it_end; ++it) + delete *it; + my_opers.clear(); + } + + void OnNickIdentify(User *u) override + { + try + { + if (!this->ldap) + throw LDAPException("No LDAP interface. Is m_ldap loaded and configured correctly?"); + else if (this->basedn.empty() || this->filter.empty() || opertype_attribute.empty()) + throw LDAPException("Could not search LDAP for opertype settings, invalid configuration."); + + if (!this->binddn.empty()) + this->ldap->Bind(NULL, this->binddn.replace_all_cs("%a", u->Account()->GetDisplay()), this->password.c_str()); + this->ldap->Search(new IdentifyInterface(this, u), this->basedn, this->filter.replace_all_cs("%a", u->Account()->GetDisplay())); + } + catch (const LDAPException &ex) + { + Log() << ex.GetReason(); + } + } + + void OnDelCore(NickServ::Account *nc) override + { + if (nc->o != NULL && my_opers.count(nc->o) > 0) + { + my_opers.erase(nc->o); + delete nc->o; + nc->o = NULL; + } + } +}; + +MODULE_INIT(LDAPOper) diff --git a/modules/extra/m_ldap.cpp b/modules/extra/m_ldap.cpp deleted file mode 100644 index 71c31b9ac..000000000 --- a/modules/extra/m_ldap.cpp +++ /dev/null @@ -1,623 +0,0 @@ -/* - * - * (C) 2011-2015 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -/* RequiredLibraries: ldap_r,lber */ -/* RequiredWindowsLibraries: libldap_r,liblber */ - -#include "module.h" -#include "modules/ldap.h" -#include - -class LDAPService; -static Pipe *me; - -class LDAPRequest -{ - public: - LDAPService *service; - LDAPInterface *inter; - LDAPMessage *message; /* message returned by ldap_ */ - LDAPResult *result; /* final result */ - struct timeval tv; - QueryType type; - - LDAPRequest(LDAPService *s, LDAPInterface *i) - : service(s) - , inter(i) - , message(NULL) - , result(NULL) - { - type = QUERY_UNKNOWN; - tv.tv_sec = 0; - tv.tv_usec = 100000; - } - - virtual ~LDAPRequest() - { - delete result; - if (inter != NULL) - inter->OnDelete(); - if (message != NULL) - ldap_msgfree(message); - } - - virtual int run() = 0; -}; - -class LDAPBind : public LDAPRequest -{ - Anope::string who, pass; - - public: - LDAPBind(LDAPService *s, LDAPInterface *i, const Anope::string &w, const Anope::string &p) - : LDAPRequest(s, i) - , who(w) - , pass(p) - { - type = QUERY_BIND; - } - - int run() anope_override; -}; - -class LDAPSearch : public LDAPRequest -{ - Anope::string base; - Anope::string filter; - - public: - LDAPSearch(LDAPService *s, LDAPInterface *i, const Anope::string &b, const Anope::string &f) - : LDAPRequest(s, i) - , base(b) - , filter(f) - { - type = QUERY_SEARCH; - } - - int run() anope_override; -}; - -class LDAPAdd : public LDAPRequest -{ - Anope::string dn; - LDAPMods attributes; - - public: - LDAPAdd(LDAPService *s, LDAPInterface *i, const Anope::string &d, const LDAPMods &attr) - : LDAPRequest(s, i) - , dn(d) - , attributes(attr) - { - type = QUERY_ADD; - } - - int run() anope_override; -}; - -class LDAPDel : public LDAPRequest -{ - Anope::string dn; - - public: - LDAPDel(LDAPService *s, LDAPInterface *i, const Anope::string &d) - : LDAPRequest(s, i) - , dn(d) - { - type = QUERY_DELETE; - } - - int run() anope_override; -}; - -class LDAPModify : public LDAPRequest -{ - Anope::string base; - LDAPMods attributes; - - public: - LDAPModify(LDAPService *s, LDAPInterface *i, const Anope::string &b, const LDAPMods &attr) - : LDAPRequest(s, i) - , base(b) - , attributes(attr) - { - type = QUERY_MODIFY; - } - - int run() anope_override; -}; - -class LDAPService : public LDAPProvider, public Thread, public Condition -{ - Anope::string server; - Anope::string admin_binddn; - Anope::string admin_pass; - - LDAP *con; - - time_t last_connect; - - public: - static LDAPMod **BuildMods(const LDAPMods &attributes) - { - LDAPMod **mods = new LDAPMod*[attributes.size() + 1]; - memset(mods, 0, sizeof(LDAPMod*) * (attributes.size() + 1)); - for (unsigned x = 0; x < attributes.size(); ++x) - { - const LDAPModification &l = attributes[x]; - mods[x] = new LDAPMod(); - - if (l.op == LDAPModification::LDAP_ADD) - mods[x]->mod_op = LDAP_MOD_ADD; - else if (l.op == LDAPModification::LDAP_DEL) - mods[x]->mod_op = LDAP_MOD_DELETE; - else if (l.op == LDAPModification::LDAP_REPLACE) - mods[x]->mod_op = LDAP_MOD_REPLACE; - else if (l.op != 0) - throw LDAPException("Unknown LDAP operation"); - mods[x]->mod_type = strdup(l.name.c_str()); - mods[x]->mod_values = new char*[l.values.size() + 1]; - memset(mods[x]->mod_values, 0, sizeof(char *) * (l.values.size() + 1)); - for (unsigned j = 0, c = 0; j < l.values.size(); ++j) - if (!l.values[j].empty()) - mods[x]->mod_values[c++] = strdup(l.values[j].c_str()); - } - return mods; - } - - static void FreeMods(LDAPMod **mods) - { - for (int i = 0; mods[i] != NULL; ++i) - { - free(mods[i]->mod_type); - for (int j = 0; mods[i]->mod_values[j] != NULL; ++j) - free(mods[i]->mod_values[j]); - delete [] mods[i]->mod_values; - } - delete [] mods; - } - - private: - void Connect() - { - int i = ldap_initialize(&this->con, this->server.c_str()); - if (i != LDAP_SUCCESS) - throw LDAPException("Unable to connect to LDAP service " + this->name + ": " + ldap_err2string(i)); - - const int version = LDAP_VERSION3; - i = ldap_set_option(this->con, LDAP_OPT_PROTOCOL_VERSION, &version); - if (i != LDAP_OPT_SUCCESS) - throw LDAPException("Unable to set protocol version for " + this->name + ": " + ldap_err2string(i)); - - const struct timeval tv = { 0, 0 }; - i = ldap_set_option(this->con, LDAP_OPT_NETWORK_TIMEOUT, &tv); - if (i != LDAP_OPT_SUCCESS) - throw LDAPException("Unable to set timeout for " + this->name + ": " + ldap_err2string(i)); - } - - void Reconnect() - { - /* Only try one connect a minute. It is an expensive blocking operation */ - if (last_connect > Anope::CurTime - 60) - throw LDAPException("Unable to connect to LDAP service " + this->name + ": reconnecting too fast"); - last_connect = Anope::CurTime; - - ldap_unbind_ext(this->con, NULL, NULL); - - Connect(); - } - - void QueueRequest(LDAPRequest *r) - { - this->Lock(); - this->queries.push_back(r); - this->Wakeup(); - this->Unlock(); - } - - public: - typedef std::vector query_queue; - query_queue queries, results; - Mutex process_mutex; /* held when processing requests not in either queue */ - - LDAPService(Module *o, const Anope::string &n, const Anope::string &s, const Anope::string &b, const Anope::string &p) : LDAPProvider(o, n), server(s), admin_binddn(b), admin_pass(p), last_connect(0) - { - Connect(); - } - - ~LDAPService() - { - /* At this point the thread has stopped so we don't need to hold process_mutex */ - - this->Lock(); - - for (unsigned int i = 0; i < this->queries.size(); ++i) - { - LDAPRequest *req = this->queries[i]; - - /* queries have no results yet */ - req->result = new LDAPResult(); - req->result->type = req->type; - req->result->error = "LDAP Interface is going away"; - if (req->inter) - req->inter->OnError(*req->result); - - delete req; - } - this->queries.clear(); - - for (unsigned int i = 0; i < this->results.size(); ++i) - { - LDAPRequest *req = this->results[i]; - - /* even though this may have already finished successfully we return that it didn't */ - req->result->error = "LDAP Interface is going away"; - if (req->inter) - req->inter->OnError(*req->result); - - delete req; - } - - this->Unlock(); - - ldap_unbind_ext(this->con, NULL, NULL); - } - - void BindAsAdmin(LDAPInterface *i) override - { - this->Bind(i, this->admin_binddn, this->admin_pass); - } - - void Bind(LDAPInterface *i, const Anope::string &who, const Anope::string &pass) override - { - LDAPBind *b = new LDAPBind(this, i, who, pass); - QueueRequest(b); - } - - void Search(LDAPInterface *i, const Anope::string &base, const Anope::string &filter) override - { - if (i == NULL) - throw LDAPException("No interface"); - - LDAPSearch *s = new LDAPSearch(this, i, base, filter); - QueueRequest(s); - } - - void Add(LDAPInterface *i, const Anope::string &dn, LDAPMods &attributes) override - { - LDAPAdd *add = new LDAPAdd(this, i, dn, attributes); - QueueRequest(add); - } - - void Del(LDAPInterface *i, const Anope::string &dn) anope_override - { - LDAPDel *del = new LDAPDel(this, i, dn); - QueueRequest(del); - } - - void Modify(LDAPInterface *i, const Anope::string &base, LDAPMods &attributes) anope_override - { - LDAPModify *mod = new LDAPModify(this, i, base, attributes); - QueueRequest(mod); - } - - private: - void BuildReply(int res, LDAPRequest *req) - { - LDAPResult *ldap_result = req->result = new LDAPResult(); - req->result->type = req->type; - - if (res != LDAP_SUCCESS) - { - ldap_result->error = ldap_err2string(res); - return; - } - - if (req->message == NULL) - { - return; - } - - /* a search result */ - - for (LDAPMessage *cur = ldap_first_message(this->con, req->message); cur; cur = ldap_next_message(this->con, cur)) - { - LDAPAttributes attributes; - - char *dn = ldap_get_dn(this->con, cur); - if (dn != NULL) - { - attributes["dn"].push_back(dn); - ldap_memfree(dn); - dn = NULL; - } - - BerElement *ber = NULL; - - for (char *attr = ldap_first_attribute(this->con, cur, &ber); attr; attr = ldap_next_attribute(this->con, cur, ber)) - { - berval **vals = ldap_get_values_len(this->con, cur, attr); - int count = ldap_count_values_len(vals); - - std::vector attrs; - for (int j = 0; j < count; ++j) - attrs.push_back(vals[j]->bv_val); - attributes[attr] = attrs; - - ldap_value_free_len(vals); - ldap_memfree(attr); - } - - if (ber != NULL) - ber_free(ber, 0); - - ldap_result->messages.push_back(attributes); - } - } - - void SendRequests() - { - process_mutex.Lock(); - - query_queue q; - this->Lock(); - queries.swap(q); - this->Unlock(); - - if (q.empty()) - { - process_mutex.Unlock(); - return; - } - - for (unsigned int i = 0; i < q.size(); ++i) - { - LDAPRequest *req = q[i]; - int ret = req->run(); - - if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT) - { - /* try again */ - try - { - Reconnect(); - } - catch (const LDAPException &) - { - } - - ret = req->run(); - } - - BuildReply(ret, req); - - this->Lock(); - results.push_back(req); - this->Unlock(); - } - - me->Notify(); - - process_mutex.Unlock(); - } - - public: - void Run() anope_override - { - while (!this->GetExitState()) - { - this->Lock(); - /* Queries can be non empty if one is pushed during SendRequests() */ - if (queries.empty()) - this->Wait(); - this->Unlock(); - - SendRequests(); - } - } - - LDAP* GetConnection() - { - return con; - } -}; - -class ModuleLDAP : public Module, public Pipe - , public EventHook -{ - std::map LDAPServices; - - public: - - ModuleLDAP(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - { - me = this; - } - - ~ModuleLDAP() - { - for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it) - { - it->second->SetExitState(); - it->second->Wakeup(); - it->second->Join(); - delete it->second; - } - LDAPServices.clear(); - } - - void OnReload(Configuration::Conf *config) override - { - Configuration::Block *conf = config->GetModule(this); - - for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end();) - { - const Anope::string &cname = it->first; - LDAPService *s = it->second; - int i; - - ++it; - - for (i = 0; i < conf->CountBlock("ldap"); ++i) - if (conf->GetBlock("ldap", i)->Get("name", "ldap/main") == cname) - break; - - if (i == conf->CountBlock("ldap")) - { - Log(LOG_NORMAL, "ldap") << "LDAP: Removing server connection " << cname; - - s->SetExitState(); - s->Wakeup(); - s->Join(); - delete s; - this->LDAPServices.erase(cname); - } - } - - for (int i = 0; i < conf->CountBlock("ldap"); ++i) - { - Configuration::Block *ldap = conf->GetBlock("ldap", i); - - const Anope::string &connname = ldap->Get("name", "ldap/main"); - - if (this->LDAPServices.find(connname) == this->LDAPServices.end()) - { - const Anope::string &server = ldap->Get("server", "127.0.0.1"); - const Anope::string &admin_binddn = ldap->Get("admin_binddn"); - const Anope::string &admin_password = ldap->GetAnope::string>("admin_password"); - - try - { - LDAPService *ss = new LDAPService(this, connname, server, admin_binddn, admin_password); - ss->Start(); - this->LDAPServices.insert(std::make_pair(connname, ss)); - - Log(LOG_NORMAL, "ldap") << "LDAP: Successfully initialized server " << connname << " (" << server << ")"; - } - catch (const LDAPException &ex) - { - Log(LOG_NORMAL, "ldap") << "LDAP: " << ex.GetReason(); - } - } - } - } - - void OnModuleUnload(User *, Module *m) override - { - for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it) - { - LDAPService *s = it->second; - - s->process_mutex.Lock(); - s->Lock(); - - for (unsigned int i = s->queries.size(); i > 0; --i) - { - LDAPRequest *req = s->queries[i - 1]; - LDAPInterface *li = req->inter; - - if (li && li->owner == m) - { - s->queries.erase(s->queries.begin() + i - 1); - delete req; - } - } - for (unsigned int i = s->results.size(); i > 0; --i) - { - LDAPRequest *req = s->results[i - 1]; - LDAPInterface *li = req->inter; - - if (li && li->owner == m) - { - s->results.erase(s->results.begin() + i - 1); - delete req; - } - } - - s->Unlock(); - s->process_mutex.Unlock(); - } - } - - void OnNotify() override - { - for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it) - { - LDAPService *s = it->second; - - LDAPService::query_queue results; - s->Lock(); - results.swap(s->results); - s->Unlock(); - - for (unsigned int i = 0; i < results.size(); ++i) - { - LDAPRequest *req = results[i]; - LDAPInterface *li = req->inter; - LDAPResult *r = req->result; - - if (li != NULL) - { - if (!r->getError().empty()) - { - Log(this) << "Error running LDAP query: " << r->getError(); - li->OnError(*r); - } - else - li->OnResult(*r); - } - - delete req; - } - } - } -}; - -int LDAPBind::run() -{ - berval cred; - cred.bv_val = strdup(pass.c_str()); - cred.bv_len = pass.length(); - - int i = ldap_sasl_bind_s(service->GetConnection(), who.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); - - free(cred.bv_val); - - return i; -} - -int LDAPSearch::run() -{ - return ldap_search_ext_s(service->GetConnection(), base.c_str(), LDAP_SCOPE_SUBTREE, filter.c_str(), NULL, 0, NULL, NULL, &tv, 0, &message); -} - -int LDAPAdd::run() -{ - LDAPMod **mods = LDAPService::BuildMods(attributes); - int i = ldap_add_ext_s(service->GetConnection(), dn.c_str(), mods, NULL, NULL); - LDAPService::FreeMods(mods); - return i; -} - -int LDAPDel::run() -{ - return ldap_delete_ext_s(service->GetConnection(), dn.c_str(), NULL, NULL); -} - -int LDAPModify::run() -{ - LDAPMod **mods = LDAPService::BuildMods(attributes); - int i = ldap_modify_ext_s(service->GetConnection(), base.c_str(), mods, NULL, NULL); - LDAPService::FreeMods(mods); - return i; -} - -MODULE_INIT(ModuleLDAP) - diff --git a/modules/extra/m_ldap_authentication.cpp b/modules/extra/m_ldap_authentication.cpp deleted file mode 100644 index dd63fd3fb..000000000 --- a/modules/extra/m_ldap_authentication.cpp +++ /dev/null @@ -1,302 +0,0 @@ -#include "module.h" -#include "modules/ldap.h" -#include "modules/nickserv.h" - -static Module *me; - -static Anope::string basedn; -static Anope::string search_filter; -static Anope::string object_class; -static Anope::string email_attribute; -static Anope::string username_attribute; - -struct IdentifyInfo -{ - Reference user; - IdentifyRequest *req; - ServiceReference lprov; - bool admin_bind; - Anope::string dn; - - IdentifyInfo(User *u, IdentifyRequest *r, ServiceReference &lp) : user(u), req(r), lprov(lp), admin_bind(true) - { - req->Hold(me); - } - - ~IdentifyInfo() - { - req->Release(me); - } -}; - -class IdentifyInterface : public LDAPInterface -{ - IdentifyInfo *ii; - - public: - IdentifyInterface(Module *m, IdentifyInfo *i) : LDAPInterface(m), ii(i) { } - - ~IdentifyInterface() - { - delete ii; - } - - void OnDelete() anope_override - { - delete this; - } - - void OnResult(const LDAPResult &r) override - { - if (!ii->lprov) - return; - - switch (r.type) - { - case QUERY_SEARCH: - { - if (!r.empty()) - { - try - { - const LDAPAttributes &attr = r.get(0); - ii->dn = attr.get("dn"); - Log(LOG_DEBUG) << "m_ldap_authenticationn: binding as " << ii->dn; - - ii->lprov->Bind(new IdentifyInterface(this->owner, ii), ii->dn, ii->req->GetPassword()); - ii = NULL; - } - catch (const LDAPException &ex) - { - Log(this->owner) << "Error binding after search: " << ex.GetReason(); - } - } - break; - } - case QUERY_BIND: - { - if (ii->admin_bind) - { - Anope::string sf = search_filter.replace_all_cs("%account", ii->req->GetAccount()).replace_all_cs("%object_class", object_class); - try - { - Log(LOG_DEBUG) << "m_ldap_authentication: searching for " << sf; - ii->lprov->Search(new IdentifyInterface(this->owner, ii), basedn, sf); - ii->admin_bind = false; - ii = NULL; - } - catch (const LDAPException &ex) - { - Log(this->owner) << "Unable to search for " << sf << ": " << ex.GetReason(); - } - } - else - { - NickServ::Nick *na = NickServ::FindNick(ii->req->GetAccount()); - if (na == NULL) - { - na = new NickServ::Nick(ii->req->GetAccount(), new NickServ::Account(ii->req->GetAccount())); - na->SetLastRealname(ii->user ? ii->user->realname : ii->req->GetAccount()); - NickServ::Event::OnNickRegister(&NickServ::Event::NickRegister::OnNickRegister, ii->user, na, ii->req->GetPassword());; - ServiceBot *NickServ = Config->GetClient("NickServ"); - if (ii->user && NickServ) - ii->user->SendMessage(NickServ, _("Your account \002%s\002 has been successfully created."), na->GetNick().c_str()); - } - // encrypt and store the password in the nickcore - Anope::Encrypt(ii->req->GetPassword(), na->GetAccount()->pass); - - na->GetAccount()->Extend("m_ldap_authentication_dn", ii->dn); - ii->req->Success(me); - } - break; - } - default: - break; - } - } - - void OnError(const LDAPResult &r) override - { - } -}; - -class OnIdentifyInterface : public LDAPInterface -{ - Anope::string uid; - - public: - OnIdentifyInterface(Module *m, const Anope::string &i) : LDAPInterface(m), uid(i) { } - - void OnDelete() anope_override - { - delete this; - } - - void OnResult(const LDAPResult &r) override - { - User *u = User::Find(uid); - - if (!u || !u->Account() || r.empty()) - return; - - try - { - const LDAPAttributes &attr = r.get(0); - Anope::string email = attr.get(email_attribute); - - if (!email.equals_ci(u->Account()->GetEmail())) - { - u->Account()->GetEmail() = email; - ServiceBot *NickServ = Config->GetClient("NickServ"); - if (NickServ) - u->SendMessage(NickServ, _("Your email has been updated to \002%s\002"), email.c_str()); - Log(this->owner) << "Updated email address for " << u->nick << " (" << u->Account()->GetDisplay() << ") to " << email; - } - } - catch (const LDAPException &ex) - { - Log(this->owner) << ex.GetReason(); - } - } - - void OnError(const LDAPResult &r) override - { - Log(this->owner) << r.error; - } -}; - -class OnRegisterInterface : public LDAPInterface -{ - public: - OnRegisterInterface(Module *m) : LDAPInterface(m) { } - - void OnResult(const LDAPResult &r) override - { - Log(this->owner) << "Successfully added newly created account to LDAP"; - } - - void OnError(const LDAPResult &r) override - { - Log(this->owner) << "Error adding newly created account to LDAP: " << r.getError(); - } -}; - -class ModuleLDAPAuthentication : public Module - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - ServiceReference ldap; - OnRegisterInterface orinterface; - - PrimitiveExtensibleItem dn; - - Anope::string password_attribute; - Anope::string disable_register_reason; - Anope::string disable_email_reason; - - public: - ModuleLDAPAuthentication(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - , EventHook("OnPreCommand", EventHook::Priority::FIRST) - , EventHook("OnCheckAuthentication", EventHook::Priority::FIRST) - , EventHook("OnNickIdentify", EventHook::Priority::FIRST) - , EventHook("OnNickRegister", EventHook::Priority::FIRST) - , ldap("LDAPProvider", "ldap/main") - , orinterface(this) - , dn(this, "m_ldap_authentication_dn") - { - me = this; - } - - void OnReload(Configuration::Conf *config) override - { - Configuration::Block *conf = Config->GetModule(this); - - basedn = conf->Get("basedn"); - search_filter = conf->Get("search_filter"); - object_class = conf->Get("object_class"); - username_attribute = conf->Get("username_attribute"); - this->password_attribute = conf->Get("password_attribute"); - email_attribute = conf->Get("email_attribute"); - this->disable_register_reason = conf->Get("disable_register_reason"); - this->disable_email_reason = conf->Get("disable_email_reason"); - - if (!email_attribute.empty()) - /* Don't complain to users about how they need to update their email, we will do it for them */ - config->GetModule("nickserv")->Set("forceemail", "false"); - } - - EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override - { - if (!this->disable_register_reason.empty()) - { - if (command->name == "nickserv/register" || command->name == "nickserv/group") - { - source.Reply(this->disable_register_reason); - return EVENT_STOP; - } - } - - if (!email_attribute.empty() && !this->disable_email_reason.empty() && command->name == "nickserv/set/email") - { - source.Reply(this->disable_email_reason); - return EVENT_STOP; - } - - return EVENT_CONTINUE; - } - - void OnCheckAuthentication(User *u, IdentifyRequest *req) override - { - if (!this->ldap) - return; - - IdentifyInfo *ii = new IdentifyInfo(u, req, this->ldap); - this->ldap->BindAsAdmin(new IdentifyInterface(this, ii)); - } - - void OnNickIdentify(User *u) override - { - if (email_attribute.empty() || !this->ldap) - return; - - Anope::string *d = dn.Get(u->Account()); - if (!d || d->empty()) - return; - - this->ldap->Search(new OnIdentifyInterface(this, u->GetUID()), *d, "(" + email_attribute + "=*)"); - } - - void OnNickRegister(User *, NickServ::Nick *na, const Anope::string &pass) override - { - if (!this->disable_register_reason.empty() || !this->ldap) - return; - - this->ldap->BindAsAdmin(NULL); - - LDAPMods attributes; - attributes.resize(4); - - attributes[0].name = "objectClass"; - attributes[0].values.push_back("top"); - attributes[0].values.push_back(object_class); - - attributes[1].name = username_attribute; - attributes[1].values.push_back(na->GetNick()); - - if (!na->GetAccount()->GetEmail().empty()) - { - attributes[2].name = email_attribute; - attributes[2].values.push_back(na->GetAccount()->GetEmail()); - } - - attributes[3].name = this->password_attribute; - attributes[3].values.push_back(pass); - - Anope::string new_dn = username_attribute + "=" + na->GetNick() + "," + basedn; - this->ldap->Add(&this->orinterface, new_dn, attributes); - } -}; - -MODULE_INIT(ModuleLDAPAuthentication) diff --git a/modules/extra/m_ldap_oper.cpp b/modules/extra/m_ldap_oper.cpp deleted file mode 100644 index 31653544e..000000000 --- a/modules/extra/m_ldap_oper.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include "module.h" -#include "modules/ldap.h" - -static std::set my_opers; -static Anope::string opertype_attribute; - -class IdentifyInterface : public LDAPInterface -{ - Reference u; - - public: - IdentifyInterface(Module *m, User *user) : LDAPInterface(m), u(user) - { - } - - void OnResult(const LDAPResult &r) override - { - if (!u || !u->Account()) - return; - - NickServ::Account *nc = u->Account(); - - try - { - const LDAPAttributes &attr = r.get(0); - - const Anope::string &opertype = attr.get(opertype_attribute); - - OperType *ot = OperType::Find(opertype); - if (ot != NULL && (nc->o == NULL || ot != nc->o->ot)) - { - Oper *o = nc->o; - if (o != NULL && my_opers.count(o) > 0) - { - my_opers.erase(o); - delete o; - } - o = new Oper(u->nick, ot); - my_opers.insert(o); - nc->o = o; - - Log(this->owner) << "Tied " << u->nick << " (" << nc->GetDisplay() << ") to opertype " << ot->GetName(); - } - } - catch (const LDAPException &ex) - { - if (nc->o != NULL) - { - if (my_opers.count(nc->o) > 0) - { - my_opers.erase(nc->o); - delete nc->o; - } - nc->o = NULL; - - Log(this->owner) << "Removed services operator from " << u->nick << " (" << nc->GetDisplay() << ")"; - } - } - } - - void OnError(const LDAPResult &r) override - { - } - - void OnDelete() anope_override - { - delete this; - } -}; - -class LDAPOper : public Module - , public EventHook - , public EventHook -{ - ServiceReference ldap; - - Anope::string binddn; - Anope::string password; - Anope::string basedn; - Anope::string filter; - public: - LDAPOper(const Anope::string &modname, const Anope::string &creator) - : Module(modname, creator, EXTRA | VENDOR) - , ldap("LDAPProvider", "ldap/main") - { - - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = Config->GetModule(this); - - this->binddn = config->Get("binddn"); - this->password = config->Get("password"); - this->basedn = config->Get("basedn"); - this->filter = config->Get("filter"); - opertype_attribute = config->Get("opertype_attribute"); - - for (std::set::iterator it = my_opers.begin(), it_end = my_opers.end(); it != it_end; ++it) - delete *it; - my_opers.clear(); - } - - void OnNickIdentify(User *u) override - { - try - { - if (!this->ldap) - throw LDAPException("No LDAP interface. Is m_ldap loaded and configured correctly?"); - else if (this->basedn.empty() || this->filter.empty() || opertype_attribute.empty()) - throw LDAPException("Could not search LDAP for opertype settings, invalid configuration."); - - if (!this->binddn.empty()) - this->ldap->Bind(NULL, this->binddn.replace_all_cs("%a", u->Account()->GetDisplay()), this->password.c_str()); - this->ldap->Search(new IdentifyInterface(this, u), this->basedn, this->filter.replace_all_cs("%a", u->Account()->GetDisplay())); - } - catch (const LDAPException &ex) - { - Log() << ex.GetReason(); - } - } - - void OnDelCore(NickServ::Account *nc) override - { - if (nc->o != NULL && my_opers.count(nc->o) > 0) - { - my_opers.erase(nc->o); - delete nc->o; - nc->o = NULL; - } - } -}; - -MODULE_INIT(LDAPOper) diff --git a/modules/extra/m_mysql.cpp b/modules/extra/m_mysql.cpp deleted file mode 100644 index 843a2ba0a..000000000 --- a/modules/extra/m_mysql.cpp +++ /dev/null @@ -1,614 +0,0 @@ -/* RequiredLibraries: mysqlclient */ -/* RequiredWindowsLibraries: libmysql */ - -#include "module.h" -#include "modules/sql.h" -#define NO_CLIENT_LONG_LONG -#ifdef WIN32 -# include -#else -# include -#endif - -using namespace SQL; - -/** Non blocking threaded MySQL API, based loosely from InspIRCd's m_mysql.cpp - * - * This module spawns a single thread that is used to execute blocking MySQL queries. - * When a module requests a query to be executed it is added to a list for the thread - * (which never stops looping and sleeing) to pick up and execute, the result of which - * is inserted in to another queue to be picked up by the main thread. The main thread - * uses Pipe to become notified through the socket engine when there are results waiting - * to be sent back to the modules requesting the query - */ - -class MySQLService; - -/** A query request - */ -struct QueryRequest -{ - /* The connection to the database */ - MySQLService *service; - /* The interface to use once we have the result to send the data back */ - Interface *sqlinterface; - /* The actual query */ - Query query; - - QueryRequest(MySQLService *s, Interface *i, const Query &q) : service(s), sqlinterface(i), query(q) { } -}; - -/** A query result */ -struct QueryResult -{ - /* The interface to send the data back on */ - Interface *sqlinterface; - /* The result */ - Result result; - - QueryResult(Interface *i, Result &r) : sqlinterface(i), result(r) { } -}; - -/** A MySQL result - */ -class MySQLResult : public Result -{ - MYSQL_RES *res; - - public: - MySQLResult(unsigned int i, const Query &q, const Anope::string &fq, MYSQL_RES *r) : Result(i, q, fq), res(r) - { - if (!res) - return; - - unsigned num_fields = mysql_num_fields(res); - MYSQL_FIELD *fields = mysql_fetch_fields(res); - - /* It is not thread safe to log anything here using Log(this->owner) now :( */ - - if (!num_fields || !fields) - return; - - for (unsigned field_count = 0; field_count < num_fields; ++field_count) - columns.push_back(fields[field_count].name ? fields[field_count].name : ""); - - for (MYSQL_ROW row; (row = mysql_fetch_row(res));) - { - std::vector values; - - for (unsigned field_count = 0; field_count < num_fields; ++field_count) - { - const char *data = row[field_count]; - - Value v; - v.null = !data; - v.value = data ? data : ""; - values.push_back(v); - } - - this->values.push_back(values); - } - } - - MySQLResult(const Query &q, const Anope::string &fq, const Anope::string &err) : Result(0, q, fq, err), res(NULL) - { - } - - ~MySQLResult() - { - if (this->res) - mysql_free_result(this->res); - } -}; - -/** A MySQL connection, there can be multiple - */ -class MySQLService : public Provider -{ - std::map > active_schema, indexes; - - Anope::string database; - Anope::string server; - Anope::string user; - Anope::string password; - int port; - - MYSQL *sql; - - /** Escape a query. - * Note the mutex must be held! - */ - Anope::string Escape(const Anope::string &query); - - public: - /* Locked by the SQL thread when a query is pending on this database, - * prevents us from deleting a connection while a query is executing - * in the thread - */ - Mutex Lock; - - MySQLService(Module *o, const Anope::string &n, const Anope::string &d, const Anope::string &s, const Anope::string &u, const Anope::string &p, int po); - - ~MySQLService(); - - void Run(Interface *i, const Query &query) override; - - Result RunQuery(const Query &query) override; - - std::vector InitSchema(const Anope::string &prefix) override; - std::vector Replace(const Anope::string &table, const Query &, const std::set &) override; - std::vector CreateTable(const Anope::string &prefix, const Anope::string &table) override; - std::vector AlterTable(const Anope::string &, const Anope::string &table, const Anope::string &field, bool) override; - std::vector CreateIndex(const Anope::string &table, const Anope::string &field) override; - - Query BeginTransaction() override; - Query Commit() override; - - Serialize::ID GetID(const Anope::string &) override; - - Query GetTables(const Anope::string &prefix) override; - - void Connect(); - - bool CheckConnection(); - - Anope::string BuildQuery(const Query &q); -}; - -/** The SQL thread used to execute queries - */ -class DispatcherThread : public Thread, public Condition -{ - public: - DispatcherThread() : Thread() { } - - void Run() override; -}; - -class ModuleSQL; -static ModuleSQL *me; -class ModuleSQL : public Module - , public Pipe - , public EventHook -{ - /* SQL connections */ - std::map MySQLServices; - public: - /* Pending query requests */ - std::deque QueryRequests; - /* Pending finished requests with results */ - std::deque FinishedRequests; - /* The thread used to execute queries */ - DispatcherThread *DThread; - - ModuleSQL(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - { - me = this; - - - DThread = new DispatcherThread(); - DThread->Start(); - } - - ~ModuleSQL() - { - for (std::map::iterator it = this->MySQLServices.begin(); it != this->MySQLServices.end(); ++it) - delete it->second; - MySQLServices.clear(); - - DThread->SetExitState(); - DThread->Wakeup(); - DThread->Join(); - delete DThread; - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - - for (std::map::iterator it = this->MySQLServices.begin(); it != this->MySQLServices.end();) - { - const Anope::string &cname = it->first; - MySQLService *s = it->second; - int i; - - ++it; - - for (i = 0; i < config->CountBlock("mysql"); ++i) - if (config->GetBlock("mysql", i)->Get("name", "mysql/main") == cname) - break; - - if (i == config->CountBlock("mysql")) - { - Log(LOG_NORMAL, "mysql") << "MySQL: Removing server connection " << cname; - - delete s; - this->MySQLServices.erase(cname); - } - } - - for (int i = 0; i < config->CountBlock("mysql"); ++i) - { - Configuration::Block *block = config->GetBlock("mysql", i); - const Anope::string &connname = block->Get("name", "mysql/main"); - - if (this->MySQLServices.find(connname) == this->MySQLServices.end()) - { - const Anope::string &database = block->Get("database", "anope"); - const Anope::string &server = block->Get("server", "127.0.0.1"); - const Anope::string &user = block->Get("username", "anope"); - const Anope::string &password = block->Get("password"); - int port = block->Get("port", "3306"); - - try - { - MySQLService *ss = new MySQLService(this, connname, database, server, user, password, port); - this->MySQLServices.insert(std::make_pair(connname, ss)); - - Log(LOG_NORMAL, "mysql") << "MySQL: Successfully connected to server " << connname << " (" << server << ")"; - } - catch (const SQL::Exception &ex) - { - Log(LOG_NORMAL, "mysql") << "MySQL: " << ex.GetReason(); - } - } - } - } - - void OnModuleUnload(User *, Module *m) override - { - this->DThread->Lock(); - - for (unsigned i = this->QueryRequests.size(); i > 0; --i) - { - QueryRequest &r = this->QueryRequests[i - 1]; - - if (r.sqlinterface && r.sqlinterface->owner == m) - { - if (i == 1) - { - r.service->Lock.Lock(); - r.service->Lock.Unlock(); - } - - this->QueryRequests.erase(this->QueryRequests.begin() + i - 1); - } - } - - this->DThread->Unlock(); - - this->OnNotify(); - } - - void OnNotify() override - { - this->DThread->Lock(); - std::deque finishedRequests = this->FinishedRequests; - this->FinishedRequests.clear(); - this->DThread->Unlock(); - - for (std::deque::const_iterator it = finishedRequests.begin(), it_end = finishedRequests.end(); it != it_end; ++it) - { - const QueryResult &qr = *it; - - if (!qr.sqlinterface) - throw SQL::Exception("NULL qr.sqlinterface in MySQLPipe::OnNotify() ?"); - - if (qr.result.GetError().empty()) - qr.sqlinterface->OnResult(qr.result); - else - qr.sqlinterface->OnError(qr.result); - } - } -}; - -MySQLService::MySQLService(Module *o, const Anope::string &n, const Anope::string &d, const Anope::string &s, const Anope::string &u, const Anope::string &p, int po) -: Provider(o, n), database(d), server(s), user(u), password(p), port(po), sql(NULL) -{ - Connect(); -} - -MySQLService::~MySQLService() -{ - me->DThread->Lock(); - this->Lock.Lock(); - mysql_close(this->sql); - this->sql = NULL; - - for (unsigned i = me->QueryRequests.size(); i > 0; --i) - { - QueryRequest &r = me->QueryRequests[i - 1]; - - if (r.service == this) - { - if (r.sqlinterface) - r.sqlinterface->OnError(Result(0, r.query, "SQL Interface is going away")); - me->QueryRequests.erase(me->QueryRequests.begin() + i - 1); - } - } - this->Lock.Unlock(); - me->DThread->Unlock(); -} - -void MySQLService::Run(Interface *i, const Query &query) -{ - me->DThread->Lock(); - me->QueryRequests.push_back(QueryRequest(this, i, query)); - me->DThread->Unlock(); - me->DThread->Wakeup(); -} - -Result MySQLService::RunQuery(const Query &query) -{ - this->Lock.Lock(); - - Anope::string real_query = this->BuildQuery(query); - - if (this->CheckConnection() && !mysql_real_query(this->sql, real_query.c_str(), real_query.length())) - { - MYSQL_RES *res = mysql_store_result(this->sql); - unsigned int id = mysql_insert_id(this->sql); - - /* because we enabled CLIENT_MULTI_RESULTS in our options - * a multiple statement or a procedure call can return - * multiple result sets. - * we must process them all before the next query. - */ - - while (!mysql_next_result(this->sql)) - mysql_free_result(mysql_store_result(this->sql)); - - this->Lock.Unlock(); - return MySQLResult(id, query, real_query, res); - } - else - { - Anope::string error = mysql_error(this->sql); - this->Lock.Unlock(); - return MySQLResult(query, real_query, error); - } -} - -std::vector MySQLService::InitSchema(const Anope::string &prefix) -{ - std::vector queries; - - Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + "id` (" - "`id` bigint(20) NOT NULL" - ") ENGINE=InnoDB"; - queries.push_back(t); - - t = "CREATE TABLE IF NOT EXISTS `" + prefix + "objects` (`id` bigint(20) NOT NULL PRIMARY KEY, `type` varchar(256)) ENGINE=InnoDB"; - queries.push_back(t); - - t = "CREATE TABLE IF NOT EXISTS `" + prefix + "edges` (" - "`id` bigint(20) NOT NULL," - "`field` varchar(64) NOT NULL," - "`other_id` bigint(20) NOT NULL," - "PRIMARY KEY (`id`, `field`)," - "KEY `other` (`other_id`)," - "CONSTRAINT `edges_id_fk` FOREIGN KEY (`id`) REFERENCES `" + prefix + "objects` (`id`)," - "CONSTRAINT `edges_other_id_fk` FOREIGN KEY (`other_id`) REFERENCES `" + prefix + "objects` (`id`)" - ") ENGINE=InnoDB"; - queries.push_back(t); - - return queries; -} - -std::vector MySQLService::Replace(const Anope::string &table, const Query &q, const std::set &keys) -{ - std::vector queries; - - Anope::string query_text = "INSERT INTO `" + table + "` ("; - for (const std::pair &p : q.parameters) - query_text += "`" + p.first + "`,"; - query_text.erase(query_text.length() - 1); - query_text += ") VALUES ("; - for (const std::pair &p : q.parameters) - query_text += "@" + p.first + "@,"; - query_text.erase(query_text.length() - 1); - query_text += ") ON DUPLICATE KEY UPDATE "; - for (const std::pair &p : q.parameters) - if (!keys.count(p.first)) - query_text += "`" + p.first + "` = VALUES(`" + p.first + "`),"; - query_text.erase(query_text.length() - 1); - - Query query(query_text); - query.parameters = q.parameters; - - queries.push_back(query); - - return queries; -} - -std::vector MySQLService::CreateTable(const Anope::string &prefix, const Anope::string &table) -{ - std::vector queries; - - if (active_schema.find(prefix + table) == active_schema.end()) - { - Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + table + "` (`id` bigint(20) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB"; - queries.push_back(t); - - t = "ALTER TABLE `" + prefix + table + "` " - "ADD CONSTRAINT `" + table + "_id_fk` FOREIGN KEY (`id`) REFERENCES `" + prefix + "objects` (`id`)"; - queries.push_back(t); - - active_schema[prefix + table]; - } - - return queries; -} - -std::vector MySQLService::AlterTable(const Anope::string &prefix, const Anope::string &table, const Anope::string &field, bool object) -{ - std::vector queries; - std::set &s = active_schema[prefix + table]; - - if (!s.count(field)) - { - Query column; - if (!object) - column = "ALTER TABLE `" + prefix + table + "` ADD COLUMN `" + field + "` TINYTEXT"; - else - column = "ALTER TABLE `" + prefix + table + "` " - "ADD COLUMN `" + field + "` bigint(20), " - "ADD CONSTRAINT `" + table + "_" + field + "_fk` FOREIGN KEY (`" + field + "`) REFERENCES `" + prefix + "objects` (`id`)"; - queries.push_back(column); - s.insert(field); - } - - return queries; -} - -std::vector MySQLService::CreateIndex(const Anope::string &table, const Anope::string &field) -{ - std::vector queries; - - if (indexes[table].count(field)) - return queries; - - Query t = "ALTER TABLE `" + table + "` ADD KEY `idx_" + field + "` (`" + field + "`(512))"; - queries.push_back(t); - - indexes[table].insert(field); - - return queries; -} - -Query MySQLService::BeginTransaction() -{ - return Query("START TRANSACTION WITH CONSISTENT SNAPSHOT"); -} - -Query MySQLService::Commit() -{ - return Query("COMMIT"); -} - -Serialize::ID MySQLService::GetID(const Anope::string &prefix) -{ - Query query("SELECT `id` FROM `" + prefix + "id` FOR UPDATE"); - Serialize::ID id; - - Result res = RunQuery(query); - if (res.Rows()) - { - id = convertTo(res.Get(0, "id")); - - Query update_query("UPDATE `" + prefix + "id` SET `id` = `id` + 1"); - RunQuery(update_query); - } - else - { - id = 0; - - Query insert_query("INSERT INTO `" + prefix + "id` (id) VALUES(@id@)"); - insert_query.SetValue("id", 1); - RunQuery(insert_query); - } - - return id; -} - -Query MySQLService::GetTables(const Anope::string &prefix) -{ - return Query("SHOW TABLES LIKE '" + prefix + "%';"); -} - -void MySQLService::Connect() -{ - this->sql = mysql_init(this->sql); - - const unsigned int timeout = 1; - mysql_options(this->sql, MYSQL_OPT_CONNECT_TIMEOUT, reinterpret_cast(&timeout)); - - bool connect = mysql_real_connect(this->sql, this->server.c_str(), this->user.c_str(), this->password.c_str(), this->database.c_str(), this->port, NULL, CLIENT_MULTI_RESULTS); - - if (!connect) - throw SQL::Exception("Unable to connect to MySQL service " + this->name + ": " + mysql_error(this->sql)); - - Log(LOG_DEBUG) << "Successfully connected to MySQL service " << this->name << " at " << this->server << ":" << this->port; -} - - -bool MySQLService::CheckConnection() -{ - if (!this->sql || mysql_ping(this->sql)) - { - try - { - this->Connect(); - } - catch (const SQL::Exception &) - { - return false; - } - } - - return true; -} - -Anope::string MySQLService::Escape(const Anope::string &query) -{ - std::vector buffer(query.length() * 2 + 1); - mysql_real_escape_string(this->sql, &buffer[0], query.c_str(), query.length()); - return &buffer[0]; -} - -Anope::string MySQLService::BuildQuery(const Query &q) -{ - Anope::string real_query = q.query; - - for (std::map::const_iterator it = q.parameters.begin(), it_end = q.parameters.end(); it != it_end; ++it) - { - const QueryData& qd = it->second; - Anope::string replacement; - - if (qd.null) - replacement = "NULL"; - else if (!qd.escape) - replacement = qd.data; - else - replacement = "'" + this->Escape(qd.data) + "'"; - - real_query = real_query.replace_all_cs("@" + it->first + "@", replacement); - } - - return real_query; -} - -void DispatcherThread::Run() -{ - this->Lock(); - - while (!this->GetExitState()) - { - if (!me->QueryRequests.empty()) - { - QueryRequest &r = me->QueryRequests.front(); - this->Unlock(); - - Result sresult = r.service->RunQuery(r.query); - - this->Lock(); - if (!me->QueryRequests.empty() && me->QueryRequests.front().query == r.query) - { - if (r.sqlinterface) - me->FinishedRequests.push_back(QueryResult(r.sqlinterface, sresult)); - me->QueryRequests.pop_front(); - } - } - else - { - if (!me->FinishedRequests.empty()) - me->Notify(); - this->Wait(); - } - } - - this->Unlock(); -} - -MODULE_INIT(ModuleSQL) - diff --git a/modules/extra/m_sasl_dh-aes.cpp b/modules/extra/m_sasl_dh-aes.cpp deleted file mode 100644 index dd06d118b..000000000 --- a/modules/extra/m_sasl_dh-aes.cpp +++ /dev/null @@ -1,184 +0,0 @@ -/* RequiredLibraries: ssl,crypto */ -/* RequiredWindowsLibraries: ssleay32,libeay32 */ - -#include "module.h" -#include "modules/sasl.h" - -#include -#include -#include - -using namespace SASL; - -class DHAES : public Mechanism -{ - void Err(Session* sess, BIGNUM* key = NULL) - { - if (key) - BN_free(key); - - sasl->Fail(sess); - delete sess; - } - - public: - struct DHAESSession : SASL::Session - { - DH* dh; - DHAESSession(Mechanism *m, const Anope::string &u, DH* dh_params) : SASL::Session(m, u) - { - if (!(dh = DH_new())) - return; - - dh->g = BN_dup(dh_params->g); - dh->p = BN_dup(dh_params->p); - - if (!DH_generate_key(dh)) - { - DH_free(dh); - dh = NULL; - } - } - - ~DHAESSession() - { - if (dh) - DH_free(dh); - } - }; - - DH* dh_params; - const size_t keysize; - SASL::Session* CreateSession(const Anope::string &uid) override - { - return new DHAESSession(this, uid, dh_params); - } - - DHAES(Module *o) : Mechanism(o, "DH-AES"), keysize(256 / 8) - { - if (!(dh_params = DH_new())) - throw ModuleException("DH_new() failed!"); - - if (!DH_generate_parameters_ex(dh_params, keysize * 8, 5, NULL)) - { - DH_free(dh_params); - throw ModuleException("Could not generate DH-params"); - } - } - - ~DHAES() - { - DH_free(dh_params); - } - - void ProcessMessage(SASL::Session *session, const SASL::Message &m) override - { - DHAESSession *sess = anope_dynamic_static_cast(session); - - if (!sess->dh) - { - sasl->SendMessage(sess, "D", "A"); - delete sess; - return; - } - - if (m.type == "S") - { - // Format: [ss]

[ss][ss] - // Where ss is a unsigned short with the size of the key - const BIGNUM* dhval[] = { sess->dh->p, sess->dh->g, sess->dh->pub_key }; - - // Find the size of our buffer - initialized at 6 because of string size data - size_t size = 6; - for (size_t i = 0; i < 3; i++) - size += BN_num_bytes(dhval[i]); - - // Fill in the DH data - std::vector buffer(size); - for (size_t i = 0, pos = 0; i < 3; i++) - { - *reinterpret_cast(&buffer[pos]) = htons(BN_num_bytes(dhval[i])); - pos += 2; - BN_bn2bin(dhval[i], &buffer[pos]); - pos += BN_num_bytes(dhval[i]); - } - - Anope::string encoded; - Anope::B64Encode(Anope::string(buffer.begin(), buffer.end()), encoded); - sasl->SendMessage(sess, "C", encoded); - } - else if (m.type == "C") - { - // Make sure we have some data - actual size check is done later - if (m.data.length() < 10) - return Err(sess); - - // Format: [ss][ss][ss] - // = \0\0 - - Anope::string decoded; - Anope::B64Decode(m.data, decoded); - - // Make sure we have an IV and at least one encrypted block - if ((decoded.length() < keysize + 2 + (AES_BLOCK_SIZE * 2)) || ((decoded.length() - keysize - 2) % AES_BLOCK_SIZE)) - return Err(sess); - - const unsigned char* data = reinterpret_cast(decoded.data()); - - // Control the size of the key - if (ntohs(*reinterpret_cast(&data[0])) != keysize) - return Err(sess); - - // Convert pubkey from binary - size_t pos = 2; - BIGNUM* pubkey = BN_bin2bn(&data[pos], keysize, NULL); - if (!pubkey) - return Err(sess); - - // Find shared key - std::vector secretkey(keysize); - if (DH_compute_key(&secretkey[0], pubkey, sess->dh) != static_cast(keysize)) - return Err(sess, pubkey); - - // Set decryption key - AES_KEY AESKey; - AES_set_decrypt_key(&secretkey[0], keysize * 8, &AESKey); - - // Fetch IV - pos += keysize; - std::vector IV(data + pos, data + pos + AES_BLOCK_SIZE); - - // Find encrypted blocks, and decrypt - pos += AES_BLOCK_SIZE; - size_t size = decoded.length() - pos; - std::vector decrypted(size + 2, 0); - AES_cbc_encrypt(&data[pos], reinterpret_cast(&decrypted[0]), size, &AESKey, &IV[0], AES_DECRYPT); - - std::string username = &decrypted[0]; - std::string password = &decrypted[username.length() + 1]; - - if (username.empty() || password.empty() || !IRCD->IsNickValid(username) || password.find_first_of("\r\n") != Anope::string::npos) - return Err(sess, pubkey); - - SASL::IdentifyRequest* req = new SASL::IdentifyRequest(this->owner, m.source, username, password); - Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, nullptr, req); - req->Dispatch(); - - BN_free(pubkey); - } - } -}; - - -class ModuleSASLDHAES : public Module -{ - DHAES dhaes; - - public: - ModuleSASLDHAES(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) - , dhaes(this) - { - } -}; - -MODULE_INIT(ModuleSASLDHAES) diff --git a/modules/extra/m_sasl_dh-blowfish.cpp b/modules/extra/m_sasl_dh-blowfish.cpp deleted file mode 100644 index f6e4b27b3..000000000 --- a/modules/extra/m_sasl_dh-blowfish.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* RequiredLibraries: ssl,crypto */ -/* RequiredWindowsLibraries: ssleay32,libeay32 */ - -#include "module.h" -#include "modules/sasl.h" - -#include -#include -#include - -using namespace SASL; - -class DHBS : public Mechanism -{ - void Err(Session* sess, BIGNUM* key = NULL) - { - if (key) - BN_free(key); - - sasl->Fail(sess); - delete sess; - } - - public: - struct DHBSSession : SASL::Session - { - DH* dh; - DHBSSession(Mechanism *m, const Anope::string &u, DH* dh_params) : SASL::Session(m, u) - { - if (!(dh = DH_new())) - return; - - dh->g = BN_dup(dh_params->g); - dh->p = BN_dup(dh_params->p); - - if (!DH_generate_key(dh)) - { - DH_free(dh); - dh = NULL; - } - } - - ~DHBSSession() - { - if (dh) - DH_free(dh); - } - }; - - DH* dh_params; - const size_t keysize; - SASL::Session* CreateSession(const Anope::string &uid) override - { - return new DHBSSession(this, uid, dh_params); - } - - DHBS(Module *o) : Mechanism(o, "DH-BLOWFISH"), keysize(256 / 8) - { - if (!(dh_params = DH_new())) - throw ModuleException("DH_new() failed!"); - - if (!DH_generate_parameters_ex(dh_params, keysize * 8, 5, NULL)) - { - DH_free(dh_params); - throw ModuleException("Could not generate DH-params"); - } - } - - ~DHBS() - { - DH_free(dh_params); - } - - void ProcessMessage(SASL::Session *session, const SASL::Message &m) override - { - DHBSSession *sess = anope_dynamic_static_cast(session); - - if (!sess->dh) - { - sasl->SendMessage(sess, "D", "A"); - delete sess; - return; - } - - if (m.type == "S") - { - // Format: [ss]

[ss][ss] - // Where ss is a unsigned short with the size of the key - const BIGNUM* dhval[] = { sess->dh->p, sess->dh->g, sess->dh->pub_key }; - - // Find the size of our buffer - initialized at 6 because of string size data - size_t size = 6; - for (size_t i = 0; i < 3; i++) - size += BN_num_bytes(dhval[i]); - - // Fill in the DH data - std::vector buffer(size); - for (size_t i = 0, pos = 0; i < 3; i++) - { - *reinterpret_cast(&buffer[pos]) = htons(BN_num_bytes(dhval[i])); - pos += 2; - BN_bn2bin(dhval[i], &buffer[pos]); - pos += BN_num_bytes(dhval[i]); - } - - Anope::string encoded; - Anope::B64Encode(Anope::string(buffer.begin(), buffer.end()), encoded); - sasl->SendMessage(sess, "C", encoded); - } - else if (m.type == "C") - { - // Make sure we have some data - actual size check is done later - if (m.data.length() < 10) - return Err(sess); - - // Format: [ss]<\0> - - Anope::string decoded; - Anope::B64Decode(m.data, decoded); - - // As we rely on the client giving us a null terminator at the right place, - // let's add one extra in case the client tries to crash us - const size_t decodedlen = decoded.length(); - decoded.push_back('\0'); - - // Make sure we have enough data for at least the key, a one letter username, and a block of data - if (decodedlen < keysize + 2 + 2 + 8) - return Err(sess); - - const unsigned char* data = reinterpret_cast(decoded.data()); - - // Control the size of the key - if (ntohs(*reinterpret_cast(&data[0])) != keysize) - return Err(sess); - - // Convert pubkey from binary - size_t pos = 2; - BIGNUM* pubkey = BN_bin2bn(&data[pos], keysize, NULL); - if (!pubkey) - return Err(sess); - - // Find shared key - std::vector secretkey(DH_size(sess->dh) + 1, 0); - if (DH_compute_key(&secretkey[0], pubkey, sess->dh) != static_cast(keysize)) - return Err(sess, pubkey); - - // Set decryption key - BF_KEY BFKey; - BF_set_key(&BFKey, keysize, &secretkey[0]); - - pos += keysize; - const Anope::string username = reinterpret_cast(&data[pos]); - // Check that the username is valid, and that we have at least one block of data - // 2 + 1 + 8 = uint16_t size for keylen, \0 for username, 8 for one block of data - if (username.empty() || username.length() + keysize + 2 + 1 + 8 > decodedlen || !IRCD->IsNickValid(username)) - return Err(sess, pubkey); - - pos += username.length() + 1; - size_t size = decodedlen - pos; - - // Blowfish data blocks are 64 bits wide - valid format? - if (size % 8) - return Err(sess, pubkey); - - std::vector decrypted(size + 1, 0); - for (size_t i = 0; i < size; i += 8) - BF_ecb_encrypt(&data[pos + i], reinterpret_cast(&decrypted[i]), &BFKey, BF_DECRYPT); - - std::string password = &decrypted[0]; - if (password.empty() || password.find_first_of("\r\n") != Anope::string::npos) - return Err(sess, pubkey); - - SASL::IdentifyRequest* req = new SASL::IdentifyRequest(this->owner, m.source, username, password); - Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, nullptr, req); - req->Dispatch(); - - BN_free(pubkey); - } - } -}; - - -class ModuleSASLDHBS : public Module -{ - DHBS dhbs; - - public: - ModuleSASLDHBS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) - , dhbs(this) - { - } -}; - -MODULE_INIT(ModuleSASLDHBS) diff --git a/modules/extra/m_sql_authentication.cpp b/modules/extra/m_sql_authentication.cpp deleted file mode 100644 index 1c2dbe729..000000000 --- a/modules/extra/m_sql_authentication.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include "module.h" -#include "modules/sql.h" -#include "modules/nickserv.h" - -static Module *me; - -class SQLAuthenticationResult : public SQL::Interface -{ - Reference user; - IdentifyRequest *req; - - public: - SQLAuthenticationResult(User *u, IdentifyRequest *r) : SQL::Interface(me), user(u), req(r) - { - req->Hold(me); - } - - ~SQLAuthenticationResult() - { - req->Release(me); - } - - void OnResult(const SQL::Result &r) override - { - if (r.Rows() == 0) - { - Log(LOG_DEBUG) << "m_sql_authentication: Unsuccessful authentication for " << req->GetAccount(); - delete this; - return; - } - - Log(LOG_DEBUG) << "m_sql_authentication: Successful authentication for " << req->GetAccount(); - - Anope::string email; - try - { - email = r.Get(0, "email"); - } - catch (const SQL::Exception &) { } - - NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); - ServiceBot *NickServ = Config->GetClient("NickServ"); - if (na == NULL) - { - na = new NickServ::Nick(req->GetAccount(), new NickServ::Account(req->GetAccount())); - NickServ::Event::OnNickRegister(&NickServ::Event::NickRegister::OnNickRegister, user, na, ""); - if (user && NickServ) - user->SendMessage(NickServ, _("Your account \002%s\002 has been successfully created."), na->GetNick().c_str()); - } - - if (!email.empty() && email != na->GetAccount()->GetEmail()) - { - na->GetAccount()->GetEmail() = email; - if (user && NickServ) - user->SendMessage(NickServ, _("Your email has been updated to \002%s\002."), email.c_str()); - } - - req->Success(me); - delete this; - } - - void OnError(const SQL::Result &r) override - { - Log(this->owner) << "m_sql_authentication: Error executing query " << r.GetQuery().query << ": " << r.GetError(); - delete this; - } -}; - -class ModuleSQLAuthentication : public Module - , public EventHook - , public EventHook -{ - Anope::string engine; - Anope::string query; - Anope::string disable_reason, disable_email_reason; - - ServiceReference SQL; - - public: - ModuleSQLAuthentication(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - { - me = this; - - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - this->engine = config->Get("engine"); - this->query = config->Get("query"); - this->disable_reason = config->Get("disable_reason"); - this->disable_email_reason = config->Get("disable_email_reason"); - - this->SQL = ServiceReference("SQL::Provider", this->engine); - } - - EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override - { - if (!this->disable_reason.empty() && (command->name == "nickserv/register" || command->name == "nickserv/group")) - { - source.Reply(this->disable_reason); - return EVENT_STOP; - } - - if (!this->disable_email_reason.empty() && command->name == "nickserv/set/email") - { - source.Reply(this->disable_email_reason); - return EVENT_STOP; - } - - return EVENT_CONTINUE; - } - - void OnCheckAuthentication(User *u, IdentifyRequest *req) override - { - if (!this->SQL) - { - Log(this) << "Unable to find SQL engine"; - return; - } - - SQL::Query q(this->query); - q.SetValue("a", req->GetAccount()); - q.SetValue("p", req->GetPassword()); - if (u) - { - q.SetValue("n", u->nick); - q.SetValue("i", u->ip.addr()); - } - else - { - q.SetValue("n", ""); - q.SetValue("i", ""); - } - - - this->SQL->Run(new SQLAuthenticationResult(u, req), q); - - Log(LOG_DEBUG) << "m_sql_authentication: Checking authentication for " << req->GetAccount(); - } -}; - -MODULE_INIT(ModuleSQLAuthentication) diff --git a/modules/extra/m_sql_log.cpp b/modules/extra/m_sql_log.cpp deleted file mode 100644 index 6922d168e..000000000 --- a/modules/extra/m_sql_log.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - */ - -#include "module.h" -#include "modules/sql.h" - -class SQLLog : public Module - , public EventHook -{ - std::set inited; - Anope::string table; - - public: - SQLLog(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) - { - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - this->table = config->Get("table", "logs"); - } - - void OnLogMessage(LogInfo *li, const Log *l, const Anope::string &msg) override - { - Anope::string ref_name; - ServiceReference SQL; - - for (unsigned i = 0; i < li->targets.size(); ++i) - { - const Anope::string &target = li->targets[i]; - size_t sz = target.find("sql_log:"); - if (!sz) - { - ref_name = target.substr(8); - SQL = ServiceReference("SQL::Provider", ref_name); - break; - } - } - - if (!SQL) - return; - - if (!inited.count(ref_name)) - { - inited.insert(ref_name); - - SQL::Query create("CREATE TABLE IF NOT EXISTS `" + table + "` (" - "`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," - "`type` varchar(64) NOT NULL," - "`user` varchar(64) NOT NULL," - "`acc` varchar(64) NOT NULL," - "`command` varchar(64) NOT NULL," - "`channel` varchar(64) NOT NULL," - "`msg` text NOT NULL" - ")"); - - SQL->Run(NULL, create); - } - - SQL::Query insert("INSERT INTO `" + table + "` (`type`,`user`,`acc`,`command`,`channel`,`msg`)" - "VALUES (@type@, @user@, @acc@, @command@, @channel@, @msg@)"); - - switch (l->type) - { - case LOG_ADMIN: - insert.SetValue("type", "ADMIN"); - break; - case LOG_OVERRIDE: - insert.SetValue("type", "OVERRIDE"); - break; - case LOG_COMMAND: - insert.SetValue("type", "COMMAND"); - break; - case LOG_SERVER: - insert.SetValue("type", "SERVER"); - break; - case LOG_CHANNEL: - insert.SetValue("type", "CHANNEL"); - break; - case LOG_USER: - insert.SetValue("type", "USER"); - break; - case LOG_MODULE: - insert.SetValue("type", "MODULE"); - break; - case LOG_NORMAL: - insert.SetValue("type", "NORMAL"); - break; - default: - return; - } - - insert.SetValue("user", l->u ? l->u->nick : ""); - insert.SetValue("acc", l->nc ? l->nc->GetDisplay() : ""); - insert.SetValue("command", l->c ? l->c->name : ""); - insert.SetValue("channel", l->ci ? l->ci->GetName() : ""); - insert.SetValue("msg", msg); - - SQL->Run(NULL, insert); - } -}; - -MODULE_INIT(SQLLog) diff --git a/modules/extra/m_sql_oper.cpp b/modules/extra/m_sql_oper.cpp deleted file mode 100644 index 6bddbf0eb..000000000 --- a/modules/extra/m_sql_oper.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include "module.h" -#include "modules/sql.h" - -struct SQLOper : Oper -{ - SQLOper(const Anope::string &n, OperType *o) : Oper(n, o) { } -}; - -class SQLOperResult : public SQL::Interface -{ - Reference user; - - struct SQLOperResultDeleter - { - SQLOperResult *res; - SQLOperResultDeleter(SQLOperResult *r) : res(r) { } - ~SQLOperResultDeleter() { delete res; } - }; - - public: - SQLOperResult(Module *m, User *u) : SQL::Interface(m), user(u) { } - - void OnResult(const SQL::Result &r) override - { - SQLOperResultDeleter d(this); - - if (!user || !user->Account() || r.Rows() == 0) - return; - - Anope::string opertype; - try - { - opertype = r.Get(0, "opertype"); - } - catch (const SQL::Exception &) - { - return; - } - - Log(LOG_DEBUG) << "m_sql_oper: Got result for " << user->nick << ", opertype " << opertype; - - Anope::string modes; - try - { - modes = r.Get(0, "modes"); - } - catch (const SQL::Exception &) { } - - ServiceBot *OperServ = Config->GetClient("OperServ"); - if (opertype.empty()) - { - if (user->Account() && user->Account()->o && dynamic_cast(user->Account()->o)) - { - delete user->Account()->o; - user->Account()->o = NULL; - - Log(this->owner) << "m_sql_oper: Removed services operator from " << user->nick << " (" << user->Account()->GetDisplay() << ")"; - user->RemoveMode(OperServ, "OPER"); // Probably not set, just incase - } - return; - } - - OperType *ot = OperType::Find(opertype); - if (ot == NULL) - { - Log(this->owner) << "m_sql_oper: Oper " << user->nick << " has type " << opertype << ", but this opertype does not exist?"; - return; - } - - if (!user->Account()->o || user->Account()->o->ot != ot) - { - Log(this->owner) << "m_sql_oper: Tieing oper " << user->nick << " to type " << opertype; - user->Account()->o = new SQLOper(user->Account()->GetDisplay(), ot); - } - - if (!user->HasMode("OPER")) - { - IRCD->SendOper(user); - - if (!modes.empty()) - user->SetModes(OperServ, "%s", modes.c_str()); - } - } - - void OnError(const SQL::Result &r) override - { - SQLOperResultDeleter d(this); - Log(this->owner) << "m_sql_oper: Error executing query " << r.GetQuery().query << ": " << r.GetError(); - } -}; - -class ModuleSQLOper : public Module - , public EventHook -{ - Anope::string engine; - Anope::string query; - - ServiceReference SQL; - - public: - ModuleSQLOper(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), - EventHook() - { - } - - ~ModuleSQLOper() - { - for (nickcore_map::const_iterator it = NickServ::AccountList->begin(), it_end = NickServ::AccountList->end(); it != it_end; ++it) - { - NickServ::Account *nc = it->second; - - if (nc->o && dynamic_cast(nc->o)) - { - delete nc->o; - nc->o = NULL; - } - } - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - - this->engine = config->Get("engine"); - this->query = config->Get("query"); - - this->SQL = ServiceReference("SQL::Provider", this->engine); - } - - void OnNickIdentify(User *u) override - { - if (!this->SQL) - { - Log() << "Unable to find SQL engine"; - return; - } - - SQL::Query q(this->query); - q.SetValue("a", u->Account()->GetDisplay()); - q.SetValue("i", u->ip.addr()); - - this->SQL->Run(new SQLOperResult(this, u), q); - - Log(LOG_DEBUG) << "m_sql_oper: Checking authentication for " << u->Account()->GetDisplay(); - } -}; - -MODULE_INIT(ModuleSQLOper) diff --git a/modules/extra/m_sqlite.cpp b/modules/extra/m_sqlite.cpp deleted file mode 100644 index 9370a0d77..000000000 --- a/modules/extra/m_sqlite.cpp +++ /dev/null @@ -1,380 +0,0 @@ -/* RequiredLibraries: sqlite3 */ -/* RequiredWindowsLibraries: sqlite3 */ - -#include "module.h" -#include "modules/sql.h" -#include - -using namespace SQL; - -/* SQLite3 API, based from InspiRCd */ - -/** A SQLite result - */ -class SQLiteResult : public Result -{ - public: - SQLiteResult(sqlite3 *sql, unsigned int id, const Query &q, const Anope::string &fq, sqlite3_stmt *stmt) : Result(id, q, fq) - { - int cols = sqlite3_column_count(stmt); - for (int i = 0; i < cols; ++i) - this->columns.push_back(sqlite3_column_name(stmt, i)); - - int err; - while ((err = sqlite3_step(stmt)) == SQLITE_ROW) - { - std::vector values; - - for (int i = 0; i < cols; ++i) - { - const char *data = reinterpret_cast(sqlite3_column_text(stmt, i)); - - Value v; - v.null = !data; - v.value = data ? data : ""; - values.push_back(v); - } - - this->values.push_back(values); - } - - if (err != SQLITE_DONE) - { - error = sqlite3_errmsg(sql); - } - } - - SQLiteResult(const Query &q, const Anope::string &fq, const Anope::string &err) : Result(0, q, fq, err) - { - } -}; - -/** A SQLite database, there can be multiple - */ -class SQLiteService : public Provider -{ - std::map > active_schema, indexes; - - Anope::string database; - - sqlite3 *sql; - - Anope::string Escape(const Anope::string &query); - - public: - SQLiteService(Module *o, const Anope::string &n, const Anope::string &d); - - ~SQLiteService(); - - void Run(Interface *i, const Query &query) override; - - Result RunQuery(const Query &query); - - std::vector InitSchema(const Anope::string &prefix) override; - std::vector Replace(const Anope::string &table, const Query &, const std::set &) override; - std::vector CreateTable(const Anope::string &, const Anope::string &table) override; - std::vector AlterTable(const Anope::string &, const Anope::string &table, const Anope::string &field, bool) override; - std::vector CreateIndex(const Anope::string &table, const Anope::string &field) override; - - Query BeginTransaction() override; - Query Commit() override; - - Serialize::ID GetID(const Anope::string &) override; - - Query GetTables(const Anope::string &prefix); - - Anope::string BuildQuery(const Query &q); -}; - -class ModuleSQLite : public Module -{ - /* SQL connections */ - std::map SQLiteServices; - - public: - ModuleSQLite(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - { - } - - ~ModuleSQLite() - { - for (std::map::iterator it = this->SQLiteServices.begin(); it != this->SQLiteServices.end(); ++it) - delete it->second; - SQLiteServices.clear(); - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - - for (std::map::iterator it = this->SQLiteServices.begin(); it != this->SQLiteServices.end();) - { - const Anope::string &cname = it->first; - SQLiteService *s = it->second; - int i, num; - ++it; - - for (i = 0, num = config->CountBlock("sqlite"); i < num; ++i) - if (config->GetBlock("sqlite", i)->Get("name", "sqlite/main") == cname) - break; - - if (i == num) - { - Log(LOG_NORMAL, "sqlite") << "SQLite: Removing server connection " << cname; - - delete s; - this->SQLiteServices.erase(cname); - } - } - - for (int i = 0; i < config->CountBlock("sqlite"); ++i) - { - Configuration::Block *block = config->GetBlock("sqlite", i); - Anope::string connname = block->Get("name", "sqlite/main"); - - if (this->SQLiteServices.find(connname) == this->SQLiteServices.end()) - { - Anope::string database = Anope::DataDir + "/" + block->Get("database", "anope"); - - try - { - SQLiteService *ss = new SQLiteService(this, connname, database); - this->SQLiteServices[connname] = ss; - - Log(LOG_NORMAL, "sqlite") << "SQLite: Successfully added database " << database; - } - catch (const SQL::Exception &ex) - { - Log(LOG_NORMAL, "sqlite") << "SQLite: " << ex.GetReason(); - } - } - } - } -}; - -SQLiteService::SQLiteService(Module *o, const Anope::string &n, const Anope::string &d) -: Provider(o, n), database(d), sql(NULL) -{ - int db = sqlite3_open_v2(database.c_str(), &this->sql, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); - if (db != SQLITE_OK) - throw SQL::Exception("Unable to open SQLite database " + database + ": " + sqlite3_errmsg(this->sql)); -} - -SQLiteService::~SQLiteService() -{ - sqlite3_interrupt(this->sql); - sqlite3_close(this->sql); -} - -void SQLiteService::Run(Interface *i, const Query &query) -{ - Result res = this->RunQuery(query); - if (!res.GetError().empty()) - i->OnError(res); - else - i->OnResult(res); -} - -Result SQLiteService::RunQuery(const Query &query) -{ - Anope::string real_query = this->BuildQuery(query); - sqlite3_stmt *stmt; - int err = sqlite3_prepare_v2(this->sql, real_query.c_str(), real_query.length(), &stmt, NULL); - if (err != SQLITE_OK) - { - return SQLiteResult(query, real_query, sqlite3_errmsg(this->sql)); - } - - int id = sqlite3_last_insert_rowid(this->sql); - SQLiteResult result(this->sql, id, query, real_query, stmt); - - sqlite3_finalize(stmt); - - return result; -} - -std::vector SQLiteService::InitSchema(const Anope::string &prefix) -{ - std::vector queries; - - Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + "id` (" - "`id`" - ")"; - queries.push_back(t); - - t = "CREATE TABLE IF NOT EXISTS `" + prefix + "objects` (`id` PRIMARY KEY, `type`)"; - queries.push_back(t); - - t = "CREATE TABLE IF NOT EXISTS `" + prefix + "edges` (" - "`id`," - "`field`," - "`other_id`," - "PRIMARY KEY (`id`, `field`)" - ")"; - queries.push_back(t); - - t = "CREATE INDEX IF NOT EXISTS idx_edge ON `" + prefix + "edges` (other_id)"; - queries.push_back(t); - - return queries; -} - -std::vector SQLiteService::Replace(const Anope::string &table, const Query &q, const std::set &keys) -{ - std::vector queries; - - Anope::string query_text = "INSERT OR IGNORE INTO `" + table + "` ("; - for (const std::pair &p : q.parameters) - query_text += "`" + p.first + "`,"; - query_text.erase(query_text.length() - 1); - query_text += ") VALUES ("; - for (const std::pair &p : q.parameters) - query_text += "@" + p.first + "@,"; - query_text.erase(query_text.length() - 1); - query_text += ")"; - - Query query(query_text); - query.parameters = q.parameters; - queries.push_back(query); - - query_text = "UPDATE `" + table + "` SET "; - for (const std::pair &p : q.parameters) - if (!keys.count(p.first)) - query_text += "`" + p.first + "` = @" + p.first + "@,"; - query_text.erase(query_text.length() - 1); - unsigned int i = 0; - for (const Anope::string &key : keys) - { - if (!i++) - query_text += " WHERE "; - else - query_text += " AND "; - query_text += "`" + key + "` = @" + key + "@"; - } - - query = query_text; - query.parameters = q.parameters; - queries.push_back(query); - - return queries; -} - -std::vector SQLiteService::CreateTable(const Anope::string &prefix, const Anope::string &table) -{ - std::vector queries; - - if (active_schema.find(prefix + table) == active_schema.end()) - { - Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + table + "` (`id` bigint(20) NOT NULL, PRIMARY KEY (`id`))"; - queries.push_back(t); - - active_schema[prefix + table]; - } - - return queries; -} - -std::vector SQLiteService::AlterTable(const Anope::string &prefix, const Anope::string &table, const Anope::string &field, bool) -{ - std::vector queries; - std::set &s = active_schema[prefix + table]; - - if (!s.count(field)) - { - Query t = "ALTER TABLE `" + prefix + table + "` ADD `" + field + "` COLLATE NOCASE"; - queries.push_back(t); - s.insert(field); - } - - return queries; -} - -std::vector SQLiteService::CreateIndex(const Anope::string &table, const Anope::string &field) -{ - std::vector queries; - - if (indexes[table].count(field)) - return queries; - - Query t = "CREATE INDEX IF NOT EXISTS idx_" + field + " ON `" + table + "` (" + field + ")"; - queries.push_back(t); - - indexes[table].insert(field); - - return queries; -} - -Query SQLiteService::BeginTransaction() -{ - return Query("BEGIN TRANSACTION"); -} - -Query SQLiteService::Commit() -{ - return Query("COMMIT"); -} - -Serialize::ID SQLiteService::GetID(const Anope::string &prefix) -{ - /* must be in a deferred or reserved transaction here for atomic row update */ - - Query query("SELECT `id` FROM `" + prefix + "id`"); - Serialize::ID id; - - Result res = RunQuery(query); - if (res.Rows()) - { - id = convertTo(res.Get(0, "id")); - - Query update_query("UPDATE `" + prefix + "id` SET `id` = `id` + 1"); - RunQuery(update_query); - } - else - { - id = 0; - - Query insert_query("INSERT INTO `" + prefix + "id` (id) VALUES(@id@)"); - insert_query.SetValue("id", 1); - RunQuery(insert_query); - } - - return id; -} - -Query SQLiteService::GetTables(const Anope::string &prefix) -{ - return Query("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '" + prefix + "%';"); -} - -Anope::string SQLiteService::Escape(const Anope::string &query) -{ - char *e = sqlite3_mprintf("%q", query.c_str()); - Anope::string buffer = e; - sqlite3_free(e); - return buffer; -} - -Anope::string SQLiteService::BuildQuery(const Query &q) -{ - Anope::string real_query = q.query; - - for (std::map::const_iterator it = q.parameters.begin(), it_end = q.parameters.end(); it != it_end; ++it) - { - const QueryData& qd = it->second; - Anope::string replacement; - - if (qd.null) - replacement = "NULL"; - else if (!qd.escape) - replacement = qd.data; - else - replacement = "'" + this->Escape(qd.data) + "'"; - - real_query = real_query.replace_all_cs("@" + it->first + "@", replacement); - } - - return real_query; -} - -MODULE_INIT(ModuleSQLite) - diff --git a/modules/extra/m_ssl_gnutls.cpp b/modules/extra/m_ssl_gnutls.cpp deleted file mode 100644 index 93a56f03a..000000000 --- a/modules/extra/m_ssl_gnutls.cpp +++ /dev/null @@ -1,658 +0,0 @@ -/* - * (C) 2014 Attila Molnar - * (C) 2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - */ - -/* RequiredLibraries: gnutls */ -/* RequiredWindowsLibraries: libgnutls-28 */ - -#include "module.h" -#include "modules/ssl.h" - -#include -#include -#include - -class GnuTLSModule; -static GnuTLSModule *me; - -namespace GnuTLS { class X509CertCredentials; } - -class MySSLService : public SSLService -{ - public: - MySSLService(Module *o, const Anope::string &n); - - /** Initialize a socket to use SSL - * @param s The socket - */ - void Init(Socket *s) override; -}; - -class SSLSocketIO : public SocketIO -{ - public: - gnutls_session_t sess; - GnuTLS::X509CertCredentials* mycreds; - - /** Constructor - */ - SSLSocketIO(); - - /** Really receive something from the buffer - * @param s The socket - * @param buf The buf to read to - * @param sz How much to read - * @return Number of bytes received - */ - int Recv(Socket *s, char *buf, size_t sz) override; - - /** Write something to the socket - * @param s The socket - * @param buf The data to write - * @param size The length of the data - */ - int Send(Socket *s, const char *buf, size_t sz) override; - - /** Accept a connection from a socket - * @param s The socket - * @return The new socket - */ - ClientSocket *Accept(ListenSocket *s) override; - - /** Finished accepting a connection from a socket - * @param s The socket - * @return SF_ACCEPTED if accepted, SF_ACCEPTING if still in process, SF_DEAD on error - */ - SocketFlag FinishAccept(ClientSocket *cs) override; - - /** Connect the socket - * @param s THe socket - * @param target IP to connect to - * @param port to connect to - */ - void Connect(ConnectionSocket *s, const Anope::string &target, int port) override; - - /** Called to potentially finish a pending connection - * @param s The socket - * @return SF_CONNECTED on success, SF_CONNECTING if still pending, and SF_DEAD on error. - */ - SocketFlag FinishConnect(ConnectionSocket *s) override; - - /** Called when the socket is destructing - */ - void Destroy() override; -}; - -namespace GnuTLS -{ - class Init - { - public: - Init() { gnutls_global_init(); } - ~Init() { gnutls_global_deinit(); } - }; - - /** Used to create a gnutls_datum_t* from an Anope::string - */ - class Datum - { - gnutls_datum_t datum; - - public: - Datum(const Anope::string &dat) - { - datum.data = reinterpret_cast(const_cast(dat.data())); - datum.size = static_cast(dat.length()); - } - - const gnutls_datum_t *get() const { return &datum; } - }; - - class DHParams - { - gnutls_dh_params_t dh_params; - - public: - DHParams() : dh_params(NULL) { } - - void Import(const Anope::string &dhstr) - { - if (dh_params != NULL) - { - gnutls_dh_params_deinit(dh_params); - dh_params = NULL; - } - - int ret = gnutls_dh_params_init(&dh_params); - if (ret < 0) - throw ConfigException("Unable to initialize DH parameters"); - - ret = gnutls_dh_params_import_pkcs3(dh_params, Datum(dhstr).get(), GNUTLS_X509_FMT_PEM); - if (ret < 0) - { - gnutls_dh_params_deinit(dh_params); - dh_params = NULL; - throw ConfigException("Unable to import DH parameters"); - } - } - - ~DHParams() - { - if (dh_params) - gnutls_dh_params_deinit(dh_params); - } - - gnutls_dh_params_t get() const { return dh_params; } - }; - - class X509Key - { - /** Ensure that the key is deinited in case the constructor of X509Key throws - */ - class RAIIKey - { - public: - gnutls_x509_privkey_t key; - - RAIIKey() - { - int ret = gnutls_x509_privkey_init(&key); - if (ret < 0) - throw ConfigException("gnutls_x509_privkey_init() failed"); - } - - ~RAIIKey() - { - gnutls_x509_privkey_deinit(key); - } - } key; - - public: - /** Import */ - X509Key(const Anope::string &keystr) - { - int ret = gnutls_x509_privkey_import(key.key, Datum(keystr).get(), GNUTLS_X509_FMT_PEM); - if (ret < 0) - throw ConfigException("Error loading private key: " + Anope::string(gnutls_strerror(ret))); - } - - gnutls_x509_privkey_t& get() { return key.key; } - }; - - class X509CertList - { - std::vector certs; - - public: - /** Import */ - X509CertList(const Anope::string &certstr) - { - unsigned int certcount = 3; - certs.resize(certcount); - Datum datum(certstr); - - int ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); - if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) - { - // the buffer wasn't big enough to hold all certs but gnutls changed certcount to the number of available certs, - // try again with a bigger buffer - certs.resize(certcount); - ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); - } - - if (ret < 0) - throw ConfigException("Unable to load certificates" + Anope::string(gnutls_strerror(ret))); - - // Resize the vector to the actual number of certs because we rely on its size being correct - // when deallocating the certs - certs.resize(certcount); - } - - ~X509CertList() - { - for (std::vector::iterator i = certs.begin(); i != certs.end(); ++i) - gnutls_x509_crt_deinit(*i); - } - - gnutls_x509_crt_t* raw() { return &certs[0]; } - unsigned int size() const { return certs.size(); } - }; - - class X509CertCredentials - { - unsigned int refcount; - gnutls_certificate_credentials_t cred; - DHParams dh; - - static Anope::string LoadFile(const Anope::string &filename) - { - std::ifstream ifs(filename.c_str()); - const Anope::string ret((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); - return ret; - } - - #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12)) - static int cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr_st* st); - #else - static int cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr2_st* st); - #endif - - public: - X509CertList certs; - X509Key key; - - X509CertCredentials(const Anope::string &certfile, const Anope::string &keyfile) - : refcount(0), certs(LoadFile(certfile)), key(LoadFile(keyfile)) - { - if (gnutls_certificate_allocate_credentials(&cred) < 0) - throw ConfigException("Cannot allocate certificate credentials"); - - int ret = gnutls_certificate_set_x509_key(cred, certs.raw(), certs.size(), key.get()); - if (ret < 0) - { - gnutls_certificate_free_credentials(cred); - throw ConfigException("Unable to set cert/key pair"); - } - - #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12)) - gnutls_certificate_client_set_retrieve_function(cred, cert_callback); - #else - gnutls_certificate_set_retrieve_function(cred, cert_callback); - #endif - } - - ~X509CertCredentials() - { - gnutls_certificate_free_credentials(cred); - } - - void SetupSession(gnutls_session_t sess) - { - gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, cred); - gnutls_set_default_priority(sess); - } - - void SetDH(const Anope::string &dhfile) - { - const Anope::string dhdata = LoadFile(dhfile); - dh.Import(dhdata); - gnutls_certificate_set_dh_params(cred, dh.get()); - } - - bool HasDH() const - { - return (dh.get() != NULL); - } - - void incrref() { refcount++; } - void decrref() { if (!--refcount) delete this; } - }; -} - -class GnuTLSModule : public Module - , public EventHook -{ - GnuTLS::Init libinit; - - public: - GnuTLS::X509CertCredentials *cred; - MySSLService service; - - GnuTLSModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - , cred(NULL) - , service(this, "ssl") - { - me = this; - this->SetPermanent(true); - } - - ~GnuTLSModule() - { - for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) - { - Socket *s = it->second; - ++it; - - if (dynamic_cast(s->io)) - delete s; - } - - if (cred) - cred->decrref(); - } - - static void CheckFile(const Anope::string &filename) - { - if (!Anope::IsFile(filename.c_str())) - { - Log() << "File does not exist: " << filename; - throw ConfigException("Error loading certificate/private key"); - } - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - - const Anope::string certfile = config->Get("cert", "data/anope.crt"); - const Anope::string keyfile = config->Get("key", "data/anope.key"); - const Anope::string dhfile = config->Get("dh", "data/dhparams.pem"); - - CheckFile(certfile); - CheckFile(keyfile); - - GnuTLS::X509CertCredentials *newcred = new GnuTLS::X509CertCredentials(certfile, keyfile); - - // DH params is not mandatory - if (Anope::IsFile(dhfile.c_str())) - { - try - { - newcred->SetDH(dhfile); - } - catch (...) - { - delete newcred; - throw; - } - Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded DH parameters from " << dhfile; - } - - if (cred) - cred->decrref(); - cred = newcred; - cred->incrref(); - - Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded certificate " << certfile << " and private key " << keyfile; - } - - void OnPreServerConnect() override - { - Configuration::Block *config = Config->GetBlock("uplink", Anope::CurrentUplink); - - if (config->Get("ssl")) - { - this->service.Init(UplinkSock); - } - } -}; - -MySSLService::MySSLService(Module *o, const Anope::string &n) : SSLService(o, n) -{ -} - -void MySSLService::Init(Socket *s) -{ - if (s->io != &NormalSocketIO) - throw CoreException("Socket initializing SSL twice"); - - s->io = new SSLSocketIO(); -} - -int SSLSocketIO::Recv(Socket *s, char *buf, size_t sz) -{ - int ret = gnutls_record_recv(this->sess, buf, sz); - - if (ret > 0) - TotalRead += ret; - else if (ret < 0) - { - switch (ret) - { - case GNUTLS_E_AGAIN: - case GNUTLS_E_INTERRUPTED: - SocketEngine::SetLastError(EAGAIN); - break; - default: - if (s == UplinkSock) - { - // Log and fake an errno because this is a fatal error on the uplink socket - Log() << "SSL error: " << gnutls_strerror(ret); - } - SocketEngine::SetLastError(ECONNRESET); - } - } - - return ret; -} - -int SSLSocketIO::Send(Socket *s, const char *buf, size_t sz) -{ - int ret = gnutls_record_send(this->sess, buf, sz); - - if (ret > 0) - TotalWritten += ret; - else - { - switch (ret) - { - case 0: - case GNUTLS_E_AGAIN: - case GNUTLS_E_INTERRUPTED: - SocketEngine::SetLastError(EAGAIN); - break; - default: - if (s == UplinkSock) - { - // Log and fake an errno because this is a fatal error on the uplink socket - Log() << "SSL error: " << gnutls_strerror(ret); - } - SocketEngine::SetLastError(ECONNRESET); - } - } - - return ret; -} - -ClientSocket *SSLSocketIO::Accept(ListenSocket *s) -{ - if (s->io == &NormalSocketIO) - throw SocketException("Attempting to accept on uninitialized socket with SSL"); - - sockaddrs conaddr; - - socklen_t size = sizeof(conaddr); - int newsock = accept(s->GetFD(), &conaddr.sa, &size); - -#ifndef INVALID_SOCKET - const int INVALID_SOCKET = -1; -#endif - - if (newsock < 0 || newsock == INVALID_SOCKET) - throw SocketException("Unable to accept connection: " + Anope::LastError()); - - ClientSocket *newsocket = s->OnAccept(newsock, conaddr); - me->service.Init(newsocket); - SSLSocketIO *io = anope_dynamic_static_cast(newsocket->io); - - if (gnutls_init(&io->sess, GNUTLS_SERVER) != GNUTLS_E_SUCCESS) - throw SocketException("Unable to initialize SSL socket"); - - me->cred->SetupSession(io->sess); - gnutls_transport_set_ptr(io->sess, reinterpret_cast(newsock)); - - newsocket->flags[SF_ACCEPTING] = true; - this->FinishAccept(newsocket); - - return newsocket; -} - -SocketFlag SSLSocketIO::FinishAccept(ClientSocket *cs) -{ - if (cs->io == &NormalSocketIO) - throw SocketException("Attempting to finish connect uninitialized socket with SSL"); - else if (cs->flags[SF_ACCEPTED]) - return SF_ACCEPTED; - else if (!cs->flags[SF_ACCEPTING]) - throw SocketException("SSLSocketIO::FinishAccept called for a socket not accepted nor accepting?"); - - SSLSocketIO *io = anope_dynamic_static_cast(cs->io); - - int ret = gnutls_handshake(io->sess); - if (ret < 0) - { - if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) - { - // gnutls_handshake() wants to read or write again; - // if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write. - if (gnutls_record_get_direction(io->sess) == 0) - { - SocketEngine::Change(cs, false, SF_WRITABLE); - SocketEngine::Change(cs, true, SF_READABLE); - } - else - { - SocketEngine::Change(cs, true, SF_WRITABLE); - SocketEngine::Change(cs, false, SF_READABLE); - } - return SF_ACCEPTING; - } - else - { - cs->OnError(Anope::string(gnutls_strerror(ret))); - cs->flags[SF_DEAD] = true; - cs->flags[SF_ACCEPTING] = false; - return SF_DEAD; - } - } - else - { - cs->flags[SF_ACCEPTED] = true; - cs->flags[SF_ACCEPTING] = false; - SocketEngine::Change(cs, false, SF_WRITABLE); - SocketEngine::Change(cs, true, SF_READABLE); - cs->OnAccept(); - return SF_ACCEPTED; - } -} - -void SSLSocketIO::Connect(ConnectionSocket *s, const Anope::string &target, int port) -{ - if (s->io == &NormalSocketIO) - throw SocketException("Attempting to connect uninitialized socket with SSL"); - - s->flags[SF_CONNECTING] = s->flags[SF_CONNECTED] = false; - - s->conaddr.pton(s->IsIPv6() ? AF_INET6 : AF_INET, target, port); - int c = connect(s->GetFD(), &s->conaddr.sa, s->conaddr.size()); - if (c == -1) - { - if (Anope::LastErrorCode() != EINPROGRESS) - { - s->OnError(Anope::LastError()); - s->flags[SF_DEAD] = true; - return; - } - else - { - SocketEngine::Change(s, true, SF_WRITABLE); - s->flags[SF_CONNECTING] = true; - return; - } - } - else - { - s->flags[SF_CONNECTING] = true; - this->FinishConnect(s); - } -} - -SocketFlag SSLSocketIO::FinishConnect(ConnectionSocket *s) -{ - if (s->io == &NormalSocketIO) - throw SocketException("Attempting to finish connect uninitialized socket with SSL"); - else if (s->flags[SF_CONNECTED]) - return SF_CONNECTED; - else if (!s->flags[SF_CONNECTING]) - throw SocketException("SSLSocketIO::FinishConnect called for a socket not connected nor connecting?"); - - SSLSocketIO *io = anope_dynamic_static_cast(s->io); - - if (io->sess == NULL) - { - if (gnutls_init(&io->sess, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS) - throw SocketException("Unable to initialize SSL socket"); - me->cred->SetupSession(io->sess); - gnutls_transport_set_ptr(io->sess, reinterpret_cast(s->GetFD())); - } - - int ret = gnutls_handshake(io->sess); - if (ret < 0) - { - if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) - { - // gnutls_handshake() wants to read or write again; - // if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write. - if (gnutls_record_get_direction(io->sess) == 0) - { - SocketEngine::Change(s, false, SF_WRITABLE); - SocketEngine::Change(s, true, SF_READABLE); - } - else - { - SocketEngine::Change(s, true, SF_WRITABLE); - SocketEngine::Change(s, false, SF_READABLE); - } - - return SF_CONNECTING; - } - else - { - s->OnError(Anope::string(gnutls_strerror(ret))); - s->flags[SF_CONNECTING] = false; - s->flags[SF_DEAD] = true; - return SF_DEAD; - } - } - else - { - s->flags[SF_CONNECTING] = false; - s->flags[SF_CONNECTED] = true; - SocketEngine::Change(s, false, SF_WRITABLE); - SocketEngine::Change(s, true, SF_READABLE); - s->OnConnect(); - return SF_CONNECTED; - } -} - -void SSLSocketIO::Destroy() -{ - if (this->sess) - { - gnutls_bye(this->sess, GNUTLS_SHUT_WR); - gnutls_deinit(this->sess); - } - - mycreds->decrref(); - - delete this; -} - -SSLSocketIO::SSLSocketIO() : sess(NULL), mycreds(me->cred) -{ - mycreds->incrref(); -} - -#if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12)) -int GnuTLS::X509CertCredentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr_st* st) -{ - st->type = GNUTLS_CRT_X509; -#else -int GnuTLS::X509CertCredentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr2_st* st) -{ - st->cert_type = GNUTLS_CRT_X509; - st->key_type = GNUTLS_PRIVKEY_X509; -#endif - st->ncerts = me->cred->certs.size(); - st->cert.x509 = me->cred->certs.raw(); - st->key.x509 = me->cred->key.get(); - st->deinit_all = 0; - - return 0; -} - -MODULE_INIT(GnuTLSModule) diff --git a/modules/extra/m_ssl_openssl.cpp b/modules/extra/m_ssl_openssl.cpp deleted file mode 100644 index c5e2feefc..000000000 --- a/modules/extra/m_ssl_openssl.cpp +++ /dev/null @@ -1,416 +0,0 @@ -/* RequiredLibraries: ssl,crypto */ -/* RequiredWindowsLibraries: ssleay32,libeay32 */ - -#include "module.h" -#include "modules/ssl.h" - -#define OPENSSL_NO_SHA512 -#include -#include -#include -#include -#include - -static SSL_CTX *server_ctx, *client_ctx; - -class MySSLService : public SSLService -{ - public: - MySSLService(Module *o, const Anope::string &n); - - /** Initialize a socket to use SSL - * @param s The socket - */ - void Init(Socket *s) override; -}; - -class SSLSocketIO : public SocketIO -{ - public: - /* The SSL socket for this socket */ - SSL *sslsock; - - /** Constructor - */ - SSLSocketIO(); - - /** Really receive something from the buffer - * @param s The socket - * @param buf The buf to read to - * @param sz How much to read - * @return Number of bytes received - */ - int Recv(Socket *s, char *buf, size_t sz) override; - - /** Write something to the socket - * @param s The socket - * @param buf The data to write - * @param size The length of the data - */ - int Send(Socket *s, const char *buf, size_t sz) override; - - /** Accept a connection from a socket - * @param s The socket - * @return The new socket - */ - ClientSocket *Accept(ListenSocket *s) override; - - /** Finished accepting a connection from a socket - * @param s The socket - * @return SF_ACCEPTED if accepted, SF_ACCEPTING if still in process, SF_DEAD on error - */ - SocketFlag FinishAccept(ClientSocket *cs) override; - - /** Connect the socket - * @param s THe socket - * @param target IP to connect to - * @param port to connect to - */ - void Connect(ConnectionSocket *s, const Anope::string &target, int port) override; - - /** Called to potentially finish a pending connection - * @param s The socket - * @return SF_CONNECTED on success, SF_CONNECTING if still pending, and SF_DEAD on error. - */ - SocketFlag FinishConnect(ConnectionSocket *s) override; - - /** Called when the socket is destructing - */ - void Destroy() override; -}; - -class SSLModule; -static SSLModule *me; -class SSLModule : public Module - , public EventHook -{ - Anope::string certfile, keyfile; - - public: - MySSLService service; - - SSLModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - , service(this, "ssl") - { - me = this; - - this->SetPermanent(true); - - SSL_library_init(); - SSL_load_error_strings(); - - client_ctx = SSL_CTX_new(SSLv23_client_method()); - server_ctx = SSL_CTX_new(SSLv23_server_method()); - - if (!client_ctx || !server_ctx) - throw ModuleException("Error initializing SSL CTX"); - - long opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_CIPHER_SERVER_PREFERENCE; - SSL_CTX_set_options(client_ctx, opts); - SSL_CTX_set_options(server_ctx, opts); - - SSL_CTX_set_mode(client_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - SSL_CTX_set_mode(server_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - - Anope::string context_name = "Anope"; - SSL_CTX_set_session_id_context(client_ctx, reinterpret_cast(context_name.c_str()), context_name.length()); - SSL_CTX_set_session_id_context(server_ctx, reinterpret_cast(context_name.c_str()), context_name.length()); - } - - ~SSLModule() - { - for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) - { - Socket *s = it->second; - ++it; - - if (dynamic_cast(s->io)) - delete s; - } - - SSL_CTX_free(client_ctx); - SSL_CTX_free(server_ctx); - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - - this->certfile = config->Get("cert", "data/anope.crt"); - this->keyfile = config->Get("key", "data/anope.key"); - - if (Anope::IsFile(this->certfile.c_str())) - { - if (!SSL_CTX_use_certificate_file(client_ctx, this->certfile.c_str(), SSL_FILETYPE_PEM) || !SSL_CTX_use_certificate_file(server_ctx, this->certfile.c_str(), SSL_FILETYPE_PEM)) - throw ConfigException("Error loading certificate"); - else - Log(LOG_DEBUG) << "m_ssl_openssl: Successfully loaded certificate " << this->certfile; - } - else - Log() << "Unable to open certificate " << this->certfile; - - if (Anope::IsFile(this->keyfile.c_str())) - { - if (!SSL_CTX_use_PrivateKey_file(client_ctx, this->keyfile.c_str(), SSL_FILETYPE_PEM) || !SSL_CTX_use_PrivateKey_file(server_ctx, this->keyfile.c_str(), SSL_FILETYPE_PEM)) - throw ConfigException("Error loading private key"); - else - Log(LOG_DEBUG) << "m_ssl_openssl: Successfully loaded private key " << this->keyfile; - } - else - { - if (Anope::IsFile(this->certfile.c_str())) - throw ConfigException("Error loading private key " + this->keyfile + " - file not found"); - else - Log() << "Unable to open private key " << this->keyfile; - } - - // Allow disabling SSLv3 - if (!config->Get("sslv3").empty()) - { - if (config->Get("sslv3")) - { - SSL_CTX_clear_options(client_ctx, SSL_OP_NO_SSLv3); - SSL_CTX_clear_options(server_ctx, SSL_OP_NO_SSLv3); - } - else - { - SSL_CTX_set_options(client_ctx, SSL_OP_NO_SSLv3); - SSL_CTX_set_options(server_ctx, SSL_OP_NO_SSLv3); - } - } - } - - void OnPreServerConnect() override - { - Configuration::Block *config = Config->GetBlock("uplink", Anope::CurrentUplink); - - if (config->Get("ssl")) - { - this->service.Init(UplinkSock); - } - } -}; - -MySSLService::MySSLService(Module *o, const Anope::string &n) : SSLService(o, n) -{ -} - -void MySSLService::Init(Socket *s) -{ - if (s->io != &NormalSocketIO) - throw CoreException("Socket initializing SSL twice"); - - s->io = new SSLSocketIO(); -} - -SSLSocketIO::SSLSocketIO() -{ - this->sslsock = NULL; -} - -int SSLSocketIO::Recv(Socket *s, char *buf, size_t sz) -{ - int i = SSL_read(this->sslsock, buf, sz); - if (i > 0) - TotalRead += i; - else if (i < 0) - { - int err = SSL_get_error(this->sslsock, i); - switch (err) - { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - SocketEngine::SetLastError(EAGAIN); - } - } - - return i; -} - -int SSLSocketIO::Send(Socket *s, const char *buf, size_t sz) -{ - int i = SSL_write(this->sslsock, buf, sz); - if (i > 0) - TotalWritten += i; - else if (i < 0) - { - int err = SSL_get_error(this->sslsock, i); - switch (err) - { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - SocketEngine::SetLastError(EAGAIN); - } - } - return i; -} - -ClientSocket *SSLSocketIO::Accept(ListenSocket *s) -{ - if (s->io == &NormalSocketIO) - throw SocketException("Attempting to accept on uninitialized socket with SSL"); - - sockaddrs conaddr; - - socklen_t size = sizeof(conaddr); - int newsock = accept(s->GetFD(), &conaddr.sa, &size); - -#ifndef INVALID_SOCKET - const int INVALID_SOCKET = -1; -#endif - - if (newsock < 0 || newsock == INVALID_SOCKET) - throw SocketException("Unable to accept connection: " + Anope::LastError()); - - ClientSocket *newsocket = s->OnAccept(newsock, conaddr); - me->service.Init(newsocket); - SSLSocketIO *io = anope_dynamic_static_cast(newsocket->io); - - io->sslsock = SSL_new(server_ctx); - if (!io->sslsock) - throw SocketException("Unable to initialize SSL socket"); - - SSL_set_accept_state(io->sslsock); - - if (!SSL_set_fd(io->sslsock, newsocket->GetFD())) - throw SocketException("Unable to set SSL fd"); - - newsocket->flags[SF_ACCEPTING] = true; - this->FinishAccept(newsocket); - - return newsocket; -} - -SocketFlag SSLSocketIO::FinishAccept(ClientSocket *cs) -{ - if (cs->io == &NormalSocketIO) - throw SocketException("Attempting to finish connect uninitialized socket with SSL"); - else if (cs->flags[SF_ACCEPTED]) - return SF_ACCEPTED; - else if (!cs->flags[SF_ACCEPTING]) - throw SocketException("SSLSocketIO::FinishAccept called for a socket not accepted nor accepting?"); - - SSLSocketIO *io = anope_dynamic_static_cast(cs->io); - - int ret = SSL_accept(io->sslsock); - if (ret <= 0) - { - int error = SSL_get_error(io->sslsock, ret); - if (ret == -1 && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)) - { - SocketEngine::Change(cs, error == SSL_ERROR_WANT_WRITE, SF_WRITABLE); - SocketEngine::Change(cs, error == SSL_ERROR_WANT_READ, SF_READABLE); - return SF_ACCEPTING; - } - else - { - cs->OnError(ERR_error_string(ERR_get_error(), NULL)); - cs->flags[SF_DEAD] = true; - cs->flags[SF_ACCEPTING] = false; - return SF_DEAD; - } - } - else - { - cs->flags[SF_ACCEPTED] = true; - cs->flags[SF_ACCEPTING] = false; - SocketEngine::Change(cs, false, SF_WRITABLE); - SocketEngine::Change(cs, true, SF_READABLE); - cs->OnAccept(); - return SF_ACCEPTED; - } -} - -void SSLSocketIO::Connect(ConnectionSocket *s, const Anope::string &target, int port) -{ - if (s->io == &NormalSocketIO) - throw SocketException("Attempting to connect uninitialized socket with SSL"); - - s->flags[SF_CONNECTING] = s->flags[SF_CONNECTED] = false; - - s->conaddr.pton(s->IsIPv6() ? AF_INET6 : AF_INET, target, port); - int c = connect(s->GetFD(), &s->conaddr.sa, s->conaddr.size()); - if (c == -1) - { - if (Anope::LastErrorCode() != EINPROGRESS) - { - s->OnError(Anope::LastError()); - s->flags[SF_DEAD] = true; - return; - } - else - { - SocketEngine::Change(s, true, SF_WRITABLE); - s->flags[SF_CONNECTING] = true; - return; - } - } - else - { - s->flags[SF_CONNECTING] = true; - this->FinishConnect(s); - } -} - -SocketFlag SSLSocketIO::FinishConnect(ConnectionSocket *s) -{ - if (s->io == &NormalSocketIO) - throw SocketException("Attempting to finish connect uninitialized socket with SSL"); - else if (s->flags[SF_CONNECTED]) - return SF_CONNECTED; - else if (!s->flags[SF_CONNECTING]) - throw SocketException("SSLSocketIO::FinishConnect called for a socket not connected nor connecting?"); - - SSLSocketIO *io = anope_dynamic_static_cast(s->io); - - if (io->sslsock == NULL) - { - io->sslsock = SSL_new(client_ctx); - if (!io->sslsock) - throw SocketException("Unable to initialize SSL socket"); - - if (!SSL_set_fd(io->sslsock, s->GetFD())) - throw SocketException("Unable to set SSL fd"); - } - - int ret = SSL_connect(io->sslsock); - if (ret <= 0) - { - int error = SSL_get_error(io->sslsock, ret); - if (ret == -1 && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)) - { - SocketEngine::Change(s, error == SSL_ERROR_WANT_WRITE, SF_WRITABLE); - SocketEngine::Change(s, error == SSL_ERROR_WANT_READ, SF_READABLE); - return SF_CONNECTING; - } - else - { - s->OnError(ERR_error_string(ERR_get_error(), NULL)); - s->flags[SF_CONNECTING] = false; - s->flags[SF_DEAD] = true; - return SF_DEAD; - } - } - else - { - s->flags[SF_CONNECTING] = false; - s->flags[SF_CONNECTED] = true; - SocketEngine::Change(s, false, SF_WRITABLE); - SocketEngine::Change(s, true, SF_READABLE); - s->OnConnect(); - return SF_CONNECTED; - } -} - -void SSLSocketIO::Destroy() -{ - if (this->sslsock) - { - SSL_shutdown(this->sslsock); - SSL_free(this->sslsock); - } - - delete this; -} - -MODULE_INIT(SSLModule) diff --git a/modules/extra/mysql.cpp b/modules/extra/mysql.cpp new file mode 100644 index 000000000..843a2ba0a --- /dev/null +++ b/modules/extra/mysql.cpp @@ -0,0 +1,614 @@ +/* RequiredLibraries: mysqlclient */ +/* RequiredWindowsLibraries: libmysql */ + +#include "module.h" +#include "modules/sql.h" +#define NO_CLIENT_LONG_LONG +#ifdef WIN32 +# include +#else +# include +#endif + +using namespace SQL; + +/** Non blocking threaded MySQL API, based loosely from InspIRCd's m_mysql.cpp + * + * This module spawns a single thread that is used to execute blocking MySQL queries. + * When a module requests a query to be executed it is added to a list for the thread + * (which never stops looping and sleeing) to pick up and execute, the result of which + * is inserted in to another queue to be picked up by the main thread. The main thread + * uses Pipe to become notified through the socket engine when there are results waiting + * to be sent back to the modules requesting the query + */ + +class MySQLService; + +/** A query request + */ +struct QueryRequest +{ + /* The connection to the database */ + MySQLService *service; + /* The interface to use once we have the result to send the data back */ + Interface *sqlinterface; + /* The actual query */ + Query query; + + QueryRequest(MySQLService *s, Interface *i, const Query &q) : service(s), sqlinterface(i), query(q) { } +}; + +/** A query result */ +struct QueryResult +{ + /* The interface to send the data back on */ + Interface *sqlinterface; + /* The result */ + Result result; + + QueryResult(Interface *i, Result &r) : sqlinterface(i), result(r) { } +}; + +/** A MySQL result + */ +class MySQLResult : public Result +{ + MYSQL_RES *res; + + public: + MySQLResult(unsigned int i, const Query &q, const Anope::string &fq, MYSQL_RES *r) : Result(i, q, fq), res(r) + { + if (!res) + return; + + unsigned num_fields = mysql_num_fields(res); + MYSQL_FIELD *fields = mysql_fetch_fields(res); + + /* It is not thread safe to log anything here using Log(this->owner) now :( */ + + if (!num_fields || !fields) + return; + + for (unsigned field_count = 0; field_count < num_fields; ++field_count) + columns.push_back(fields[field_count].name ? fields[field_count].name : ""); + + for (MYSQL_ROW row; (row = mysql_fetch_row(res));) + { + std::vector values; + + for (unsigned field_count = 0; field_count < num_fields; ++field_count) + { + const char *data = row[field_count]; + + Value v; + v.null = !data; + v.value = data ? data : ""; + values.push_back(v); + } + + this->values.push_back(values); + } + } + + MySQLResult(const Query &q, const Anope::string &fq, const Anope::string &err) : Result(0, q, fq, err), res(NULL) + { + } + + ~MySQLResult() + { + if (this->res) + mysql_free_result(this->res); + } +}; + +/** A MySQL connection, there can be multiple + */ +class MySQLService : public Provider +{ + std::map > active_schema, indexes; + + Anope::string database; + Anope::string server; + Anope::string user; + Anope::string password; + int port; + + MYSQL *sql; + + /** Escape a query. + * Note the mutex must be held! + */ + Anope::string Escape(const Anope::string &query); + + public: + /* Locked by the SQL thread when a query is pending on this database, + * prevents us from deleting a connection while a query is executing + * in the thread + */ + Mutex Lock; + + MySQLService(Module *o, const Anope::string &n, const Anope::string &d, const Anope::string &s, const Anope::string &u, const Anope::string &p, int po); + + ~MySQLService(); + + void Run(Interface *i, const Query &query) override; + + Result RunQuery(const Query &query) override; + + std::vector InitSchema(const Anope::string &prefix) override; + std::vector Replace(const Anope::string &table, const Query &, const std::set &) override; + std::vector CreateTable(const Anope::string &prefix, const Anope::string &table) override; + std::vector AlterTable(const Anope::string &, const Anope::string &table, const Anope::string &field, bool) override; + std::vector CreateIndex(const Anope::string &table, const Anope::string &field) override; + + Query BeginTransaction() override; + Query Commit() override; + + Serialize::ID GetID(const Anope::string &) override; + + Query GetTables(const Anope::string &prefix) override; + + void Connect(); + + bool CheckConnection(); + + Anope::string BuildQuery(const Query &q); +}; + +/** The SQL thread used to execute queries + */ +class DispatcherThread : public Thread, public Condition +{ + public: + DispatcherThread() : Thread() { } + + void Run() override; +}; + +class ModuleSQL; +static ModuleSQL *me; +class ModuleSQL : public Module + , public Pipe + , public EventHook +{ + /* SQL connections */ + std::map MySQLServices; + public: + /* Pending query requests */ + std::deque QueryRequests; + /* Pending finished requests with results */ + std::deque FinishedRequests; + /* The thread used to execute queries */ + DispatcherThread *DThread; + + ModuleSQL(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + { + me = this; + + + DThread = new DispatcherThread(); + DThread->Start(); + } + + ~ModuleSQL() + { + for (std::map::iterator it = this->MySQLServices.begin(); it != this->MySQLServices.end(); ++it) + delete it->second; + MySQLServices.clear(); + + DThread->SetExitState(); + DThread->Wakeup(); + DThread->Join(); + delete DThread; + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + + for (std::map::iterator it = this->MySQLServices.begin(); it != this->MySQLServices.end();) + { + const Anope::string &cname = it->first; + MySQLService *s = it->second; + int i; + + ++it; + + for (i = 0; i < config->CountBlock("mysql"); ++i) + if (config->GetBlock("mysql", i)->Get("name", "mysql/main") == cname) + break; + + if (i == config->CountBlock("mysql")) + { + Log(LOG_NORMAL, "mysql") << "MySQL: Removing server connection " << cname; + + delete s; + this->MySQLServices.erase(cname); + } + } + + for (int i = 0; i < config->CountBlock("mysql"); ++i) + { + Configuration::Block *block = config->GetBlock("mysql", i); + const Anope::string &connname = block->Get("name", "mysql/main"); + + if (this->MySQLServices.find(connname) == this->MySQLServices.end()) + { + const Anope::string &database = block->Get("database", "anope"); + const Anope::string &server = block->Get("server", "127.0.0.1"); + const Anope::string &user = block->Get("username", "anope"); + const Anope::string &password = block->Get("password"); + int port = block->Get("port", "3306"); + + try + { + MySQLService *ss = new MySQLService(this, connname, database, server, user, password, port); + this->MySQLServices.insert(std::make_pair(connname, ss)); + + Log(LOG_NORMAL, "mysql") << "MySQL: Successfully connected to server " << connname << " (" << server << ")"; + } + catch (const SQL::Exception &ex) + { + Log(LOG_NORMAL, "mysql") << "MySQL: " << ex.GetReason(); + } + } + } + } + + void OnModuleUnload(User *, Module *m) override + { + this->DThread->Lock(); + + for (unsigned i = this->QueryRequests.size(); i > 0; --i) + { + QueryRequest &r = this->QueryRequests[i - 1]; + + if (r.sqlinterface && r.sqlinterface->owner == m) + { + if (i == 1) + { + r.service->Lock.Lock(); + r.service->Lock.Unlock(); + } + + this->QueryRequests.erase(this->QueryRequests.begin() + i - 1); + } + } + + this->DThread->Unlock(); + + this->OnNotify(); + } + + void OnNotify() override + { + this->DThread->Lock(); + std::deque finishedRequests = this->FinishedRequests; + this->FinishedRequests.clear(); + this->DThread->Unlock(); + + for (std::deque::const_iterator it = finishedRequests.begin(), it_end = finishedRequests.end(); it != it_end; ++it) + { + const QueryResult &qr = *it; + + if (!qr.sqlinterface) + throw SQL::Exception("NULL qr.sqlinterface in MySQLPipe::OnNotify() ?"); + + if (qr.result.GetError().empty()) + qr.sqlinterface->OnResult(qr.result); + else + qr.sqlinterface->OnError(qr.result); + } + } +}; + +MySQLService::MySQLService(Module *o, const Anope::string &n, const Anope::string &d, const Anope::string &s, const Anope::string &u, const Anope::string &p, int po) +: Provider(o, n), database(d), server(s), user(u), password(p), port(po), sql(NULL) +{ + Connect(); +} + +MySQLService::~MySQLService() +{ + me->DThread->Lock(); + this->Lock.Lock(); + mysql_close(this->sql); + this->sql = NULL; + + for (unsigned i = me->QueryRequests.size(); i > 0; --i) + { + QueryRequest &r = me->QueryRequests[i - 1]; + + if (r.service == this) + { + if (r.sqlinterface) + r.sqlinterface->OnError(Result(0, r.query, "SQL Interface is going away")); + me->QueryRequests.erase(me->QueryRequests.begin() + i - 1); + } + } + this->Lock.Unlock(); + me->DThread->Unlock(); +} + +void MySQLService::Run(Interface *i, const Query &query) +{ + me->DThread->Lock(); + me->QueryRequests.push_back(QueryRequest(this, i, query)); + me->DThread->Unlock(); + me->DThread->Wakeup(); +} + +Result MySQLService::RunQuery(const Query &query) +{ + this->Lock.Lock(); + + Anope::string real_query = this->BuildQuery(query); + + if (this->CheckConnection() && !mysql_real_query(this->sql, real_query.c_str(), real_query.length())) + { + MYSQL_RES *res = mysql_store_result(this->sql); + unsigned int id = mysql_insert_id(this->sql); + + /* because we enabled CLIENT_MULTI_RESULTS in our options + * a multiple statement or a procedure call can return + * multiple result sets. + * we must process them all before the next query. + */ + + while (!mysql_next_result(this->sql)) + mysql_free_result(mysql_store_result(this->sql)); + + this->Lock.Unlock(); + return MySQLResult(id, query, real_query, res); + } + else + { + Anope::string error = mysql_error(this->sql); + this->Lock.Unlock(); + return MySQLResult(query, real_query, error); + } +} + +std::vector MySQLService::InitSchema(const Anope::string &prefix) +{ + std::vector queries; + + Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + "id` (" + "`id` bigint(20) NOT NULL" + ") ENGINE=InnoDB"; + queries.push_back(t); + + t = "CREATE TABLE IF NOT EXISTS `" + prefix + "objects` (`id` bigint(20) NOT NULL PRIMARY KEY, `type` varchar(256)) ENGINE=InnoDB"; + queries.push_back(t); + + t = "CREATE TABLE IF NOT EXISTS `" + prefix + "edges` (" + "`id` bigint(20) NOT NULL," + "`field` varchar(64) NOT NULL," + "`other_id` bigint(20) NOT NULL," + "PRIMARY KEY (`id`, `field`)," + "KEY `other` (`other_id`)," + "CONSTRAINT `edges_id_fk` FOREIGN KEY (`id`) REFERENCES `" + prefix + "objects` (`id`)," + "CONSTRAINT `edges_other_id_fk` FOREIGN KEY (`other_id`) REFERENCES `" + prefix + "objects` (`id`)" + ") ENGINE=InnoDB"; + queries.push_back(t); + + return queries; +} + +std::vector MySQLService::Replace(const Anope::string &table, const Query &q, const std::set &keys) +{ + std::vector queries; + + Anope::string query_text = "INSERT INTO `" + table + "` ("; + for (const std::pair &p : q.parameters) + query_text += "`" + p.first + "`,"; + query_text.erase(query_text.length() - 1); + query_text += ") VALUES ("; + for (const std::pair &p : q.parameters) + query_text += "@" + p.first + "@,"; + query_text.erase(query_text.length() - 1); + query_text += ") ON DUPLICATE KEY UPDATE "; + for (const std::pair &p : q.parameters) + if (!keys.count(p.first)) + query_text += "`" + p.first + "` = VALUES(`" + p.first + "`),"; + query_text.erase(query_text.length() - 1); + + Query query(query_text); + query.parameters = q.parameters; + + queries.push_back(query); + + return queries; +} + +std::vector MySQLService::CreateTable(const Anope::string &prefix, const Anope::string &table) +{ + std::vector queries; + + if (active_schema.find(prefix + table) == active_schema.end()) + { + Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + table + "` (`id` bigint(20) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB"; + queries.push_back(t); + + t = "ALTER TABLE `" + prefix + table + "` " + "ADD CONSTRAINT `" + table + "_id_fk` FOREIGN KEY (`id`) REFERENCES `" + prefix + "objects` (`id`)"; + queries.push_back(t); + + active_schema[prefix + table]; + } + + return queries; +} + +std::vector MySQLService::AlterTable(const Anope::string &prefix, const Anope::string &table, const Anope::string &field, bool object) +{ + std::vector queries; + std::set &s = active_schema[prefix + table]; + + if (!s.count(field)) + { + Query column; + if (!object) + column = "ALTER TABLE `" + prefix + table + "` ADD COLUMN `" + field + "` TINYTEXT"; + else + column = "ALTER TABLE `" + prefix + table + "` " + "ADD COLUMN `" + field + "` bigint(20), " + "ADD CONSTRAINT `" + table + "_" + field + "_fk` FOREIGN KEY (`" + field + "`) REFERENCES `" + prefix + "objects` (`id`)"; + queries.push_back(column); + s.insert(field); + } + + return queries; +} + +std::vector MySQLService::CreateIndex(const Anope::string &table, const Anope::string &field) +{ + std::vector queries; + + if (indexes[table].count(field)) + return queries; + + Query t = "ALTER TABLE `" + table + "` ADD KEY `idx_" + field + "` (`" + field + "`(512))"; + queries.push_back(t); + + indexes[table].insert(field); + + return queries; +} + +Query MySQLService::BeginTransaction() +{ + return Query("START TRANSACTION WITH CONSISTENT SNAPSHOT"); +} + +Query MySQLService::Commit() +{ + return Query("COMMIT"); +} + +Serialize::ID MySQLService::GetID(const Anope::string &prefix) +{ + Query query("SELECT `id` FROM `" + prefix + "id` FOR UPDATE"); + Serialize::ID id; + + Result res = RunQuery(query); + if (res.Rows()) + { + id = convertTo(res.Get(0, "id")); + + Query update_query("UPDATE `" + prefix + "id` SET `id` = `id` + 1"); + RunQuery(update_query); + } + else + { + id = 0; + + Query insert_query("INSERT INTO `" + prefix + "id` (id) VALUES(@id@)"); + insert_query.SetValue("id", 1); + RunQuery(insert_query); + } + + return id; +} + +Query MySQLService::GetTables(const Anope::string &prefix) +{ + return Query("SHOW TABLES LIKE '" + prefix + "%';"); +} + +void MySQLService::Connect() +{ + this->sql = mysql_init(this->sql); + + const unsigned int timeout = 1; + mysql_options(this->sql, MYSQL_OPT_CONNECT_TIMEOUT, reinterpret_cast(&timeout)); + + bool connect = mysql_real_connect(this->sql, this->server.c_str(), this->user.c_str(), this->password.c_str(), this->database.c_str(), this->port, NULL, CLIENT_MULTI_RESULTS); + + if (!connect) + throw SQL::Exception("Unable to connect to MySQL service " + this->name + ": " + mysql_error(this->sql)); + + Log(LOG_DEBUG) << "Successfully connected to MySQL service " << this->name << " at " << this->server << ":" << this->port; +} + + +bool MySQLService::CheckConnection() +{ + if (!this->sql || mysql_ping(this->sql)) + { + try + { + this->Connect(); + } + catch (const SQL::Exception &) + { + return false; + } + } + + return true; +} + +Anope::string MySQLService::Escape(const Anope::string &query) +{ + std::vector buffer(query.length() * 2 + 1); + mysql_real_escape_string(this->sql, &buffer[0], query.c_str(), query.length()); + return &buffer[0]; +} + +Anope::string MySQLService::BuildQuery(const Query &q) +{ + Anope::string real_query = q.query; + + for (std::map::const_iterator it = q.parameters.begin(), it_end = q.parameters.end(); it != it_end; ++it) + { + const QueryData& qd = it->second; + Anope::string replacement; + + if (qd.null) + replacement = "NULL"; + else if (!qd.escape) + replacement = qd.data; + else + replacement = "'" + this->Escape(qd.data) + "'"; + + real_query = real_query.replace_all_cs("@" + it->first + "@", replacement); + } + + return real_query; +} + +void DispatcherThread::Run() +{ + this->Lock(); + + while (!this->GetExitState()) + { + if (!me->QueryRequests.empty()) + { + QueryRequest &r = me->QueryRequests.front(); + this->Unlock(); + + Result sresult = r.service->RunQuery(r.query); + + this->Lock(); + if (!me->QueryRequests.empty() && me->QueryRequests.front().query == r.query) + { + if (r.sqlinterface) + me->FinishedRequests.push_back(QueryResult(r.sqlinterface, sresult)); + me->QueryRequests.pop_front(); + } + } + else + { + if (!me->FinishedRequests.empty()) + me->Notify(); + this->Wait(); + } + } + + this->Unlock(); +} + +MODULE_INIT(ModuleSQL) + diff --git a/modules/extra/sasl_dh-aes.cpp b/modules/extra/sasl_dh-aes.cpp new file mode 100644 index 000000000..dd06d118b --- /dev/null +++ b/modules/extra/sasl_dh-aes.cpp @@ -0,0 +1,184 @@ +/* RequiredLibraries: ssl,crypto */ +/* RequiredWindowsLibraries: ssleay32,libeay32 */ + +#include "module.h" +#include "modules/sasl.h" + +#include +#include +#include + +using namespace SASL; + +class DHAES : public Mechanism +{ + void Err(Session* sess, BIGNUM* key = NULL) + { + if (key) + BN_free(key); + + sasl->Fail(sess); + delete sess; + } + + public: + struct DHAESSession : SASL::Session + { + DH* dh; + DHAESSession(Mechanism *m, const Anope::string &u, DH* dh_params) : SASL::Session(m, u) + { + if (!(dh = DH_new())) + return; + + dh->g = BN_dup(dh_params->g); + dh->p = BN_dup(dh_params->p); + + if (!DH_generate_key(dh)) + { + DH_free(dh); + dh = NULL; + } + } + + ~DHAESSession() + { + if (dh) + DH_free(dh); + } + }; + + DH* dh_params; + const size_t keysize; + SASL::Session* CreateSession(const Anope::string &uid) override + { + return new DHAESSession(this, uid, dh_params); + } + + DHAES(Module *o) : Mechanism(o, "DH-AES"), keysize(256 / 8) + { + if (!(dh_params = DH_new())) + throw ModuleException("DH_new() failed!"); + + if (!DH_generate_parameters_ex(dh_params, keysize * 8, 5, NULL)) + { + DH_free(dh_params); + throw ModuleException("Could not generate DH-params"); + } + } + + ~DHAES() + { + DH_free(dh_params); + } + + void ProcessMessage(SASL::Session *session, const SASL::Message &m) override + { + DHAESSession *sess = anope_dynamic_static_cast(session); + + if (!sess->dh) + { + sasl->SendMessage(sess, "D", "A"); + delete sess; + return; + } + + if (m.type == "S") + { + // Format: [ss]

[ss][ss] + // Where ss is a unsigned short with the size of the key + const BIGNUM* dhval[] = { sess->dh->p, sess->dh->g, sess->dh->pub_key }; + + // Find the size of our buffer - initialized at 6 because of string size data + size_t size = 6; + for (size_t i = 0; i < 3; i++) + size += BN_num_bytes(dhval[i]); + + // Fill in the DH data + std::vector buffer(size); + for (size_t i = 0, pos = 0; i < 3; i++) + { + *reinterpret_cast(&buffer[pos]) = htons(BN_num_bytes(dhval[i])); + pos += 2; + BN_bn2bin(dhval[i], &buffer[pos]); + pos += BN_num_bytes(dhval[i]); + } + + Anope::string encoded; + Anope::B64Encode(Anope::string(buffer.begin(), buffer.end()), encoded); + sasl->SendMessage(sess, "C", encoded); + } + else if (m.type == "C") + { + // Make sure we have some data - actual size check is done later + if (m.data.length() < 10) + return Err(sess); + + // Format: [ss][ss][ss] + // = \0\0 + + Anope::string decoded; + Anope::B64Decode(m.data, decoded); + + // Make sure we have an IV and at least one encrypted block + if ((decoded.length() < keysize + 2 + (AES_BLOCK_SIZE * 2)) || ((decoded.length() - keysize - 2) % AES_BLOCK_SIZE)) + return Err(sess); + + const unsigned char* data = reinterpret_cast(decoded.data()); + + // Control the size of the key + if (ntohs(*reinterpret_cast(&data[0])) != keysize) + return Err(sess); + + // Convert pubkey from binary + size_t pos = 2; + BIGNUM* pubkey = BN_bin2bn(&data[pos], keysize, NULL); + if (!pubkey) + return Err(sess); + + // Find shared key + std::vector secretkey(keysize); + if (DH_compute_key(&secretkey[0], pubkey, sess->dh) != static_cast(keysize)) + return Err(sess, pubkey); + + // Set decryption key + AES_KEY AESKey; + AES_set_decrypt_key(&secretkey[0], keysize * 8, &AESKey); + + // Fetch IV + pos += keysize; + std::vector IV(data + pos, data + pos + AES_BLOCK_SIZE); + + // Find encrypted blocks, and decrypt + pos += AES_BLOCK_SIZE; + size_t size = decoded.length() - pos; + std::vector decrypted(size + 2, 0); + AES_cbc_encrypt(&data[pos], reinterpret_cast(&decrypted[0]), size, &AESKey, &IV[0], AES_DECRYPT); + + std::string username = &decrypted[0]; + std::string password = &decrypted[username.length() + 1]; + + if (username.empty() || password.empty() || !IRCD->IsNickValid(username) || password.find_first_of("\r\n") != Anope::string::npos) + return Err(sess, pubkey); + + SASL::IdentifyRequest* req = new SASL::IdentifyRequest(this->owner, m.source, username, password); + Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, nullptr, req); + req->Dispatch(); + + BN_free(pubkey); + } + } +}; + + +class ModuleSASLDHAES : public Module +{ + DHAES dhaes; + + public: + ModuleSASLDHAES(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) + , dhaes(this) + { + } +}; + +MODULE_INIT(ModuleSASLDHAES) diff --git a/modules/extra/sasl_dh-blowfish.cpp b/modules/extra/sasl_dh-blowfish.cpp new file mode 100644 index 000000000..f6e4b27b3 --- /dev/null +++ b/modules/extra/sasl_dh-blowfish.cpp @@ -0,0 +1,194 @@ +/* RequiredLibraries: ssl,crypto */ +/* RequiredWindowsLibraries: ssleay32,libeay32 */ + +#include "module.h" +#include "modules/sasl.h" + +#include +#include +#include + +using namespace SASL; + +class DHBS : public Mechanism +{ + void Err(Session* sess, BIGNUM* key = NULL) + { + if (key) + BN_free(key); + + sasl->Fail(sess); + delete sess; + } + + public: + struct DHBSSession : SASL::Session + { + DH* dh; + DHBSSession(Mechanism *m, const Anope::string &u, DH* dh_params) : SASL::Session(m, u) + { + if (!(dh = DH_new())) + return; + + dh->g = BN_dup(dh_params->g); + dh->p = BN_dup(dh_params->p); + + if (!DH_generate_key(dh)) + { + DH_free(dh); + dh = NULL; + } + } + + ~DHBSSession() + { + if (dh) + DH_free(dh); + } + }; + + DH* dh_params; + const size_t keysize; + SASL::Session* CreateSession(const Anope::string &uid) override + { + return new DHBSSession(this, uid, dh_params); + } + + DHBS(Module *o) : Mechanism(o, "DH-BLOWFISH"), keysize(256 / 8) + { + if (!(dh_params = DH_new())) + throw ModuleException("DH_new() failed!"); + + if (!DH_generate_parameters_ex(dh_params, keysize * 8, 5, NULL)) + { + DH_free(dh_params); + throw ModuleException("Could not generate DH-params"); + } + } + + ~DHBS() + { + DH_free(dh_params); + } + + void ProcessMessage(SASL::Session *session, const SASL::Message &m) override + { + DHBSSession *sess = anope_dynamic_static_cast(session); + + if (!sess->dh) + { + sasl->SendMessage(sess, "D", "A"); + delete sess; + return; + } + + if (m.type == "S") + { + // Format: [ss]

[ss][ss] + // Where ss is a unsigned short with the size of the key + const BIGNUM* dhval[] = { sess->dh->p, sess->dh->g, sess->dh->pub_key }; + + // Find the size of our buffer - initialized at 6 because of string size data + size_t size = 6; + for (size_t i = 0; i < 3; i++) + size += BN_num_bytes(dhval[i]); + + // Fill in the DH data + std::vector buffer(size); + for (size_t i = 0, pos = 0; i < 3; i++) + { + *reinterpret_cast(&buffer[pos]) = htons(BN_num_bytes(dhval[i])); + pos += 2; + BN_bn2bin(dhval[i], &buffer[pos]); + pos += BN_num_bytes(dhval[i]); + } + + Anope::string encoded; + Anope::B64Encode(Anope::string(buffer.begin(), buffer.end()), encoded); + sasl->SendMessage(sess, "C", encoded); + } + else if (m.type == "C") + { + // Make sure we have some data - actual size check is done later + if (m.data.length() < 10) + return Err(sess); + + // Format: [ss]<\0> + + Anope::string decoded; + Anope::B64Decode(m.data, decoded); + + // As we rely on the client giving us a null terminator at the right place, + // let's add one extra in case the client tries to crash us + const size_t decodedlen = decoded.length(); + decoded.push_back('\0'); + + // Make sure we have enough data for at least the key, a one letter username, and a block of data + if (decodedlen < keysize + 2 + 2 + 8) + return Err(sess); + + const unsigned char* data = reinterpret_cast(decoded.data()); + + // Control the size of the key + if (ntohs(*reinterpret_cast(&data[0])) != keysize) + return Err(sess); + + // Convert pubkey from binary + size_t pos = 2; + BIGNUM* pubkey = BN_bin2bn(&data[pos], keysize, NULL); + if (!pubkey) + return Err(sess); + + // Find shared key + std::vector secretkey(DH_size(sess->dh) + 1, 0); + if (DH_compute_key(&secretkey[0], pubkey, sess->dh) != static_cast(keysize)) + return Err(sess, pubkey); + + // Set decryption key + BF_KEY BFKey; + BF_set_key(&BFKey, keysize, &secretkey[0]); + + pos += keysize; + const Anope::string username = reinterpret_cast(&data[pos]); + // Check that the username is valid, and that we have at least one block of data + // 2 + 1 + 8 = uint16_t size for keylen, \0 for username, 8 for one block of data + if (username.empty() || username.length() + keysize + 2 + 1 + 8 > decodedlen || !IRCD->IsNickValid(username)) + return Err(sess, pubkey); + + pos += username.length() + 1; + size_t size = decodedlen - pos; + + // Blowfish data blocks are 64 bits wide - valid format? + if (size % 8) + return Err(sess, pubkey); + + std::vector decrypted(size + 1, 0); + for (size_t i = 0; i < size; i += 8) + BF_ecb_encrypt(&data[pos + i], reinterpret_cast(&decrypted[i]), &BFKey, BF_DECRYPT); + + std::string password = &decrypted[0]; + if (password.empty() || password.find_first_of("\r\n") != Anope::string::npos) + return Err(sess, pubkey); + + SASL::IdentifyRequest* req = new SASL::IdentifyRequest(this->owner, m.source, username, password); + Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, nullptr, req); + req->Dispatch(); + + BN_free(pubkey); + } + } +}; + + +class ModuleSASLDHBS : public Module +{ + DHBS dhbs; + + public: + ModuleSASLDHBS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) + , dhbs(this) + { + } +}; + +MODULE_INIT(ModuleSASLDHBS) diff --git a/modules/extra/sql_authentication.cpp b/modules/extra/sql_authentication.cpp new file mode 100644 index 000000000..1c2dbe729 --- /dev/null +++ b/modules/extra/sql_authentication.cpp @@ -0,0 +1,143 @@ +#include "module.h" +#include "modules/sql.h" +#include "modules/nickserv.h" + +static Module *me; + +class SQLAuthenticationResult : public SQL::Interface +{ + Reference user; + IdentifyRequest *req; + + public: + SQLAuthenticationResult(User *u, IdentifyRequest *r) : SQL::Interface(me), user(u), req(r) + { + req->Hold(me); + } + + ~SQLAuthenticationResult() + { + req->Release(me); + } + + void OnResult(const SQL::Result &r) override + { + if (r.Rows() == 0) + { + Log(LOG_DEBUG) << "m_sql_authentication: Unsuccessful authentication for " << req->GetAccount(); + delete this; + return; + } + + Log(LOG_DEBUG) << "m_sql_authentication: Successful authentication for " << req->GetAccount(); + + Anope::string email; + try + { + email = r.Get(0, "email"); + } + catch (const SQL::Exception &) { } + + NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); + ServiceBot *NickServ = Config->GetClient("NickServ"); + if (na == NULL) + { + na = new NickServ::Nick(req->GetAccount(), new NickServ::Account(req->GetAccount())); + NickServ::Event::OnNickRegister(&NickServ::Event::NickRegister::OnNickRegister, user, na, ""); + if (user && NickServ) + user->SendMessage(NickServ, _("Your account \002%s\002 has been successfully created."), na->GetNick().c_str()); + } + + if (!email.empty() && email != na->GetAccount()->GetEmail()) + { + na->GetAccount()->GetEmail() = email; + if (user && NickServ) + user->SendMessage(NickServ, _("Your email has been updated to \002%s\002."), email.c_str()); + } + + req->Success(me); + delete this; + } + + void OnError(const SQL::Result &r) override + { + Log(this->owner) << "m_sql_authentication: Error executing query " << r.GetQuery().query << ": " << r.GetError(); + delete this; + } +}; + +class ModuleSQLAuthentication : public Module + , public EventHook + , public EventHook +{ + Anope::string engine; + Anope::string query; + Anope::string disable_reason, disable_email_reason; + + ServiceReference SQL; + + public: + ModuleSQLAuthentication(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + { + me = this; + + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + this->engine = config->Get("engine"); + this->query = config->Get("query"); + this->disable_reason = config->Get("disable_reason"); + this->disable_email_reason = config->Get("disable_email_reason"); + + this->SQL = ServiceReference("SQL::Provider", this->engine); + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override + { + if (!this->disable_reason.empty() && (command->name == "nickserv/register" || command->name == "nickserv/group")) + { + source.Reply(this->disable_reason); + return EVENT_STOP; + } + + if (!this->disable_email_reason.empty() && command->name == "nickserv/set/email") + { + source.Reply(this->disable_email_reason); + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + void OnCheckAuthentication(User *u, IdentifyRequest *req) override + { + if (!this->SQL) + { + Log(this) << "Unable to find SQL engine"; + return; + } + + SQL::Query q(this->query); + q.SetValue("a", req->GetAccount()); + q.SetValue("p", req->GetPassword()); + if (u) + { + q.SetValue("n", u->nick); + q.SetValue("i", u->ip.addr()); + } + else + { + q.SetValue("n", ""); + q.SetValue("i", ""); + } + + + this->SQL->Run(new SQLAuthenticationResult(u, req), q); + + Log(LOG_DEBUG) << "m_sql_authentication: Checking authentication for " << req->GetAccount(); + } +}; + +MODULE_INIT(ModuleSQLAuthentication) diff --git a/modules/extra/sql_log.cpp b/modules/extra/sql_log.cpp new file mode 100644 index 000000000..6922d168e --- /dev/null +++ b/modules/extra/sql_log.cpp @@ -0,0 +1,110 @@ +/* + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + */ + +#include "module.h" +#include "modules/sql.h" + +class SQLLog : public Module + , public EventHook +{ + std::set inited; + Anope::string table; + + public: + SQLLog(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) + { + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + this->table = config->Get("table", "logs"); + } + + void OnLogMessage(LogInfo *li, const Log *l, const Anope::string &msg) override + { + Anope::string ref_name; + ServiceReference SQL; + + for (unsigned i = 0; i < li->targets.size(); ++i) + { + const Anope::string &target = li->targets[i]; + size_t sz = target.find("sql_log:"); + if (!sz) + { + ref_name = target.substr(8); + SQL = ServiceReference("SQL::Provider", ref_name); + break; + } + } + + if (!SQL) + return; + + if (!inited.count(ref_name)) + { + inited.insert(ref_name); + + SQL::Query create("CREATE TABLE IF NOT EXISTS `" + table + "` (" + "`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," + "`type` varchar(64) NOT NULL," + "`user` varchar(64) NOT NULL," + "`acc` varchar(64) NOT NULL," + "`command` varchar(64) NOT NULL," + "`channel` varchar(64) NOT NULL," + "`msg` text NOT NULL" + ")"); + + SQL->Run(NULL, create); + } + + SQL::Query insert("INSERT INTO `" + table + "` (`type`,`user`,`acc`,`command`,`channel`,`msg`)" + "VALUES (@type@, @user@, @acc@, @command@, @channel@, @msg@)"); + + switch (l->type) + { + case LOG_ADMIN: + insert.SetValue("type", "ADMIN"); + break; + case LOG_OVERRIDE: + insert.SetValue("type", "OVERRIDE"); + break; + case LOG_COMMAND: + insert.SetValue("type", "COMMAND"); + break; + case LOG_SERVER: + insert.SetValue("type", "SERVER"); + break; + case LOG_CHANNEL: + insert.SetValue("type", "CHANNEL"); + break; + case LOG_USER: + insert.SetValue("type", "USER"); + break; + case LOG_MODULE: + insert.SetValue("type", "MODULE"); + break; + case LOG_NORMAL: + insert.SetValue("type", "NORMAL"); + break; + default: + return; + } + + insert.SetValue("user", l->u ? l->u->nick : ""); + insert.SetValue("acc", l->nc ? l->nc->GetDisplay() : ""); + insert.SetValue("command", l->c ? l->c->name : ""); + insert.SetValue("channel", l->ci ? l->ci->GetName() : ""); + insert.SetValue("msg", msg); + + SQL->Run(NULL, insert); + } +}; + +MODULE_INIT(SQLLog) diff --git a/modules/extra/sql_oper.cpp b/modules/extra/sql_oper.cpp new file mode 100644 index 000000000..6bddbf0eb --- /dev/null +++ b/modules/extra/sql_oper.cpp @@ -0,0 +1,148 @@ +#include "module.h" +#include "modules/sql.h" + +struct SQLOper : Oper +{ + SQLOper(const Anope::string &n, OperType *o) : Oper(n, o) { } +}; + +class SQLOperResult : public SQL::Interface +{ + Reference user; + + struct SQLOperResultDeleter + { + SQLOperResult *res; + SQLOperResultDeleter(SQLOperResult *r) : res(r) { } + ~SQLOperResultDeleter() { delete res; } + }; + + public: + SQLOperResult(Module *m, User *u) : SQL::Interface(m), user(u) { } + + void OnResult(const SQL::Result &r) override + { + SQLOperResultDeleter d(this); + + if (!user || !user->Account() || r.Rows() == 0) + return; + + Anope::string opertype; + try + { + opertype = r.Get(0, "opertype"); + } + catch (const SQL::Exception &) + { + return; + } + + Log(LOG_DEBUG) << "m_sql_oper: Got result for " << user->nick << ", opertype " << opertype; + + Anope::string modes; + try + { + modes = r.Get(0, "modes"); + } + catch (const SQL::Exception &) { } + + ServiceBot *OperServ = Config->GetClient("OperServ"); + if (opertype.empty()) + { + if (user->Account() && user->Account()->o && dynamic_cast(user->Account()->o)) + { + delete user->Account()->o; + user->Account()->o = NULL; + + Log(this->owner) << "m_sql_oper: Removed services operator from " << user->nick << " (" << user->Account()->GetDisplay() << ")"; + user->RemoveMode(OperServ, "OPER"); // Probably not set, just incase + } + return; + } + + OperType *ot = OperType::Find(opertype); + if (ot == NULL) + { + Log(this->owner) << "m_sql_oper: Oper " << user->nick << " has type " << opertype << ", but this opertype does not exist?"; + return; + } + + if (!user->Account()->o || user->Account()->o->ot != ot) + { + Log(this->owner) << "m_sql_oper: Tieing oper " << user->nick << " to type " << opertype; + user->Account()->o = new SQLOper(user->Account()->GetDisplay(), ot); + } + + if (!user->HasMode("OPER")) + { + IRCD->SendOper(user); + + if (!modes.empty()) + user->SetModes(OperServ, "%s", modes.c_str()); + } + } + + void OnError(const SQL::Result &r) override + { + SQLOperResultDeleter d(this); + Log(this->owner) << "m_sql_oper: Error executing query " << r.GetQuery().query << ": " << r.GetError(); + } +}; + +class ModuleSQLOper : public Module + , public EventHook +{ + Anope::string engine; + Anope::string query; + + ServiceReference SQL; + + public: + ModuleSQLOper(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), + EventHook() + { + } + + ~ModuleSQLOper() + { + for (nickcore_map::const_iterator it = NickServ::AccountList->begin(), it_end = NickServ::AccountList->end(); it != it_end; ++it) + { + NickServ::Account *nc = it->second; + + if (nc->o && dynamic_cast(nc->o)) + { + delete nc->o; + nc->o = NULL; + } + } + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + + this->engine = config->Get("engine"); + this->query = config->Get("query"); + + this->SQL = ServiceReference("SQL::Provider", this->engine); + } + + void OnNickIdentify(User *u) override + { + if (!this->SQL) + { + Log() << "Unable to find SQL engine"; + return; + } + + SQL::Query q(this->query); + q.SetValue("a", u->Account()->GetDisplay()); + q.SetValue("i", u->ip.addr()); + + this->SQL->Run(new SQLOperResult(this, u), q); + + Log(LOG_DEBUG) << "m_sql_oper: Checking authentication for " << u->Account()->GetDisplay(); + } +}; + +MODULE_INIT(ModuleSQLOper) diff --git a/modules/extra/sqlite.cpp b/modules/extra/sqlite.cpp new file mode 100644 index 000000000..9370a0d77 --- /dev/null +++ b/modules/extra/sqlite.cpp @@ -0,0 +1,380 @@ +/* RequiredLibraries: sqlite3 */ +/* RequiredWindowsLibraries: sqlite3 */ + +#include "module.h" +#include "modules/sql.h" +#include + +using namespace SQL; + +/* SQLite3 API, based from InspiRCd */ + +/** A SQLite result + */ +class SQLiteResult : public Result +{ + public: + SQLiteResult(sqlite3 *sql, unsigned int id, const Query &q, const Anope::string &fq, sqlite3_stmt *stmt) : Result(id, q, fq) + { + int cols = sqlite3_column_count(stmt); + for (int i = 0; i < cols; ++i) + this->columns.push_back(sqlite3_column_name(stmt, i)); + + int err; + while ((err = sqlite3_step(stmt)) == SQLITE_ROW) + { + std::vector values; + + for (int i = 0; i < cols; ++i) + { + const char *data = reinterpret_cast(sqlite3_column_text(stmt, i)); + + Value v; + v.null = !data; + v.value = data ? data : ""; + values.push_back(v); + } + + this->values.push_back(values); + } + + if (err != SQLITE_DONE) + { + error = sqlite3_errmsg(sql); + } + } + + SQLiteResult(const Query &q, const Anope::string &fq, const Anope::string &err) : Result(0, q, fq, err) + { + } +}; + +/** A SQLite database, there can be multiple + */ +class SQLiteService : public Provider +{ + std::map > active_schema, indexes; + + Anope::string database; + + sqlite3 *sql; + + Anope::string Escape(const Anope::string &query); + + public: + SQLiteService(Module *o, const Anope::string &n, const Anope::string &d); + + ~SQLiteService(); + + void Run(Interface *i, const Query &query) override; + + Result RunQuery(const Query &query); + + std::vector InitSchema(const Anope::string &prefix) override; + std::vector Replace(const Anope::string &table, const Query &, const std::set &) override; + std::vector CreateTable(const Anope::string &, const Anope::string &table) override; + std::vector AlterTable(const Anope::string &, const Anope::string &table, const Anope::string &field, bool) override; + std::vector CreateIndex(const Anope::string &table, const Anope::string &field) override; + + Query BeginTransaction() override; + Query Commit() override; + + Serialize::ID GetID(const Anope::string &) override; + + Query GetTables(const Anope::string &prefix); + + Anope::string BuildQuery(const Query &q); +}; + +class ModuleSQLite : public Module +{ + /* SQL connections */ + std::map SQLiteServices; + + public: + ModuleSQLite(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + { + } + + ~ModuleSQLite() + { + for (std::map::iterator it = this->SQLiteServices.begin(); it != this->SQLiteServices.end(); ++it) + delete it->second; + SQLiteServices.clear(); + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + + for (std::map::iterator it = this->SQLiteServices.begin(); it != this->SQLiteServices.end();) + { + const Anope::string &cname = it->first; + SQLiteService *s = it->second; + int i, num; + ++it; + + for (i = 0, num = config->CountBlock("sqlite"); i < num; ++i) + if (config->GetBlock("sqlite", i)->Get("name", "sqlite/main") == cname) + break; + + if (i == num) + { + Log(LOG_NORMAL, "sqlite") << "SQLite: Removing server connection " << cname; + + delete s; + this->SQLiteServices.erase(cname); + } + } + + for (int i = 0; i < config->CountBlock("sqlite"); ++i) + { + Configuration::Block *block = config->GetBlock("sqlite", i); + Anope::string connname = block->Get("name", "sqlite/main"); + + if (this->SQLiteServices.find(connname) == this->SQLiteServices.end()) + { + Anope::string database = Anope::DataDir + "/" + block->Get("database", "anope"); + + try + { + SQLiteService *ss = new SQLiteService(this, connname, database); + this->SQLiteServices[connname] = ss; + + Log(LOG_NORMAL, "sqlite") << "SQLite: Successfully added database " << database; + } + catch (const SQL::Exception &ex) + { + Log(LOG_NORMAL, "sqlite") << "SQLite: " << ex.GetReason(); + } + } + } + } +}; + +SQLiteService::SQLiteService(Module *o, const Anope::string &n, const Anope::string &d) +: Provider(o, n), database(d), sql(NULL) +{ + int db = sqlite3_open_v2(database.c_str(), &this->sql, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); + if (db != SQLITE_OK) + throw SQL::Exception("Unable to open SQLite database " + database + ": " + sqlite3_errmsg(this->sql)); +} + +SQLiteService::~SQLiteService() +{ + sqlite3_interrupt(this->sql); + sqlite3_close(this->sql); +} + +void SQLiteService::Run(Interface *i, const Query &query) +{ + Result res = this->RunQuery(query); + if (!res.GetError().empty()) + i->OnError(res); + else + i->OnResult(res); +} + +Result SQLiteService::RunQuery(const Query &query) +{ + Anope::string real_query = this->BuildQuery(query); + sqlite3_stmt *stmt; + int err = sqlite3_prepare_v2(this->sql, real_query.c_str(), real_query.length(), &stmt, NULL); + if (err != SQLITE_OK) + { + return SQLiteResult(query, real_query, sqlite3_errmsg(this->sql)); + } + + int id = sqlite3_last_insert_rowid(this->sql); + SQLiteResult result(this->sql, id, query, real_query, stmt); + + sqlite3_finalize(stmt); + + return result; +} + +std::vector SQLiteService::InitSchema(const Anope::string &prefix) +{ + std::vector queries; + + Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + "id` (" + "`id`" + ")"; + queries.push_back(t); + + t = "CREATE TABLE IF NOT EXISTS `" + prefix + "objects` (`id` PRIMARY KEY, `type`)"; + queries.push_back(t); + + t = "CREATE TABLE IF NOT EXISTS `" + prefix + "edges` (" + "`id`," + "`field`," + "`other_id`," + "PRIMARY KEY (`id`, `field`)" + ")"; + queries.push_back(t); + + t = "CREATE INDEX IF NOT EXISTS idx_edge ON `" + prefix + "edges` (other_id)"; + queries.push_back(t); + + return queries; +} + +std::vector SQLiteService::Replace(const Anope::string &table, const Query &q, const std::set &keys) +{ + std::vector queries; + + Anope::string query_text = "INSERT OR IGNORE INTO `" + table + "` ("; + for (const std::pair &p : q.parameters) + query_text += "`" + p.first + "`,"; + query_text.erase(query_text.length() - 1); + query_text += ") VALUES ("; + for (const std::pair &p : q.parameters) + query_text += "@" + p.first + "@,"; + query_text.erase(query_text.length() - 1); + query_text += ")"; + + Query query(query_text); + query.parameters = q.parameters; + queries.push_back(query); + + query_text = "UPDATE `" + table + "` SET "; + for (const std::pair &p : q.parameters) + if (!keys.count(p.first)) + query_text += "`" + p.first + "` = @" + p.first + "@,"; + query_text.erase(query_text.length() - 1); + unsigned int i = 0; + for (const Anope::string &key : keys) + { + if (!i++) + query_text += " WHERE "; + else + query_text += " AND "; + query_text += "`" + key + "` = @" + key + "@"; + } + + query = query_text; + query.parameters = q.parameters; + queries.push_back(query); + + return queries; +} + +std::vector SQLiteService::CreateTable(const Anope::string &prefix, const Anope::string &table) +{ + std::vector queries; + + if (active_schema.find(prefix + table) == active_schema.end()) + { + Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + table + "` (`id` bigint(20) NOT NULL, PRIMARY KEY (`id`))"; + queries.push_back(t); + + active_schema[prefix + table]; + } + + return queries; +} + +std::vector SQLiteService::AlterTable(const Anope::string &prefix, const Anope::string &table, const Anope::string &field, bool) +{ + std::vector queries; + std::set &s = active_schema[prefix + table]; + + if (!s.count(field)) + { + Query t = "ALTER TABLE `" + prefix + table + "` ADD `" + field + "` COLLATE NOCASE"; + queries.push_back(t); + s.insert(field); + } + + return queries; +} + +std::vector SQLiteService::CreateIndex(const Anope::string &table, const Anope::string &field) +{ + std::vector queries; + + if (indexes[table].count(field)) + return queries; + + Query t = "CREATE INDEX IF NOT EXISTS idx_" + field + " ON `" + table + "` (" + field + ")"; + queries.push_back(t); + + indexes[table].insert(field); + + return queries; +} + +Query SQLiteService::BeginTransaction() +{ + return Query("BEGIN TRANSACTION"); +} + +Query SQLiteService::Commit() +{ + return Query("COMMIT"); +} + +Serialize::ID SQLiteService::GetID(const Anope::string &prefix) +{ + /* must be in a deferred or reserved transaction here for atomic row update */ + + Query query("SELECT `id` FROM `" + prefix + "id`"); + Serialize::ID id; + + Result res = RunQuery(query); + if (res.Rows()) + { + id = convertTo(res.Get(0, "id")); + + Query update_query("UPDATE `" + prefix + "id` SET `id` = `id` + 1"); + RunQuery(update_query); + } + else + { + id = 0; + + Query insert_query("INSERT INTO `" + prefix + "id` (id) VALUES(@id@)"); + insert_query.SetValue("id", 1); + RunQuery(insert_query); + } + + return id; +} + +Query SQLiteService::GetTables(const Anope::string &prefix) +{ + return Query("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '" + prefix + "%';"); +} + +Anope::string SQLiteService::Escape(const Anope::string &query) +{ + char *e = sqlite3_mprintf("%q", query.c_str()); + Anope::string buffer = e; + sqlite3_free(e); + return buffer; +} + +Anope::string SQLiteService::BuildQuery(const Query &q) +{ + Anope::string real_query = q.query; + + for (std::map::const_iterator it = q.parameters.begin(), it_end = q.parameters.end(); it != it_end; ++it) + { + const QueryData& qd = it->second; + Anope::string replacement; + + if (qd.null) + replacement = "NULL"; + else if (!qd.escape) + replacement = qd.data; + else + replacement = "'" + this->Escape(qd.data) + "'"; + + real_query = real_query.replace_all_cs("@" + it->first + "@", replacement); + } + + return real_query; +} + +MODULE_INIT(ModuleSQLite) + diff --git a/modules/extra/ssl_gnutls.cpp b/modules/extra/ssl_gnutls.cpp new file mode 100644 index 000000000..93a56f03a --- /dev/null +++ b/modules/extra/ssl_gnutls.cpp @@ -0,0 +1,658 @@ +/* + * (C) 2014 Attila Molnar + * (C) 2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +/* RequiredLibraries: gnutls */ +/* RequiredWindowsLibraries: libgnutls-28 */ + +#include "module.h" +#include "modules/ssl.h" + +#include +#include +#include + +class GnuTLSModule; +static GnuTLSModule *me; + +namespace GnuTLS { class X509CertCredentials; } + +class MySSLService : public SSLService +{ + public: + MySSLService(Module *o, const Anope::string &n); + + /** Initialize a socket to use SSL + * @param s The socket + */ + void Init(Socket *s) override; +}; + +class SSLSocketIO : public SocketIO +{ + public: + gnutls_session_t sess; + GnuTLS::X509CertCredentials* mycreds; + + /** Constructor + */ + SSLSocketIO(); + + /** Really receive something from the buffer + * @param s The socket + * @param buf The buf to read to + * @param sz How much to read + * @return Number of bytes received + */ + int Recv(Socket *s, char *buf, size_t sz) override; + + /** Write something to the socket + * @param s The socket + * @param buf The data to write + * @param size The length of the data + */ + int Send(Socket *s, const char *buf, size_t sz) override; + + /** Accept a connection from a socket + * @param s The socket + * @return The new socket + */ + ClientSocket *Accept(ListenSocket *s) override; + + /** Finished accepting a connection from a socket + * @param s The socket + * @return SF_ACCEPTED if accepted, SF_ACCEPTING if still in process, SF_DEAD on error + */ + SocketFlag FinishAccept(ClientSocket *cs) override; + + /** Connect the socket + * @param s THe socket + * @param target IP to connect to + * @param port to connect to + */ + void Connect(ConnectionSocket *s, const Anope::string &target, int port) override; + + /** Called to potentially finish a pending connection + * @param s The socket + * @return SF_CONNECTED on success, SF_CONNECTING if still pending, and SF_DEAD on error. + */ + SocketFlag FinishConnect(ConnectionSocket *s) override; + + /** Called when the socket is destructing + */ + void Destroy() override; +}; + +namespace GnuTLS +{ + class Init + { + public: + Init() { gnutls_global_init(); } + ~Init() { gnutls_global_deinit(); } + }; + + /** Used to create a gnutls_datum_t* from an Anope::string + */ + class Datum + { + gnutls_datum_t datum; + + public: + Datum(const Anope::string &dat) + { + datum.data = reinterpret_cast(const_cast(dat.data())); + datum.size = static_cast(dat.length()); + } + + const gnutls_datum_t *get() const { return &datum; } + }; + + class DHParams + { + gnutls_dh_params_t dh_params; + + public: + DHParams() : dh_params(NULL) { } + + void Import(const Anope::string &dhstr) + { + if (dh_params != NULL) + { + gnutls_dh_params_deinit(dh_params); + dh_params = NULL; + } + + int ret = gnutls_dh_params_init(&dh_params); + if (ret < 0) + throw ConfigException("Unable to initialize DH parameters"); + + ret = gnutls_dh_params_import_pkcs3(dh_params, Datum(dhstr).get(), GNUTLS_X509_FMT_PEM); + if (ret < 0) + { + gnutls_dh_params_deinit(dh_params); + dh_params = NULL; + throw ConfigException("Unable to import DH parameters"); + } + } + + ~DHParams() + { + if (dh_params) + gnutls_dh_params_deinit(dh_params); + } + + gnutls_dh_params_t get() const { return dh_params; } + }; + + class X509Key + { + /** Ensure that the key is deinited in case the constructor of X509Key throws + */ + class RAIIKey + { + public: + gnutls_x509_privkey_t key; + + RAIIKey() + { + int ret = gnutls_x509_privkey_init(&key); + if (ret < 0) + throw ConfigException("gnutls_x509_privkey_init() failed"); + } + + ~RAIIKey() + { + gnutls_x509_privkey_deinit(key); + } + } key; + + public: + /** Import */ + X509Key(const Anope::string &keystr) + { + int ret = gnutls_x509_privkey_import(key.key, Datum(keystr).get(), GNUTLS_X509_FMT_PEM); + if (ret < 0) + throw ConfigException("Error loading private key: " + Anope::string(gnutls_strerror(ret))); + } + + gnutls_x509_privkey_t& get() { return key.key; } + }; + + class X509CertList + { + std::vector certs; + + public: + /** Import */ + X509CertList(const Anope::string &certstr) + { + unsigned int certcount = 3; + certs.resize(certcount); + Datum datum(certstr); + + int ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); + if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) + { + // the buffer wasn't big enough to hold all certs but gnutls changed certcount to the number of available certs, + // try again with a bigger buffer + certs.resize(certcount); + ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); + } + + if (ret < 0) + throw ConfigException("Unable to load certificates" + Anope::string(gnutls_strerror(ret))); + + // Resize the vector to the actual number of certs because we rely on its size being correct + // when deallocating the certs + certs.resize(certcount); + } + + ~X509CertList() + { + for (std::vector::iterator i = certs.begin(); i != certs.end(); ++i) + gnutls_x509_crt_deinit(*i); + } + + gnutls_x509_crt_t* raw() { return &certs[0]; } + unsigned int size() const { return certs.size(); } + }; + + class X509CertCredentials + { + unsigned int refcount; + gnutls_certificate_credentials_t cred; + DHParams dh; + + static Anope::string LoadFile(const Anope::string &filename) + { + std::ifstream ifs(filename.c_str()); + const Anope::string ret((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + return ret; + } + + #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12)) + static int cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr_st* st); + #else + static int cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr2_st* st); + #endif + + public: + X509CertList certs; + X509Key key; + + X509CertCredentials(const Anope::string &certfile, const Anope::string &keyfile) + : refcount(0), certs(LoadFile(certfile)), key(LoadFile(keyfile)) + { + if (gnutls_certificate_allocate_credentials(&cred) < 0) + throw ConfigException("Cannot allocate certificate credentials"); + + int ret = gnutls_certificate_set_x509_key(cred, certs.raw(), certs.size(), key.get()); + if (ret < 0) + { + gnutls_certificate_free_credentials(cred); + throw ConfigException("Unable to set cert/key pair"); + } + + #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12)) + gnutls_certificate_client_set_retrieve_function(cred, cert_callback); + #else + gnutls_certificate_set_retrieve_function(cred, cert_callback); + #endif + } + + ~X509CertCredentials() + { + gnutls_certificate_free_credentials(cred); + } + + void SetupSession(gnutls_session_t sess) + { + gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, cred); + gnutls_set_default_priority(sess); + } + + void SetDH(const Anope::string &dhfile) + { + const Anope::string dhdata = LoadFile(dhfile); + dh.Import(dhdata); + gnutls_certificate_set_dh_params(cred, dh.get()); + } + + bool HasDH() const + { + return (dh.get() != NULL); + } + + void incrref() { refcount++; } + void decrref() { if (!--refcount) delete this; } + }; +} + +class GnuTLSModule : public Module + , public EventHook +{ + GnuTLS::Init libinit; + + public: + GnuTLS::X509CertCredentials *cred; + MySSLService service; + + GnuTLSModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + , cred(NULL) + , service(this, "ssl") + { + me = this; + this->SetPermanent(true); + } + + ~GnuTLSModule() + { + for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) + { + Socket *s = it->second; + ++it; + + if (dynamic_cast(s->io)) + delete s; + } + + if (cred) + cred->decrref(); + } + + static void CheckFile(const Anope::string &filename) + { + if (!Anope::IsFile(filename.c_str())) + { + Log() << "File does not exist: " << filename; + throw ConfigException("Error loading certificate/private key"); + } + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + + const Anope::string certfile = config->Get("cert", "data/anope.crt"); + const Anope::string keyfile = config->Get("key", "data/anope.key"); + const Anope::string dhfile = config->Get("dh", "data/dhparams.pem"); + + CheckFile(certfile); + CheckFile(keyfile); + + GnuTLS::X509CertCredentials *newcred = new GnuTLS::X509CertCredentials(certfile, keyfile); + + // DH params is not mandatory + if (Anope::IsFile(dhfile.c_str())) + { + try + { + newcred->SetDH(dhfile); + } + catch (...) + { + delete newcred; + throw; + } + Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded DH parameters from " << dhfile; + } + + if (cred) + cred->decrref(); + cred = newcred; + cred->incrref(); + + Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded certificate " << certfile << " and private key " << keyfile; + } + + void OnPreServerConnect() override + { + Configuration::Block *config = Config->GetBlock("uplink", Anope::CurrentUplink); + + if (config->Get("ssl")) + { + this->service.Init(UplinkSock); + } + } +}; + +MySSLService::MySSLService(Module *o, const Anope::string &n) : SSLService(o, n) +{ +} + +void MySSLService::Init(Socket *s) +{ + if (s->io != &NormalSocketIO) + throw CoreException("Socket initializing SSL twice"); + + s->io = new SSLSocketIO(); +} + +int SSLSocketIO::Recv(Socket *s, char *buf, size_t sz) +{ + int ret = gnutls_record_recv(this->sess, buf, sz); + + if (ret > 0) + TotalRead += ret; + else if (ret < 0) + { + switch (ret) + { + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + SocketEngine::SetLastError(EAGAIN); + break; + default: + if (s == UplinkSock) + { + // Log and fake an errno because this is a fatal error on the uplink socket + Log() << "SSL error: " << gnutls_strerror(ret); + } + SocketEngine::SetLastError(ECONNRESET); + } + } + + return ret; +} + +int SSLSocketIO::Send(Socket *s, const char *buf, size_t sz) +{ + int ret = gnutls_record_send(this->sess, buf, sz); + + if (ret > 0) + TotalWritten += ret; + else + { + switch (ret) + { + case 0: + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + SocketEngine::SetLastError(EAGAIN); + break; + default: + if (s == UplinkSock) + { + // Log and fake an errno because this is a fatal error on the uplink socket + Log() << "SSL error: " << gnutls_strerror(ret); + } + SocketEngine::SetLastError(ECONNRESET); + } + } + + return ret; +} + +ClientSocket *SSLSocketIO::Accept(ListenSocket *s) +{ + if (s->io == &NormalSocketIO) + throw SocketException("Attempting to accept on uninitialized socket with SSL"); + + sockaddrs conaddr; + + socklen_t size = sizeof(conaddr); + int newsock = accept(s->GetFD(), &conaddr.sa, &size); + +#ifndef INVALID_SOCKET + const int INVALID_SOCKET = -1; +#endif + + if (newsock < 0 || newsock == INVALID_SOCKET) + throw SocketException("Unable to accept connection: " + Anope::LastError()); + + ClientSocket *newsocket = s->OnAccept(newsock, conaddr); + me->service.Init(newsocket); + SSLSocketIO *io = anope_dynamic_static_cast(newsocket->io); + + if (gnutls_init(&io->sess, GNUTLS_SERVER) != GNUTLS_E_SUCCESS) + throw SocketException("Unable to initialize SSL socket"); + + me->cred->SetupSession(io->sess); + gnutls_transport_set_ptr(io->sess, reinterpret_cast(newsock)); + + newsocket->flags[SF_ACCEPTING] = true; + this->FinishAccept(newsocket); + + return newsocket; +} + +SocketFlag SSLSocketIO::FinishAccept(ClientSocket *cs) +{ + if (cs->io == &NormalSocketIO) + throw SocketException("Attempting to finish connect uninitialized socket with SSL"); + else if (cs->flags[SF_ACCEPTED]) + return SF_ACCEPTED; + else if (!cs->flags[SF_ACCEPTING]) + throw SocketException("SSLSocketIO::FinishAccept called for a socket not accepted nor accepting?"); + + SSLSocketIO *io = anope_dynamic_static_cast(cs->io); + + int ret = gnutls_handshake(io->sess); + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) + { + // gnutls_handshake() wants to read or write again; + // if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write. + if (gnutls_record_get_direction(io->sess) == 0) + { + SocketEngine::Change(cs, false, SF_WRITABLE); + SocketEngine::Change(cs, true, SF_READABLE); + } + else + { + SocketEngine::Change(cs, true, SF_WRITABLE); + SocketEngine::Change(cs, false, SF_READABLE); + } + return SF_ACCEPTING; + } + else + { + cs->OnError(Anope::string(gnutls_strerror(ret))); + cs->flags[SF_DEAD] = true; + cs->flags[SF_ACCEPTING] = false; + return SF_DEAD; + } + } + else + { + cs->flags[SF_ACCEPTED] = true; + cs->flags[SF_ACCEPTING] = false; + SocketEngine::Change(cs, false, SF_WRITABLE); + SocketEngine::Change(cs, true, SF_READABLE); + cs->OnAccept(); + return SF_ACCEPTED; + } +} + +void SSLSocketIO::Connect(ConnectionSocket *s, const Anope::string &target, int port) +{ + if (s->io == &NormalSocketIO) + throw SocketException("Attempting to connect uninitialized socket with SSL"); + + s->flags[SF_CONNECTING] = s->flags[SF_CONNECTED] = false; + + s->conaddr.pton(s->IsIPv6() ? AF_INET6 : AF_INET, target, port); + int c = connect(s->GetFD(), &s->conaddr.sa, s->conaddr.size()); + if (c == -1) + { + if (Anope::LastErrorCode() != EINPROGRESS) + { + s->OnError(Anope::LastError()); + s->flags[SF_DEAD] = true; + return; + } + else + { + SocketEngine::Change(s, true, SF_WRITABLE); + s->flags[SF_CONNECTING] = true; + return; + } + } + else + { + s->flags[SF_CONNECTING] = true; + this->FinishConnect(s); + } +} + +SocketFlag SSLSocketIO::FinishConnect(ConnectionSocket *s) +{ + if (s->io == &NormalSocketIO) + throw SocketException("Attempting to finish connect uninitialized socket with SSL"); + else if (s->flags[SF_CONNECTED]) + return SF_CONNECTED; + else if (!s->flags[SF_CONNECTING]) + throw SocketException("SSLSocketIO::FinishConnect called for a socket not connected nor connecting?"); + + SSLSocketIO *io = anope_dynamic_static_cast(s->io); + + if (io->sess == NULL) + { + if (gnutls_init(&io->sess, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS) + throw SocketException("Unable to initialize SSL socket"); + me->cred->SetupSession(io->sess); + gnutls_transport_set_ptr(io->sess, reinterpret_cast(s->GetFD())); + } + + int ret = gnutls_handshake(io->sess); + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) + { + // gnutls_handshake() wants to read or write again; + // if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write. + if (gnutls_record_get_direction(io->sess) == 0) + { + SocketEngine::Change(s, false, SF_WRITABLE); + SocketEngine::Change(s, true, SF_READABLE); + } + else + { + SocketEngine::Change(s, true, SF_WRITABLE); + SocketEngine::Change(s, false, SF_READABLE); + } + + return SF_CONNECTING; + } + else + { + s->OnError(Anope::string(gnutls_strerror(ret))); + s->flags[SF_CONNECTING] = false; + s->flags[SF_DEAD] = true; + return SF_DEAD; + } + } + else + { + s->flags[SF_CONNECTING] = false; + s->flags[SF_CONNECTED] = true; + SocketEngine::Change(s, false, SF_WRITABLE); + SocketEngine::Change(s, true, SF_READABLE); + s->OnConnect(); + return SF_CONNECTED; + } +} + +void SSLSocketIO::Destroy() +{ + if (this->sess) + { + gnutls_bye(this->sess, GNUTLS_SHUT_WR); + gnutls_deinit(this->sess); + } + + mycreds->decrref(); + + delete this; +} + +SSLSocketIO::SSLSocketIO() : sess(NULL), mycreds(me->cred) +{ + mycreds->incrref(); +} + +#if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12)) +int GnuTLS::X509CertCredentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr_st* st) +{ + st->type = GNUTLS_CRT_X509; +#else +int GnuTLS::X509CertCredentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr2_st* st) +{ + st->cert_type = GNUTLS_CRT_X509; + st->key_type = GNUTLS_PRIVKEY_X509; +#endif + st->ncerts = me->cred->certs.size(); + st->cert.x509 = me->cred->certs.raw(); + st->key.x509 = me->cred->key.get(); + st->deinit_all = 0; + + return 0; +} + +MODULE_INIT(GnuTLSModule) diff --git a/modules/extra/ssl_openssl.cpp b/modules/extra/ssl_openssl.cpp new file mode 100644 index 000000000..c5e2feefc --- /dev/null +++ b/modules/extra/ssl_openssl.cpp @@ -0,0 +1,416 @@ +/* RequiredLibraries: ssl,crypto */ +/* RequiredWindowsLibraries: ssleay32,libeay32 */ + +#include "module.h" +#include "modules/ssl.h" + +#define OPENSSL_NO_SHA512 +#include +#include +#include +#include +#include + +static SSL_CTX *server_ctx, *client_ctx; + +class MySSLService : public SSLService +{ + public: + MySSLService(Module *o, const Anope::string &n); + + /** Initialize a socket to use SSL + * @param s The socket + */ + void Init(Socket *s) override; +}; + +class SSLSocketIO : public SocketIO +{ + public: + /* The SSL socket for this socket */ + SSL *sslsock; + + /** Constructor + */ + SSLSocketIO(); + + /** Really receive something from the buffer + * @param s The socket + * @param buf The buf to read to + * @param sz How much to read + * @return Number of bytes received + */ + int Recv(Socket *s, char *buf, size_t sz) override; + + /** Write something to the socket + * @param s The socket + * @param buf The data to write + * @param size The length of the data + */ + int Send(Socket *s, const char *buf, size_t sz) override; + + /** Accept a connection from a socket + * @param s The socket + * @return The new socket + */ + ClientSocket *Accept(ListenSocket *s) override; + + /** Finished accepting a connection from a socket + * @param s The socket + * @return SF_ACCEPTED if accepted, SF_ACCEPTING if still in process, SF_DEAD on error + */ + SocketFlag FinishAccept(ClientSocket *cs) override; + + /** Connect the socket + * @param s THe socket + * @param target IP to connect to + * @param port to connect to + */ + void Connect(ConnectionSocket *s, const Anope::string &target, int port) override; + + /** Called to potentially finish a pending connection + * @param s The socket + * @return SF_CONNECTED on success, SF_CONNECTING if still pending, and SF_DEAD on error. + */ + SocketFlag FinishConnect(ConnectionSocket *s) override; + + /** Called when the socket is destructing + */ + void Destroy() override; +}; + +class SSLModule; +static SSLModule *me; +class SSLModule : public Module + , public EventHook +{ + Anope::string certfile, keyfile; + + public: + MySSLService service; + + SSLModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + , service(this, "ssl") + { + me = this; + + this->SetPermanent(true); + + SSL_library_init(); + SSL_load_error_strings(); + + client_ctx = SSL_CTX_new(SSLv23_client_method()); + server_ctx = SSL_CTX_new(SSLv23_server_method()); + + if (!client_ctx || !server_ctx) + throw ModuleException("Error initializing SSL CTX"); + + long opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_CIPHER_SERVER_PREFERENCE; + SSL_CTX_set_options(client_ctx, opts); + SSL_CTX_set_options(server_ctx, opts); + + SSL_CTX_set_mode(client_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_mode(server_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + Anope::string context_name = "Anope"; + SSL_CTX_set_session_id_context(client_ctx, reinterpret_cast(context_name.c_str()), context_name.length()); + SSL_CTX_set_session_id_context(server_ctx, reinterpret_cast(context_name.c_str()), context_name.length()); + } + + ~SSLModule() + { + for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) + { + Socket *s = it->second; + ++it; + + if (dynamic_cast(s->io)) + delete s; + } + + SSL_CTX_free(client_ctx); + SSL_CTX_free(server_ctx); + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + + this->certfile = config->Get("cert", "data/anope.crt"); + this->keyfile = config->Get("key", "data/anope.key"); + + if (Anope::IsFile(this->certfile.c_str())) + { + if (!SSL_CTX_use_certificate_file(client_ctx, this->certfile.c_str(), SSL_FILETYPE_PEM) || !SSL_CTX_use_certificate_file(server_ctx, this->certfile.c_str(), SSL_FILETYPE_PEM)) + throw ConfigException("Error loading certificate"); + else + Log(LOG_DEBUG) << "m_ssl_openssl: Successfully loaded certificate " << this->certfile; + } + else + Log() << "Unable to open certificate " << this->certfile; + + if (Anope::IsFile(this->keyfile.c_str())) + { + if (!SSL_CTX_use_PrivateKey_file(client_ctx, this->keyfile.c_str(), SSL_FILETYPE_PEM) || !SSL_CTX_use_PrivateKey_file(server_ctx, this->keyfile.c_str(), SSL_FILETYPE_PEM)) + throw ConfigException("Error loading private key"); + else + Log(LOG_DEBUG) << "m_ssl_openssl: Successfully loaded private key " << this->keyfile; + } + else + { + if (Anope::IsFile(this->certfile.c_str())) + throw ConfigException("Error loading private key " + this->keyfile + " - file not found"); + else + Log() << "Unable to open private key " << this->keyfile; + } + + // Allow disabling SSLv3 + if (!config->Get("sslv3").empty()) + { + if (config->Get("sslv3")) + { + SSL_CTX_clear_options(client_ctx, SSL_OP_NO_SSLv3); + SSL_CTX_clear_options(server_ctx, SSL_OP_NO_SSLv3); + } + else + { + SSL_CTX_set_options(client_ctx, SSL_OP_NO_SSLv3); + SSL_CTX_set_options(server_ctx, SSL_OP_NO_SSLv3); + } + } + } + + void OnPreServerConnect() override + { + Configuration::Block *config = Config->GetBlock("uplink", Anope::CurrentUplink); + + if (config->Get("ssl")) + { + this->service.Init(UplinkSock); + } + } +}; + +MySSLService::MySSLService(Module *o, const Anope::string &n) : SSLService(o, n) +{ +} + +void MySSLService::Init(Socket *s) +{ + if (s->io != &NormalSocketIO) + throw CoreException("Socket initializing SSL twice"); + + s->io = new SSLSocketIO(); +} + +SSLSocketIO::SSLSocketIO() +{ + this->sslsock = NULL; +} + +int SSLSocketIO::Recv(Socket *s, char *buf, size_t sz) +{ + int i = SSL_read(this->sslsock, buf, sz); + if (i > 0) + TotalRead += i; + else if (i < 0) + { + int err = SSL_get_error(this->sslsock, i); + switch (err) + { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + SocketEngine::SetLastError(EAGAIN); + } + } + + return i; +} + +int SSLSocketIO::Send(Socket *s, const char *buf, size_t sz) +{ + int i = SSL_write(this->sslsock, buf, sz); + if (i > 0) + TotalWritten += i; + else if (i < 0) + { + int err = SSL_get_error(this->sslsock, i); + switch (err) + { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + SocketEngine::SetLastError(EAGAIN); + } + } + return i; +} + +ClientSocket *SSLSocketIO::Accept(ListenSocket *s) +{ + if (s->io == &NormalSocketIO) + throw SocketException("Attempting to accept on uninitialized socket with SSL"); + + sockaddrs conaddr; + + socklen_t size = sizeof(conaddr); + int newsock = accept(s->GetFD(), &conaddr.sa, &size); + +#ifndef INVALID_SOCKET + const int INVALID_SOCKET = -1; +#endif + + if (newsock < 0 || newsock == INVALID_SOCKET) + throw SocketException("Unable to accept connection: " + Anope::LastError()); + + ClientSocket *newsocket = s->OnAccept(newsock, conaddr); + me->service.Init(newsocket); + SSLSocketIO *io = anope_dynamic_static_cast(newsocket->io); + + io->sslsock = SSL_new(server_ctx); + if (!io->sslsock) + throw SocketException("Unable to initialize SSL socket"); + + SSL_set_accept_state(io->sslsock); + + if (!SSL_set_fd(io->sslsock, newsocket->GetFD())) + throw SocketException("Unable to set SSL fd"); + + newsocket->flags[SF_ACCEPTING] = true; + this->FinishAccept(newsocket); + + return newsocket; +} + +SocketFlag SSLSocketIO::FinishAccept(ClientSocket *cs) +{ + if (cs->io == &NormalSocketIO) + throw SocketException("Attempting to finish connect uninitialized socket with SSL"); + else if (cs->flags[SF_ACCEPTED]) + return SF_ACCEPTED; + else if (!cs->flags[SF_ACCEPTING]) + throw SocketException("SSLSocketIO::FinishAccept called for a socket not accepted nor accepting?"); + + SSLSocketIO *io = anope_dynamic_static_cast(cs->io); + + int ret = SSL_accept(io->sslsock); + if (ret <= 0) + { + int error = SSL_get_error(io->sslsock, ret); + if (ret == -1 && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)) + { + SocketEngine::Change(cs, error == SSL_ERROR_WANT_WRITE, SF_WRITABLE); + SocketEngine::Change(cs, error == SSL_ERROR_WANT_READ, SF_READABLE); + return SF_ACCEPTING; + } + else + { + cs->OnError(ERR_error_string(ERR_get_error(), NULL)); + cs->flags[SF_DEAD] = true; + cs->flags[SF_ACCEPTING] = false; + return SF_DEAD; + } + } + else + { + cs->flags[SF_ACCEPTED] = true; + cs->flags[SF_ACCEPTING] = false; + SocketEngine::Change(cs, false, SF_WRITABLE); + SocketEngine::Change(cs, true, SF_READABLE); + cs->OnAccept(); + return SF_ACCEPTED; + } +} + +void SSLSocketIO::Connect(ConnectionSocket *s, const Anope::string &target, int port) +{ + if (s->io == &NormalSocketIO) + throw SocketException("Attempting to connect uninitialized socket with SSL"); + + s->flags[SF_CONNECTING] = s->flags[SF_CONNECTED] = false; + + s->conaddr.pton(s->IsIPv6() ? AF_INET6 : AF_INET, target, port); + int c = connect(s->GetFD(), &s->conaddr.sa, s->conaddr.size()); + if (c == -1) + { + if (Anope::LastErrorCode() != EINPROGRESS) + { + s->OnError(Anope::LastError()); + s->flags[SF_DEAD] = true; + return; + } + else + { + SocketEngine::Change(s, true, SF_WRITABLE); + s->flags[SF_CONNECTING] = true; + return; + } + } + else + { + s->flags[SF_CONNECTING] = true; + this->FinishConnect(s); + } +} + +SocketFlag SSLSocketIO::FinishConnect(ConnectionSocket *s) +{ + if (s->io == &NormalSocketIO) + throw SocketException("Attempting to finish connect uninitialized socket with SSL"); + else if (s->flags[SF_CONNECTED]) + return SF_CONNECTED; + else if (!s->flags[SF_CONNECTING]) + throw SocketException("SSLSocketIO::FinishConnect called for a socket not connected nor connecting?"); + + SSLSocketIO *io = anope_dynamic_static_cast(s->io); + + if (io->sslsock == NULL) + { + io->sslsock = SSL_new(client_ctx); + if (!io->sslsock) + throw SocketException("Unable to initialize SSL socket"); + + if (!SSL_set_fd(io->sslsock, s->GetFD())) + throw SocketException("Unable to set SSL fd"); + } + + int ret = SSL_connect(io->sslsock); + if (ret <= 0) + { + int error = SSL_get_error(io->sslsock, ret); + if (ret == -1 && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)) + { + SocketEngine::Change(s, error == SSL_ERROR_WANT_WRITE, SF_WRITABLE); + SocketEngine::Change(s, error == SSL_ERROR_WANT_READ, SF_READABLE); + return SF_CONNECTING; + } + else + { + s->OnError(ERR_error_string(ERR_get_error(), NULL)); + s->flags[SF_CONNECTING] = false; + s->flags[SF_DEAD] = true; + return SF_DEAD; + } + } + else + { + s->flags[SF_CONNECTING] = false; + s->flags[SF_CONNECTED] = true; + SocketEngine::Change(s, false, SF_WRITABLE); + SocketEngine::Change(s, true, SF_READABLE); + s->OnConnect(); + return SF_CONNECTED; + } +} + +void SSLSocketIO::Destroy() +{ + if (this->sslsock) + { + SSL_shutdown(this->sslsock); + SSL_free(this->sslsock); + } + + delete this; +} + +MODULE_INIT(SSLModule) diff --git a/modules/extra/stats/chanstats.cpp b/modules/extra/stats/chanstats.cpp new file mode 100644 index 000000000..687c077b5 --- /dev/null +++ b/modules/extra/stats/chanstats.cpp @@ -0,0 +1,648 @@ +#include "module.h" +#include "modules/sql.h" + +class CommandCSSetChanstats : public Command +{ + public: + CommandCSSetChanstats(Module *creator) : Command(creator, "chanserv/set/chanstats", 2, 2) + { + this->SetDesc(_("Turn chanstats statistics on or off")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci = ChanServ::Find(params[0]); + if (!ci) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, params[1]); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params[1].equals_ci("ON")) + { + ci->Extend("CS_STATS"); + source.Reply(_("Chanstats statistics are now enabled for this channel.")); + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable chanstats"; + } + else if (params[1].equals_ci("OFF")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable chanstats"; + ci->Shrink("CS_STATS"); + source.Reply(_("Chanstats statistics are now disabled for this channel.")); + } + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply("Turn Chanstats channel statistics ON or OFF."); + return true; + } +}; + +class CommandNSSetChanstats : public Command +{ + public: + CommandNSSetChanstats(Module *creator, const Anope::string &sname = "nickserv/set/chanstats", size_t min = 1 ) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Turn chanstats statistics on or off")); + this->SetSyntax("{ON | OFF}"); + } + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m, bool saset = false) + { + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, na->GetAccount(), param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + Log(na->GetAccount() == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable chanstats for " << na->GetAccount()->GetDisplay(); + na->GetAccount()->Extend("NS_STATS"); + if (saset) + source.Reply(_("Chanstats statistics are now enabled for %s"), na->GetAccount()->GetDisplay().c_str()); + else + source.Reply(_("Chanstats statistics are now enabled for your nick.")); + } + else if (param.equals_ci("OFF")) + { + Log(na->GetAccount() == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable chanstats for " << na->GetAccount()->GetDisplay(); + na->GetAccount()->Shrink("NS_STATS"); + if (saset) + source.Reply(_("Chanstats statistics are now disabled for %s"), na->GetAccount()->GetDisplay().c_str()); + else + source.Reply(_("Chanstats statistics are now disabled for your nick.")); + } + else + this->OnSyntaxError(source, "CHANSTATS"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Turns Chanstats statistics ON or OFF.")); + return true; + } +}; + +class CommandNSSASetChanstats : public CommandNSSetChanstats +{ + public: + CommandNSSASetChanstats(Module *creator) : CommandNSSetChanstats(creator, "nickserv/saset/chanstats", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, params[0], params[1], true); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Turns chanstats channel statistics ON or OFF for this user.")); + return true; + } +}; + +class MySQLInterface : public SQL::Interface +{ + public: + MySQLInterface(Module *o) : SQL::Interface(o) { } + + void OnResult(const SQL::Result &r) override + { + } + + void OnError(const SQL::Result &r) override + { + if (!r.GetQuery().query.empty()) + Log(LOG_DEBUG) << "Chanstats: Error executing query " << r.finished_query << ": " << r.GetError(); + else + Log(LOG_DEBUG) << "Chanstats: Error executing query: " << r.GetError(); + } +}; + +class MChanstats : public Module +{ + Serialize::Field cs_stats, ns_stats; + + CommandCSSetChanstats commandcssetchanstats; + + CommandNSSetChanstats commandnssetchanstats; + CommandNSSASetChanstats commandnssasetchanstats; + + ServiceReference sql; + MySQLInterface sqlinterface; + SQL::Query query; + Anope::string SmileysHappy, SmileysSad, SmileysOther, prefix; + std::vector TableList, ProcedureList, EventList; + bool NSDefChanstats, CSDefChanstats; + + void RunQuery(const SQL::Query &q) + { + if (sql) + sql->Run(&sqlinterface, q); + } + + size_t CountWords(const Anope::string &msg) + { + size_t words = 0; + for (size_t pos = 0; pos != Anope::string::npos; pos = msg.find(" ", pos+1)) + words++; + return words; + } + size_t CountSmileys(const Anope::string &msg, const Anope::string &smileylist) + { + size_t smileys = 0; + spacesepstream sep(smileylist); + Anope::string buf; + + while (sep.GetToken(buf) && !buf.empty()) + { + for (size_t pos = msg.find(buf, 0); pos != Anope::string::npos; pos = msg.find(buf, pos+1)) + smileys++; + } + return smileys; + } + + const Anope::string GetDisplay(User *u) + { + if (u && u->Account() && ns_stats.HasExt(u->Account())) + return u->Account()->GetDisplay(); + else + return ""; + } + + void GetTables() + { + TableList.clear(); + ProcedureList.clear(); + EventList.clear(); + if (!sql) + return; + + SQL::Result r = this->sql->RunQuery(this->sql->GetTables(prefix)); + for (int i = 0; i < r.Rows(); ++i) + { + const std::map &map = r.Row(i); + for (std::map::const_iterator it = map.begin(); it != map.end(); ++it) + TableList.push_back(it->second); + } + query = "SHOW PROCEDURE STATUS WHERE `Db` = Database();"; + r = this->sql->RunQuery(query); + for (int i = 0; i < r.Rows(); ++i) + { + ProcedureList.push_back(r.Get(i, "Name")); + } + query = "SHOW EVENTS WHERE `Db` = Database();"; + r = this->sql->RunQuery(query); + for (int i = 0; i < r.Rows(); ++i) + { + EventList.push_back(r.Get(i, "Name")); + } + } + + bool HasTable(const Anope::string &table) + { + for (std::vector::const_iterator it = TableList.begin(); it != TableList.end(); ++it) + if (*it == table) + return true; + return false; + } + + bool HasProcedure(const Anope::string &table) + { + for (std::vector::const_iterator it = ProcedureList.begin(); it != ProcedureList.end(); ++it) + if (*it == table) + return true; + return false; + } + + bool HasEvent(const Anope::string &table) + { + for (std::vector::const_iterator it = EventList.begin(); it != EventList.end(); ++it) + if (*it == table) + return true; + return false; + } + + + void CheckTables() + { + this->GetTables(); + if (!this->HasTable(prefix +"chanstats")) + { + query = "CREATE TABLE `" + prefix + "chanstats` (" + "`id` int(11) NOT NULL AUTO_INCREMENT," + "`chan` varchar(64) NOT NULL DEFAULT ''," + "`nick` varchar(64) NOT NULL DEFAULT ''," + "`type` ENUM('total', 'monthly', 'weekly', 'daily') NOT NULL," + "`letters` int(10) unsigned NOT NULL DEFAULT '0'," + "`words` int(10) unsigned NOT NULL DEFAULT '0'," + "`line` int(10) unsigned NOT NULL DEFAULT '0'," + "`actions` int(10) unsigned NOT NULL DEFAULT '0'," + "`smileys_happy` int(10) unsigned NOT NULL DEFAULT '0'," + "`smileys_sad` int(10) unsigned NOT NULL DEFAULT '0'," + "`smileys_other` int(10) unsigned NOT NULL DEFAULT '0'," + "`kicks` int(10) unsigned NOT NULL DEFAULT '0'," + "`kicked` int(10) unsigned NOT NULL DEFAULT '0'," + "`modes` int(10) unsigned NOT NULL DEFAULT '0'," + "`topics` int(10) unsigned NOT NULL DEFAULT '0'," + "`time0` int(10) unsigned NOT NULL default '0'," + "`time1` int(10) unsigned NOT NULL default '0'," + "`time2` int(10) unsigned NOT NULL default '0'," + "`time3` int(10) unsigned NOT NULL default '0'," + "`time4` int(10) unsigned NOT NULL default '0'," + "`time5` int(10) unsigned NOT NULL default '0'," + "`time6` int(10) unsigned NOT NULL default '0'," + "`time7` int(10) unsigned NOT NULL default '0'," + "`time8` int(10) unsigned NOT NULL default '0'," + "`time9` int(10) unsigned NOT NULL default '0'," + "`time10` int(10) unsigned NOT NULL default '0'," + "`time11` int(10) unsigned NOT NULL default '0'," + "`time12` int(10) unsigned NOT NULL default '0'," + "`time13` int(10) unsigned NOT NULL default '0'," + "`time14` int(10) unsigned NOT NULL default '0'," + "`time15` int(10) unsigned NOT NULL default '0'," + "`time16` int(10) unsigned NOT NULL default '0'," + "`time17` int(10) unsigned NOT NULL default '0'," + "`time18` int(10) unsigned NOT NULL default '0'," + "`time19` int(10) unsigned NOT NULL default '0'," + "`time20` int(10) unsigned NOT NULL default '0'," + "`time21` int(10) unsigned NOT NULL default '0'," + "`time22` int(10) unsigned NOT NULL default '0'," + "`time23` int(10) unsigned NOT NULL default '0'," + "PRIMARY KEY (`id`)," + "UNIQUE KEY `chan` (`chan`,`nick`,`type`)," + "KEY `nick` (`nick`)," + "KEY `chan_` (`chan`)," + "KEY `type` (`type`)" + ") ENGINE=MyISAM DEFAULT CHARSET=utf8;"; + this->RunQuery(query); + } + /* There is no CREATE OR REPLACE PROCEDURE in MySQL */ + if (this->HasProcedure(prefix + "chanstats_proc_update")) + { + query = "DROP PROCEDURE " + prefix + "chanstats_proc_update"; + this->RunQuery(query); + } + query = "CREATE PROCEDURE `" + prefix + "chanstats_proc_update`" + "(chan_ VARCHAR(255), nick_ VARCHAR(255), line_ INT(10), letters_ INT(10)," + "words_ INT(10), actions_ INT(10), sm_h_ INT(10), sm_s_ INT(10), sm_o_ INT(10)," + "kicks_ INT(10), kicked_ INT(10), modes_ INT(10), topics_ INT(10))" + "BEGIN " + "DECLARE time_ VARCHAR(20);" + "SET time_ = CONCAT('time', hour(now()));" + "INSERT IGNORE INTO `" + prefix + "chanstats` (`nick`,`chan`, `type`) VALUES " + "('', chan_, 'total'), ('', chan_, 'monthly')," + "('', chan_, 'weekly'), ('', chan_, 'daily');" + "IF nick_ != '' THEN " + "INSERT IGNORE INTO `" + prefix + "chanstats` (`nick`,`chan`, `type`) VALUES " + "(nick_, chan_, 'total'), (nick_, chan_, 'monthly')," + "(nick_, chan_, 'weekly'),(nick_, chan_, 'daily')," + "(nick_, '', 'total'), (nick_, '', 'monthly')," + "(nick_, '', 'weekly'), (nick_, '', 'daily');" + "END IF;" + "SET @update_query = CONCAT('UPDATE `" + prefix + "chanstats` SET line=line+', line_, '," + "letters=letters+', letters_, ' , words=words+', words_, ', actions=actions+', actions_, ', " + "smileys_happy=smileys_happy+', sm_h_, ', smileys_sad=smileys_sad+', sm_s_, ', " + "smileys_other=smileys_other+', sm_o_, ', kicks=kicks+', kicks_, ', kicked=kicked+', kicked_, ', " + "modes=modes+', modes_, ', topics=topics+', topics_, ', ', time_ , '=', time_, '+', line_ ,' " + "WHERE (nick='''' OR nick=''', nick_, ''') AND (chan='''' OR chan=''', chan_, ''')');" + "PREPARE update_query FROM @update_query;" + "EXECUTE update_query;" + "DEALLOCATE PREPARE update_query;" + "END"; + this->RunQuery(query); + + if (this->HasProcedure(prefix + "chanstats_proc_chgdisplay")) + { + query = "DROP PROCEDURE " + prefix + "chanstats_proc_chgdisplay;"; + this->RunQuery(query); + } + query = "CREATE PROCEDURE `" + prefix + "chanstats_proc_chgdisplay`" + "(old_nick varchar(255), new_nick varchar(255))" + "BEGIN " + "DECLARE res_count int(10) unsigned;" + "SELECT COUNT(nick) INTO res_count FROM `" + prefix + "chanstats` WHERE nick = new_nick;" + "IF res_count = 0 THEN " + "UPDATE `" + prefix + "chanstats` SET `nick` = new_nick WHERE `nick` = old_nick;" + "ELSE " + "my_cursor: BEGIN " + "DECLARE no_more_rows BOOLEAN DEFAULT FALSE;" + "DECLARE chan_ VARCHAR(255);" + "DECLARE type_ ENUM('total', 'monthly', 'weekly', 'daily');" + "DECLARE letters_, words_, line_, actions_, smileys_happy_," + "smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_," + "time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_, time9_," + "time10_, time11_, time12_, time13_, time14_, time15_, time16_, time17_, time18_," + "time19_, time20_, time21_, time22_, time23_ INT(10) unsigned;" + "DECLARE stats_cursor CURSOR FOR " + "SELECT chan, type, letters, words, line, actions, smileys_happy," + "smileys_sad, smileys_other, kicks, kicked, modes, topics, time0, time1," + "time2, time3, time4, time5, time6, time7, time8, time9, time10, time11," + "time12, time13, time14, time15, time16, time17, time18, time19, time20," + "time21, time22, time23 " + "FROM `" + prefix + "chanstats` " + "WHERE `nick` = old_nick;" + "DECLARE CONTINUE HANDLER FOR NOT FOUND " + "SET no_more_rows = TRUE;" + "OPEN stats_cursor;" + "the_loop: LOOP " + "FETCH stats_cursor " + "INTO chan_, type_, letters_, words_, line_, actions_, smileys_happy_," + "smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_," + "time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_," + "time9_, time10_, time11_, time12_, time13_, time14_, time15_, time16_," + "time17_, time18_, time19_, time20_, time21_, time22_, time23_;" + "IF no_more_rows THEN " + "CLOSE stats_cursor;" + "LEAVE the_loop;" + "END IF;" + "INSERT INTO `" + prefix + "chanstats` " + "(chan, nick, type, letters, words, line, actions, smileys_happy, " + "smileys_sad, smileys_other, kicks, kicked, modes, topics, time0, time1, " + "time2, time3, time4, time5, time6, time7, time8, time9, time10, time11," + "time12, time13, time14, time15, time16, time17, time18, time19, time20," + "time21, time22, time23)" + "VALUES (chan_, new_nick, type_, letters_, words_, line_, actions_, smileys_happy_," + "smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_," + "time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_, " + "time9_, time10_, time11_, time12_, time13_, time14_, time15_, time16_, " + "time17_, time18_, time19_, time20_, time21_, time22_, time23_)" + "ON DUPLICATE KEY UPDATE letters=letters+VALUES(letters), words=words+VALUES(words)," + "line=line+VALUES(line), actions=actions+VALUES(actions)," + "smileys_happy=smileys_happy+VALUES(smileys_happy)," + "smileys_sad=smileys_sad+VALUES(smileys_sad)," + "smileys_other=smileys_other+VALUES(smileys_other)," + "kicks=kicks+VALUES(kicks), kicked=kicked+VALUES(kicked)," + "modes=modes+VALUES(modes), topics=topics+VALUES(topics)," + "time1=time1+VALUES(time1), time2=time2+VALUES(time2), time3=time3+VALUES(time3)," + "time4=time4+VALUES(time4), time5=time5+VALUES(time5), time6=time6+VALUES(time6)," + "time7=time7+VALUES(time7), time8=time8+VALUES(time8), time9=time9+VALUES(time9)," + "time10=time10+VALUES(time10), time11=time11+VALUES(time11), time12=time12+VALUES(time12)," + "time13=time13+VALUES(time13), time14=time14+VALUES(time14), time15=time15+VALUES(time15)," + "time16=time16+VALUES(time16), time17=time17+VALUES(time17), time18=time18+VALUES(time18)," + "time19=time19+VALUES(time19), time20=time20+VALUES(time20), time21=time21+VALUES(time21)," + "time22=time22+VALUES(time22), time23=time23+VALUES(time23);" + "END LOOP;" + "DELETE FROM `" + prefix + "chanstats` WHERE `nick` = old_nick;" + "END my_cursor;" + "END IF;" + "END;"; + this->RunQuery(query); + + /* don't prepend any database prefix to events so we can always delete/change old events */ + if (this->HasEvent("chanstats_event_cleanup_daily")) + { + query = "DROP EVENT chanstats_event_cleanup_daily"; + this->RunQuery(query); + } + query = "CREATE EVENT `chanstats_event_cleanup_daily` " + "ON SCHEDULE EVERY 1 DAY STARTS CURRENT_DATE " + "DO UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0," + "smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0," + "time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0," + "time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0," + "time20=0, time21=0, time22=0, time23=0 " + "WHERE type='daily';"; + this->RunQuery(query); + + if (this->HasEvent("chanstats_event_cleanup_weekly")) + { + query = "DROP EVENT `chanstats_event_cleanup_weekly`"; + this->RunQuery(query); + } + query = "CREATE EVENT `chanstats_event_cleanup_weekly` " + "ON SCHEDULE EVERY 1 WEEK STARTS ADDDATE(CURDATE(), INTERVAL 1-DAYOFWEEK(CURDATE()) DAY) " + "DO UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0," + "smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0," + "time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0," + "time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0," + "time20=0, time21=0, time22=0, time23=0 " + "WHERE type='weekly';"; + this->RunQuery(query); + + if (this->HasEvent("chanstats_event_cleanup_monthly")) + { + query = "DROP EVENT `chanstats_event_cleanup_monthly`;"; + this->RunQuery(query); + } + query = "CREATE EVENT `chanstats_event_cleanup_monthly` " + "ON SCHEDULE EVERY 1 MONTH STARTS LAST_DAY(CURRENT_TIMESTAMP) + INTERVAL 1 DAY " + "DO BEGIN " + "UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0," + "smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0," + "time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0," + "time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0, " + "time20=0, time21=0, time22=0, time23=0 " + "WHERE type='monthly';" + "OPTIMIZE TABLE `" + prefix + "chanstats`;" + "END;"; + this->RunQuery(query); + } + + + public: + MChanstats(const Anope::string &modname, const Anope::string &creator) : + Module(modname, creator, EXTRA | VENDOR), + cs_stats(this, "CS_STATS"), ns_stats(this, "NS_STATS"), + commandcssetchanstats(this), commandnssetchanstats(this), commandnssasetchanstats(this), + sqlinterface(this) + { + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + prefix = block->Get("prefix", "anope_"); + SmileysHappy = block->Get("SmileysHappy"); + SmileysSad = block->Get("SmileysSad"); + SmileysOther = block->Get("SmileysOther"); + NSDefChanstats = block->Get("ns_def_chanstats"); + CSDefChanstats = block->Get("cs_def_chanstats"); + Anope::string engine = block->Get("engine"); + this->sql = ServiceReference("SQL::Provider", engine); + if (sql) + this->CheckTables(); + else + Log(this) << "no database connection to " << engine; + } + + void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_all) override + { + if (!show_all) + return; + if (cs_stats.HasExt(ci)) + info.AddOption(_("Chanstats")); + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override + { + if (!show_hidden) + return; + if (ns_stats.HasExt(na->GetAccount())) + info.AddOption(_("Chanstats")); + } + + void OnTopicUpdated(User *source, Channel *c, const Anope::string &user, const Anope::string &topic) override + { + if (!source || !source->Account() || !c->ci || !cs_stats.HasExt(c->ci)) + return; + query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);"; + query.SetValue("channel", c->name); + query.SetValue("nick", GetDisplay(source)); + this->RunQuery(query); + } + + EventReturn OnChannelModeSet(Channel *c, const MessageSource &setter, ChannelMode *mode, const Anope::string ¶m) override + { + this->OnModeChange(c, setter.GetUser()); + return EVENT_CONTINUE; + } + + EventReturn OnChannelModeUnset(Channel *c, const MessageSource &setter, ChannelMode *, const Anope::string ¶m) override + { + this->OnModeChange(c, setter.GetUser()); + return EVENT_CONTINUE; + } + + private: + void OnModeChange(Channel *c, User *u) + { + if (!u || !u->Account() || !c->ci || !cs_stats.HasExt(c->ci)) + return; + + query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);"; + query.SetValue("channel", c->name); + query.SetValue("nick", GetDisplay(u)); + this->RunQuery(query); + } + + public: + void OnPreUserKicked(const MessageSource &source, ChanUserContainer *cu, const Anope::string &kickmsg) override + { + if (!cu->chan->ci || !cs_stats.HasExt(cu->chan->ci)) + return; + + query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0);"; + query.SetValue("channel", cu->chan->name); + query.SetValue("nick", GetDisplay(cu->user)); + this->RunQuery(query); + + query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0);"; + query.SetValue("channel", cu->chan->name); + query.SetValue("nick", GetDisplay(source.GetUser())); + this->RunQuery(query); + } + + void OnPrivmsg(User *u, Channel *c, Anope::string &msg) override + { + if (!c->ci || !cs_stats.HasExt(c->ci)) + return; + + size_t letters = msg.length(); + size_t words = this->CountWords(msg); + + size_t action = 0; + if (msg.find("\01ACTION")!=Anope::string::npos) + { + action = 1; + letters = letters - 7; + words--; + } + + // count smileys + size_t smileys_happy = CountSmileys(msg, SmileysHappy); + size_t smileys_sad = CountSmileys(msg, SmileysSad); + size_t smileys_other = CountSmileys(msg, SmileysOther); + + // do not count smileys as words + size_t smileys = smileys_happy + smileys_sad + smileys_other; + if (smileys > words) + words = 0; + else + words = words - smileys; + + query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 1, @letters@, @words@, @action@, " + "@smileys_happy@, @smileys_sad@, @smileys_other@, '0', '0', '0', '0');"; + query.SetValue("channel", c->name); + query.SetValue("nick", GetDisplay(u)); + query.SetValue("letters", letters); + query.SetValue("words", words); + query.SetValue("action", action); + query.SetValue("smileys_happy", smileys_happy); + query.SetValue("smileys_sad", smileys_sad); + query.SetValue("smileys_other", smileys_other); + this->RunQuery(query); + } + + void OnDelCore(NickServ::Account *nc) override + { + query = "DELETE FROM `" + prefix + "chanstats` WHERE `nick` = @nick@;"; + query.SetValue("nick", nc->GetDisplay()); + this->RunQuery(query); + } + + void OnChangeCoreDisplay(NickServ::Account *nc, const Anope::string &newdisplay) override + { + query = "CALL " + prefix + "chanstats_proc_chgdisplay(@old_display@, @new_display@);"; + query.SetValue("old_display", nc->GetDisplay()); + query.SetValue("new_display", newdisplay); + this->RunQuery(query); + } + + void OnDelChan(ChanServ::Channel *ci) override + { + query = "DELETE FROM `" + prefix + "chanstats` WHERE `chan` = @channel@;"; + query.SetValue("channel", ci->GetName()); + this->RunQuery(query); + } + + void OnChanRegistered(ChanServ::Channel *ci) + { + if (CSDefChanstats) + ci->Extend("CS_STATS"); + } + + void OnNickRegister(User *user, NickAlias *na, const Anope::string &) + { + if (NSDefChanstats) + na->GetAccount()->Extend("NS_STATS"); + } +}; + +MODULE_INIT(MChanstats) + diff --git a/modules/extra/stats/m_chanstats.cpp b/modules/extra/stats/m_chanstats.cpp deleted file mode 100644 index 687c077b5..000000000 --- a/modules/extra/stats/m_chanstats.cpp +++ /dev/null @@ -1,648 +0,0 @@ -#include "module.h" -#include "modules/sql.h" - -class CommandCSSetChanstats : public Command -{ - public: - CommandCSSetChanstats(Module *creator) : Command(creator, "chanserv/set/chanstats", 2, 2) - { - this->SetDesc(_("Turn chanstats statistics on or off")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci = ChanServ::Find(params[0]); - if (!ci) - { - source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, params[1]); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(ACCESS_DENIED); - return; - } - - if (params[1].equals_ci("ON")) - { - ci->Extend("CS_STATS"); - source.Reply(_("Chanstats statistics are now enabled for this channel.")); - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable chanstats"; - } - else if (params[1].equals_ci("OFF")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable chanstats"; - ci->Shrink("CS_STATS"); - source.Reply(_("Chanstats statistics are now disabled for this channel.")); - } - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply("Turn Chanstats channel statistics ON or OFF."); - return true; - } -}; - -class CommandNSSetChanstats : public Command -{ - public: - CommandNSSetChanstats(Module *creator, const Anope::string &sname = "nickserv/set/chanstats", size_t min = 1 ) : Command(creator, sname, min, min + 1) - { - this->SetDesc(_("Turn chanstats statistics on or off")); - this->SetSyntax("{ON | OFF}"); - } - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m, bool saset = false) - { - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - { - source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, na->GetAccount(), param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (param.equals_ci("ON")) - { - Log(na->GetAccount() == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable chanstats for " << na->GetAccount()->GetDisplay(); - na->GetAccount()->Extend("NS_STATS"); - if (saset) - source.Reply(_("Chanstats statistics are now enabled for %s"), na->GetAccount()->GetDisplay().c_str()); - else - source.Reply(_("Chanstats statistics are now enabled for your nick.")); - } - else if (param.equals_ci("OFF")) - { - Log(na->GetAccount() == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable chanstats for " << na->GetAccount()->GetDisplay(); - na->GetAccount()->Shrink("NS_STATS"); - if (saset) - source.Reply(_("Chanstats statistics are now disabled for %s"), na->GetAccount()->GetDisplay().c_str()); - else - source.Reply(_("Chanstats statistics are now disabled for your nick.")); - } - else - this->OnSyntaxError(source, "CHANSTATS"); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, source.nc->GetDisplay(), params[0]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Turns Chanstats statistics ON or OFF.")); - return true; - } -}; - -class CommandNSSASetChanstats : public CommandNSSetChanstats -{ - public: - CommandNSSASetChanstats(Module *creator) : CommandNSSetChanstats(creator, "nickserv/saset/chanstats", 2) - { - this->ClearSyntax(); - this->SetSyntax(_("\037nickname\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, params[0], params[1], true); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Turns chanstats channel statistics ON or OFF for this user.")); - return true; - } -}; - -class MySQLInterface : public SQL::Interface -{ - public: - MySQLInterface(Module *o) : SQL::Interface(o) { } - - void OnResult(const SQL::Result &r) override - { - } - - void OnError(const SQL::Result &r) override - { - if (!r.GetQuery().query.empty()) - Log(LOG_DEBUG) << "Chanstats: Error executing query " << r.finished_query << ": " << r.GetError(); - else - Log(LOG_DEBUG) << "Chanstats: Error executing query: " << r.GetError(); - } -}; - -class MChanstats : public Module -{ - Serialize::Field cs_stats, ns_stats; - - CommandCSSetChanstats commandcssetchanstats; - - CommandNSSetChanstats commandnssetchanstats; - CommandNSSASetChanstats commandnssasetchanstats; - - ServiceReference sql; - MySQLInterface sqlinterface; - SQL::Query query; - Anope::string SmileysHappy, SmileysSad, SmileysOther, prefix; - std::vector TableList, ProcedureList, EventList; - bool NSDefChanstats, CSDefChanstats; - - void RunQuery(const SQL::Query &q) - { - if (sql) - sql->Run(&sqlinterface, q); - } - - size_t CountWords(const Anope::string &msg) - { - size_t words = 0; - for (size_t pos = 0; pos != Anope::string::npos; pos = msg.find(" ", pos+1)) - words++; - return words; - } - size_t CountSmileys(const Anope::string &msg, const Anope::string &smileylist) - { - size_t smileys = 0; - spacesepstream sep(smileylist); - Anope::string buf; - - while (sep.GetToken(buf) && !buf.empty()) - { - for (size_t pos = msg.find(buf, 0); pos != Anope::string::npos; pos = msg.find(buf, pos+1)) - smileys++; - } - return smileys; - } - - const Anope::string GetDisplay(User *u) - { - if (u && u->Account() && ns_stats.HasExt(u->Account())) - return u->Account()->GetDisplay(); - else - return ""; - } - - void GetTables() - { - TableList.clear(); - ProcedureList.clear(); - EventList.clear(); - if (!sql) - return; - - SQL::Result r = this->sql->RunQuery(this->sql->GetTables(prefix)); - for (int i = 0; i < r.Rows(); ++i) - { - const std::map &map = r.Row(i); - for (std::map::const_iterator it = map.begin(); it != map.end(); ++it) - TableList.push_back(it->second); - } - query = "SHOW PROCEDURE STATUS WHERE `Db` = Database();"; - r = this->sql->RunQuery(query); - for (int i = 0; i < r.Rows(); ++i) - { - ProcedureList.push_back(r.Get(i, "Name")); - } - query = "SHOW EVENTS WHERE `Db` = Database();"; - r = this->sql->RunQuery(query); - for (int i = 0; i < r.Rows(); ++i) - { - EventList.push_back(r.Get(i, "Name")); - } - } - - bool HasTable(const Anope::string &table) - { - for (std::vector::const_iterator it = TableList.begin(); it != TableList.end(); ++it) - if (*it == table) - return true; - return false; - } - - bool HasProcedure(const Anope::string &table) - { - for (std::vector::const_iterator it = ProcedureList.begin(); it != ProcedureList.end(); ++it) - if (*it == table) - return true; - return false; - } - - bool HasEvent(const Anope::string &table) - { - for (std::vector::const_iterator it = EventList.begin(); it != EventList.end(); ++it) - if (*it == table) - return true; - return false; - } - - - void CheckTables() - { - this->GetTables(); - if (!this->HasTable(prefix +"chanstats")) - { - query = "CREATE TABLE `" + prefix + "chanstats` (" - "`id` int(11) NOT NULL AUTO_INCREMENT," - "`chan` varchar(64) NOT NULL DEFAULT ''," - "`nick` varchar(64) NOT NULL DEFAULT ''," - "`type` ENUM('total', 'monthly', 'weekly', 'daily') NOT NULL," - "`letters` int(10) unsigned NOT NULL DEFAULT '0'," - "`words` int(10) unsigned NOT NULL DEFAULT '0'," - "`line` int(10) unsigned NOT NULL DEFAULT '0'," - "`actions` int(10) unsigned NOT NULL DEFAULT '0'," - "`smileys_happy` int(10) unsigned NOT NULL DEFAULT '0'," - "`smileys_sad` int(10) unsigned NOT NULL DEFAULT '0'," - "`smileys_other` int(10) unsigned NOT NULL DEFAULT '0'," - "`kicks` int(10) unsigned NOT NULL DEFAULT '0'," - "`kicked` int(10) unsigned NOT NULL DEFAULT '0'," - "`modes` int(10) unsigned NOT NULL DEFAULT '0'," - "`topics` int(10) unsigned NOT NULL DEFAULT '0'," - "`time0` int(10) unsigned NOT NULL default '0'," - "`time1` int(10) unsigned NOT NULL default '0'," - "`time2` int(10) unsigned NOT NULL default '0'," - "`time3` int(10) unsigned NOT NULL default '0'," - "`time4` int(10) unsigned NOT NULL default '0'," - "`time5` int(10) unsigned NOT NULL default '0'," - "`time6` int(10) unsigned NOT NULL default '0'," - "`time7` int(10) unsigned NOT NULL default '0'," - "`time8` int(10) unsigned NOT NULL default '0'," - "`time9` int(10) unsigned NOT NULL default '0'," - "`time10` int(10) unsigned NOT NULL default '0'," - "`time11` int(10) unsigned NOT NULL default '0'," - "`time12` int(10) unsigned NOT NULL default '0'," - "`time13` int(10) unsigned NOT NULL default '0'," - "`time14` int(10) unsigned NOT NULL default '0'," - "`time15` int(10) unsigned NOT NULL default '0'," - "`time16` int(10) unsigned NOT NULL default '0'," - "`time17` int(10) unsigned NOT NULL default '0'," - "`time18` int(10) unsigned NOT NULL default '0'," - "`time19` int(10) unsigned NOT NULL default '0'," - "`time20` int(10) unsigned NOT NULL default '0'," - "`time21` int(10) unsigned NOT NULL default '0'," - "`time22` int(10) unsigned NOT NULL default '0'," - "`time23` int(10) unsigned NOT NULL default '0'," - "PRIMARY KEY (`id`)," - "UNIQUE KEY `chan` (`chan`,`nick`,`type`)," - "KEY `nick` (`nick`)," - "KEY `chan_` (`chan`)," - "KEY `type` (`type`)" - ") ENGINE=MyISAM DEFAULT CHARSET=utf8;"; - this->RunQuery(query); - } - /* There is no CREATE OR REPLACE PROCEDURE in MySQL */ - if (this->HasProcedure(prefix + "chanstats_proc_update")) - { - query = "DROP PROCEDURE " + prefix + "chanstats_proc_update"; - this->RunQuery(query); - } - query = "CREATE PROCEDURE `" + prefix + "chanstats_proc_update`" - "(chan_ VARCHAR(255), nick_ VARCHAR(255), line_ INT(10), letters_ INT(10)," - "words_ INT(10), actions_ INT(10), sm_h_ INT(10), sm_s_ INT(10), sm_o_ INT(10)," - "kicks_ INT(10), kicked_ INT(10), modes_ INT(10), topics_ INT(10))" - "BEGIN " - "DECLARE time_ VARCHAR(20);" - "SET time_ = CONCAT('time', hour(now()));" - "INSERT IGNORE INTO `" + prefix + "chanstats` (`nick`,`chan`, `type`) VALUES " - "('', chan_, 'total'), ('', chan_, 'monthly')," - "('', chan_, 'weekly'), ('', chan_, 'daily');" - "IF nick_ != '' THEN " - "INSERT IGNORE INTO `" + prefix + "chanstats` (`nick`,`chan`, `type`) VALUES " - "(nick_, chan_, 'total'), (nick_, chan_, 'monthly')," - "(nick_, chan_, 'weekly'),(nick_, chan_, 'daily')," - "(nick_, '', 'total'), (nick_, '', 'monthly')," - "(nick_, '', 'weekly'), (nick_, '', 'daily');" - "END IF;" - "SET @update_query = CONCAT('UPDATE `" + prefix + "chanstats` SET line=line+', line_, '," - "letters=letters+', letters_, ' , words=words+', words_, ', actions=actions+', actions_, ', " - "smileys_happy=smileys_happy+', sm_h_, ', smileys_sad=smileys_sad+', sm_s_, ', " - "smileys_other=smileys_other+', sm_o_, ', kicks=kicks+', kicks_, ', kicked=kicked+', kicked_, ', " - "modes=modes+', modes_, ', topics=topics+', topics_, ', ', time_ , '=', time_, '+', line_ ,' " - "WHERE (nick='''' OR nick=''', nick_, ''') AND (chan='''' OR chan=''', chan_, ''')');" - "PREPARE update_query FROM @update_query;" - "EXECUTE update_query;" - "DEALLOCATE PREPARE update_query;" - "END"; - this->RunQuery(query); - - if (this->HasProcedure(prefix + "chanstats_proc_chgdisplay")) - { - query = "DROP PROCEDURE " + prefix + "chanstats_proc_chgdisplay;"; - this->RunQuery(query); - } - query = "CREATE PROCEDURE `" + prefix + "chanstats_proc_chgdisplay`" - "(old_nick varchar(255), new_nick varchar(255))" - "BEGIN " - "DECLARE res_count int(10) unsigned;" - "SELECT COUNT(nick) INTO res_count FROM `" + prefix + "chanstats` WHERE nick = new_nick;" - "IF res_count = 0 THEN " - "UPDATE `" + prefix + "chanstats` SET `nick` = new_nick WHERE `nick` = old_nick;" - "ELSE " - "my_cursor: BEGIN " - "DECLARE no_more_rows BOOLEAN DEFAULT FALSE;" - "DECLARE chan_ VARCHAR(255);" - "DECLARE type_ ENUM('total', 'monthly', 'weekly', 'daily');" - "DECLARE letters_, words_, line_, actions_, smileys_happy_," - "smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_," - "time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_, time9_," - "time10_, time11_, time12_, time13_, time14_, time15_, time16_, time17_, time18_," - "time19_, time20_, time21_, time22_, time23_ INT(10) unsigned;" - "DECLARE stats_cursor CURSOR FOR " - "SELECT chan, type, letters, words, line, actions, smileys_happy," - "smileys_sad, smileys_other, kicks, kicked, modes, topics, time0, time1," - "time2, time3, time4, time5, time6, time7, time8, time9, time10, time11," - "time12, time13, time14, time15, time16, time17, time18, time19, time20," - "time21, time22, time23 " - "FROM `" + prefix + "chanstats` " - "WHERE `nick` = old_nick;" - "DECLARE CONTINUE HANDLER FOR NOT FOUND " - "SET no_more_rows = TRUE;" - "OPEN stats_cursor;" - "the_loop: LOOP " - "FETCH stats_cursor " - "INTO chan_, type_, letters_, words_, line_, actions_, smileys_happy_," - "smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_," - "time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_," - "time9_, time10_, time11_, time12_, time13_, time14_, time15_, time16_," - "time17_, time18_, time19_, time20_, time21_, time22_, time23_;" - "IF no_more_rows THEN " - "CLOSE stats_cursor;" - "LEAVE the_loop;" - "END IF;" - "INSERT INTO `" + prefix + "chanstats` " - "(chan, nick, type, letters, words, line, actions, smileys_happy, " - "smileys_sad, smileys_other, kicks, kicked, modes, topics, time0, time1, " - "time2, time3, time4, time5, time6, time7, time8, time9, time10, time11," - "time12, time13, time14, time15, time16, time17, time18, time19, time20," - "time21, time22, time23)" - "VALUES (chan_, new_nick, type_, letters_, words_, line_, actions_, smileys_happy_," - "smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_," - "time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_, " - "time9_, time10_, time11_, time12_, time13_, time14_, time15_, time16_, " - "time17_, time18_, time19_, time20_, time21_, time22_, time23_)" - "ON DUPLICATE KEY UPDATE letters=letters+VALUES(letters), words=words+VALUES(words)," - "line=line+VALUES(line), actions=actions+VALUES(actions)," - "smileys_happy=smileys_happy+VALUES(smileys_happy)," - "smileys_sad=smileys_sad+VALUES(smileys_sad)," - "smileys_other=smileys_other+VALUES(smileys_other)," - "kicks=kicks+VALUES(kicks), kicked=kicked+VALUES(kicked)," - "modes=modes+VALUES(modes), topics=topics+VALUES(topics)," - "time1=time1+VALUES(time1), time2=time2+VALUES(time2), time3=time3+VALUES(time3)," - "time4=time4+VALUES(time4), time5=time5+VALUES(time5), time6=time6+VALUES(time6)," - "time7=time7+VALUES(time7), time8=time8+VALUES(time8), time9=time9+VALUES(time9)," - "time10=time10+VALUES(time10), time11=time11+VALUES(time11), time12=time12+VALUES(time12)," - "time13=time13+VALUES(time13), time14=time14+VALUES(time14), time15=time15+VALUES(time15)," - "time16=time16+VALUES(time16), time17=time17+VALUES(time17), time18=time18+VALUES(time18)," - "time19=time19+VALUES(time19), time20=time20+VALUES(time20), time21=time21+VALUES(time21)," - "time22=time22+VALUES(time22), time23=time23+VALUES(time23);" - "END LOOP;" - "DELETE FROM `" + prefix + "chanstats` WHERE `nick` = old_nick;" - "END my_cursor;" - "END IF;" - "END;"; - this->RunQuery(query); - - /* don't prepend any database prefix to events so we can always delete/change old events */ - if (this->HasEvent("chanstats_event_cleanup_daily")) - { - query = "DROP EVENT chanstats_event_cleanup_daily"; - this->RunQuery(query); - } - query = "CREATE EVENT `chanstats_event_cleanup_daily` " - "ON SCHEDULE EVERY 1 DAY STARTS CURRENT_DATE " - "DO UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0," - "smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0," - "time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0," - "time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0," - "time20=0, time21=0, time22=0, time23=0 " - "WHERE type='daily';"; - this->RunQuery(query); - - if (this->HasEvent("chanstats_event_cleanup_weekly")) - { - query = "DROP EVENT `chanstats_event_cleanup_weekly`"; - this->RunQuery(query); - } - query = "CREATE EVENT `chanstats_event_cleanup_weekly` " - "ON SCHEDULE EVERY 1 WEEK STARTS ADDDATE(CURDATE(), INTERVAL 1-DAYOFWEEK(CURDATE()) DAY) " - "DO UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0," - "smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0," - "time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0," - "time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0," - "time20=0, time21=0, time22=0, time23=0 " - "WHERE type='weekly';"; - this->RunQuery(query); - - if (this->HasEvent("chanstats_event_cleanup_monthly")) - { - query = "DROP EVENT `chanstats_event_cleanup_monthly`;"; - this->RunQuery(query); - } - query = "CREATE EVENT `chanstats_event_cleanup_monthly` " - "ON SCHEDULE EVERY 1 MONTH STARTS LAST_DAY(CURRENT_TIMESTAMP) + INTERVAL 1 DAY " - "DO BEGIN " - "UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0," - "smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0," - "time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0," - "time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0, " - "time20=0, time21=0, time22=0, time23=0 " - "WHERE type='monthly';" - "OPTIMIZE TABLE `" + prefix + "chanstats`;" - "END;"; - this->RunQuery(query); - } - - - public: - MChanstats(const Anope::string &modname, const Anope::string &creator) : - Module(modname, creator, EXTRA | VENDOR), - cs_stats(this, "CS_STATS"), ns_stats(this, "NS_STATS"), - commandcssetchanstats(this), commandnssetchanstats(this), commandnssasetchanstats(this), - sqlinterface(this) - { - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = conf->GetModule(this); - prefix = block->Get("prefix", "anope_"); - SmileysHappy = block->Get("SmileysHappy"); - SmileysSad = block->Get("SmileysSad"); - SmileysOther = block->Get("SmileysOther"); - NSDefChanstats = block->Get("ns_def_chanstats"); - CSDefChanstats = block->Get("cs_def_chanstats"); - Anope::string engine = block->Get("engine"); - this->sql = ServiceReference("SQL::Provider", engine); - if (sql) - this->CheckTables(); - else - Log(this) << "no database connection to " << engine; - } - - void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_all) override - { - if (!show_all) - return; - if (cs_stats.HasExt(ci)) - info.AddOption(_("Chanstats")); - } - - void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override - { - if (!show_hidden) - return; - if (ns_stats.HasExt(na->GetAccount())) - info.AddOption(_("Chanstats")); - } - - void OnTopicUpdated(User *source, Channel *c, const Anope::string &user, const Anope::string &topic) override - { - if (!source || !source->Account() || !c->ci || !cs_stats.HasExt(c->ci)) - return; - query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);"; - query.SetValue("channel", c->name); - query.SetValue("nick", GetDisplay(source)); - this->RunQuery(query); - } - - EventReturn OnChannelModeSet(Channel *c, const MessageSource &setter, ChannelMode *mode, const Anope::string ¶m) override - { - this->OnModeChange(c, setter.GetUser()); - return EVENT_CONTINUE; - } - - EventReturn OnChannelModeUnset(Channel *c, const MessageSource &setter, ChannelMode *, const Anope::string ¶m) override - { - this->OnModeChange(c, setter.GetUser()); - return EVENT_CONTINUE; - } - - private: - void OnModeChange(Channel *c, User *u) - { - if (!u || !u->Account() || !c->ci || !cs_stats.HasExt(c->ci)) - return; - - query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);"; - query.SetValue("channel", c->name); - query.SetValue("nick", GetDisplay(u)); - this->RunQuery(query); - } - - public: - void OnPreUserKicked(const MessageSource &source, ChanUserContainer *cu, const Anope::string &kickmsg) override - { - if (!cu->chan->ci || !cs_stats.HasExt(cu->chan->ci)) - return; - - query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0);"; - query.SetValue("channel", cu->chan->name); - query.SetValue("nick", GetDisplay(cu->user)); - this->RunQuery(query); - - query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0);"; - query.SetValue("channel", cu->chan->name); - query.SetValue("nick", GetDisplay(source.GetUser())); - this->RunQuery(query); - } - - void OnPrivmsg(User *u, Channel *c, Anope::string &msg) override - { - if (!c->ci || !cs_stats.HasExt(c->ci)) - return; - - size_t letters = msg.length(); - size_t words = this->CountWords(msg); - - size_t action = 0; - if (msg.find("\01ACTION")!=Anope::string::npos) - { - action = 1; - letters = letters - 7; - words--; - } - - // count smileys - size_t smileys_happy = CountSmileys(msg, SmileysHappy); - size_t smileys_sad = CountSmileys(msg, SmileysSad); - size_t smileys_other = CountSmileys(msg, SmileysOther); - - // do not count smileys as words - size_t smileys = smileys_happy + smileys_sad + smileys_other; - if (smileys > words) - words = 0; - else - words = words - smileys; - - query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 1, @letters@, @words@, @action@, " - "@smileys_happy@, @smileys_sad@, @smileys_other@, '0', '0', '0', '0');"; - query.SetValue("channel", c->name); - query.SetValue("nick", GetDisplay(u)); - query.SetValue("letters", letters); - query.SetValue("words", words); - query.SetValue("action", action); - query.SetValue("smileys_happy", smileys_happy); - query.SetValue("smileys_sad", smileys_sad); - query.SetValue("smileys_other", smileys_other); - this->RunQuery(query); - } - - void OnDelCore(NickServ::Account *nc) override - { - query = "DELETE FROM `" + prefix + "chanstats` WHERE `nick` = @nick@;"; - query.SetValue("nick", nc->GetDisplay()); - this->RunQuery(query); - } - - void OnChangeCoreDisplay(NickServ::Account *nc, const Anope::string &newdisplay) override - { - query = "CALL " + prefix + "chanstats_proc_chgdisplay(@old_display@, @new_display@);"; - query.SetValue("old_display", nc->GetDisplay()); - query.SetValue("new_display", newdisplay); - this->RunQuery(query); - } - - void OnDelChan(ChanServ::Channel *ci) override - { - query = "DELETE FROM `" + prefix + "chanstats` WHERE `chan` = @channel@;"; - query.SetValue("channel", ci->GetName()); - this->RunQuery(query); - } - - void OnChanRegistered(ChanServ::Channel *ci) - { - if (CSDefChanstats) - ci->Extend("CS_STATS"); - } - - void OnNickRegister(User *user, NickAlias *na, const Anope::string &) - { - if (NSDefChanstats) - na->GetAccount()->Extend("NS_STATS"); - } -}; - -MODULE_INIT(MChanstats) - -- cgit