From 352e24cba9aadc0a2ed66e88e1439a1c2a20f2f5 Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Wed, 2 Jun 2021 04:05:55 +0100 Subject: Fix renicking duplicate users when the casemapping changes. --- src/modules/m_codepage.cpp | 47 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/modules/m_codepage.cpp b/src/modules/m_codepage.cpp index 958bd07f7..40d63de37 100644 --- a/src/modules/m_codepage.cpp +++ b/src/modules/m_codepage.cpp @@ -79,17 +79,44 @@ class ModuleCodepage hashmap.swap(newhash); } + void ChangeNick(User* user, const std::string& message) + { + user->WriteNumeric(RPL_SAVENICK, user->uuid, message); + user->ChangeNick(user->uuid); + } + void CheckDuplicateNick() { - insp::flat_set duplicates; - const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); - for (UserManager::LocalList::const_iterator iter = list.begin(); iter != list.end(); ++iter) + user_hash duplicates; + const user_hash& users = ServerInstance->Users->GetUsers(); + for (user_hash::const_iterator iter = users.begin(); iter != users.end(); ++iter) { - LocalUser* user = *iter; - if (user->nick != user->uuid && !duplicates.insert(user->nick).second) + User* user = iter->second; + if (user->nick == user->uuid) + continue; // UUID users are always unique. + + std::pair check = duplicates.insert(std::make_pair(user->nick, user)); + if (check.second) + continue; // No duplicate. + + User* otheruser = check.first->second; + if (otheruser->age < user->age) { - user->WriteNumeric(RPL_SAVENICK, user->uuid, "Your nickname is no longer available."); - user->ChangeNick(user->uuid); + // The other user connected first. + ChangeNick(user, "Your nickname is no longer available."); + } + else if (otheruser->age > user->age) + { + // The other user connected last. + ChangeNick(otheruser, "Your nickname is no longer available."); + check.first->second = user; + } + else + { + // Both connected at the same time. + ChangeNick(user, "Your nickname is no longer available."); + ChangeNick(otheruser, "Your nickname is no longer available."); + duplicates.erase(check.first); } } } @@ -101,10 +128,7 @@ class ModuleCodepage { LocalUser* user = *iter; if (user->nick != user->uuid && !ServerInstance->IsNick(user->nick)) - { - user->WriteNumeric(RPL_SAVENICK, user->uuid, "Your nickname is no longer valid."); - user->ChangeNick(user->uuid); - } + ChangeNick(user, "Your nickname is no longer valid."); } } @@ -215,6 +239,7 @@ class ModuleCodepage ServerInstance->Config->CaseMapping = name; national_case_insensitive_map = casemap; + CheckDuplicateNick(); CheckRehash(newcasemap); ServerInstance->ISupport.Build(); -- cgit v1.3.1-10-gc9f91 From 26a1c1955f926b54acdafeaa7b8132e53e90bdeb Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Fri, 4 Nov 2022 12:24:12 +0000 Subject: Fix destroying duplicate channels when the casemapping changes. --- src/modules/m_codepage.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'src') diff --git a/src/modules/m_codepage.cpp b/src/modules/m_codepage.cpp index 40d63de37..bcaa7e78c 100644 --- a/src/modules/m_codepage.cpp +++ b/src/modules/m_codepage.cpp @@ -79,12 +79,63 @@ class ModuleCodepage hashmap.swap(newhash); } + void DestroyChannel(Channel* chan) + { + // Remove all of the users from the channel. Using KICK here will mean + // the user's client will probably attempt to rejoin and will enter the + // succeeding channel. Unfortunately this is the best we can do for now. + while (!chan->userlist.empty()) + chan->KickUser(ServerInstance->FakeClient, chan->userlist.begin(), "This channel does not exist anymore."); + + // Remove all modes from the channel just in case one of them keeps the channel open. + Modes::ChangeList changelist; + const ModeParser::ModeHandlerMap& chanmodes = ServerInstance->Modes->GetModes(MODETYPE_CHANNEL); + for (ModeParser::ModeHandlerMap::const_iterator i = chanmodes.begin(); i != chanmodes.end(); ++i) + i->second->RemoveMode(chan, changelist); + ServerInstance->Modes->Process(ServerInstance->FakeClient, chan, NULL, changelist, ModeParser::MODE_LOCALONLY); + + // The channel will be destroyed automatically by CheckDestroy. + } + void ChangeNick(User* user, const std::string& message) { user->WriteNumeric(RPL_SAVENICK, user->uuid, message); user->ChangeNick(user->uuid); } + void CheckDuplicateChan() + { + chan_hash duplicates; + const chan_hash& chans = ServerInstance->GetChans(); + for (chan_hash::const_iterator iter = chans.begin(); iter != chans.end(); ++iter) + { + Channel* chan = iter->second; + std::pair check = duplicates.insert(std::make_pair(chan->name, chan)); + if (check.second) + continue; // No duplicate. + + Channel* otherchan = check.first->second; + if (otherchan->age < chan->age) + { + // The other channel was created first. + DestroyChannel(chan); + } + else if (otherchan->age > chan->age) + { + // The other channel was created last. + DestroyChannel(otherchan); + check.first->second = chan; + } + else + { + // Both created at the same time. + DestroyChannel(chan); + DestroyChannel(otherchan); + duplicates.erase(check.first); + } + } + } + void CheckDuplicateNick() { user_hash duplicates; @@ -157,6 +208,7 @@ class ModuleCodepage ServerInstance->Config->CaseMapping = origcasemapname; national_case_insensitive_map = origcasemap; + CheckDuplicateChan(); CheckDuplicateNick(); CheckRehash(casemap); @@ -239,6 +291,7 @@ class ModuleCodepage ServerInstance->Config->CaseMapping = name; national_case_insensitive_map = casemap; + CheckDuplicateChan(); CheckDuplicateNick(); CheckRehash(newcasemap); -- cgit v1.3.1-10-gc9f91 From 5a24fb0f61dec76b7a022d8da37fc1c222d63d95 Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Wed, 1 Mar 2023 20:07:16 +0000 Subject: Add client cert activation/expiration times to the ssl_cert class. --- include/modules/ssl.h | 29 +++++++++++++++++++++++++++-- src/modules/extra/m_ssl_gnutls.cpp | 23 +++++++++++++++++++---- src/modules/extra/m_ssl_mbedtls.cpp | 27 +++++++++++++++++++++++++-- src/modules/extra/m_ssl_openssl.cpp | 32 ++++++++++++++++++++++++++++---- 4 files changed, 99 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/include/modules/ssl.h b/include/modules/ssl.h index 603fe9305..5b60ac9a6 100644 --- a/include/modules/ssl.h +++ b/include/modules/ssl.h @@ -47,8 +47,17 @@ class ssl_cert : public refcountbase std::string error; std::string fingerprint; bool trusted, invalid, unknownsigner, revoked; - - ssl_cert() : trusted(false), invalid(true), unknownsigner(true), revoked(false) {} + time_t activation, expiration; + + ssl_cert() + : trusted(false) + , invalid(true) + , unknownsigner(true) + , revoked(false) + , activation(0) + , expiration(0) + { + } /** Get certificate distinguished name * @return Certificate DN @@ -137,6 +146,22 @@ class ssl_cert : public refcountbase return IsUsable() && trusted && !unknownsigner; } + /** Retrieves the client certificate activation time. + * @param The time the client certificate was activated or 0 on error. + */ + time_t GetActivationTime() const + { + return activation; + } + + /** Retrieves the client certificate expiration time. + * @param The time the client certificate will expire or 0 on error. + */ + time_t GetExpirationTime() const + { + return expiration; + } + std::string GetMetaLine() const { std::stringstream value; diff --git a/src/modules/extra/m_ssl_gnutls.cpp b/src/modules/extra/m_ssl_gnutls.cpp index 2c469c4b8..da2ef1400 100644 --- a/src/modules/extra/m_ssl_gnutls.cpp +++ b/src/modules/extra/m_ssl_gnutls.cpp @@ -878,11 +878,26 @@ class GnuTLSIOHook : public SSLIOHook certinfo->fingerprint = BinToHex(buffer, buffer_size); } - /* Beware here we do not check for errors. - */ - if ((gnutls_x509_crt_get_expiration_time(cert) < ServerInstance->Time()) || (gnutls_x509_crt_get_activation_time(cert) > ServerInstance->Time())) + certinfo->activation = gnutls_x509_crt_get_activation_time(cert); + if (certinfo->activation == -1) + { + certinfo->activation = 0; + certinfo->error = "Unable to check certificate activation time"; + } + else if (certinfo->activation >= ServerInstance->Time()) + { + certinfo->error = "Certificate not activated"; + } + + certinfo->expiration = gnutls_x509_crt_get_expiration_time(cert); + if (certinfo->expiration == -1) + { + certinfo->expiration = 0; + certinfo->error = "Unable to check certificate expiration time"; + } + else if (certinfo->expiration <= ServerInstance->Time()) { - certinfo->error = "Not activated, or expired certificate"; + certinfo->error = "Certificate has expired"; } info_done_dealloc: diff --git a/src/modules/extra/m_ssl_mbedtls.cpp b/src/modules/extra/m_ssl_mbedtls.cpp index a2529dcb3..35fd8926f 100644 --- a/src/modules/extra/m_ssl_mbedtls.cpp +++ b/src/modules/extra/m_ssl_mbedtls.cpp @@ -29,6 +29,10 @@ #include "inspircd.h" #include "modules/ssl.h" +#ifdef _WIN32 +# define timegm _mkgmtime +#endif + // Fix warnings about the use of commas at end of enumerator lists on C++03. #if defined __clang__ # pragma clang diagnostic ignored "-Wc++11-extensions" @@ -631,6 +635,8 @@ class mbedTLSIOHook : public SSLIOHook return; } + certificate->activation = GetTime(&cert->valid_from); + certificate->expiration = GetTime(&cert->valid_to); if (flags == 0) { // Verification succeeded @@ -640,8 +646,10 @@ class mbedTLSIOHook : public SSLIOHook { // Verification failed certificate->trusted = false; - if ((flags & MBEDTLS_X509_BADCERT_EXPIRED) || (flags & MBEDTLS_X509_BADCERT_FUTURE)) - certificate->error = "Not activated, or expired certificate"; + if (flags & MBEDTLS_X509_BADCERT_FUTURE) + certificate->error = "Certificate not activated"; + else if (flags & MBEDTLS_X509_BADCERT_EXPIRED) + certificate->error = "Certificate has expired"; } certificate->unknownsigner = (flags & MBEDTLS_X509_BADCERT_NOT_TRUSTED); @@ -664,6 +672,21 @@ class mbedTLSIOHook : public SSLIOHook out[pos] = ' '; } + static time_t GetTime(const mbedtls_x509_time* x509time) + { + // HACK: this is terrible but there's no sensible way I can see to get + // a time_t from this. + tm ts; + ts.tm_year = x509time->year - 1900; + ts.tm_mon = x509time->mon - 1; + ts.tm_mday = x509time->day; + ts.tm_hour = x509time->hour; + ts.tm_min = x509time->min; + ts.tm_sec = x509time->sec; + + return timegm(&ts); + } + static int Pull(void* userptr, unsigned char* buffer, size_t size) { StreamSocket* const sock = reinterpret_cast(userptr); diff --git a/src/modules/extra/m_ssl_openssl.cpp b/src/modules/extra/m_ssl_openssl.cpp index b3aed3896..f786c46d3 100644 --- a/src/modules/extra/m_ssl_openssl.cpp +++ b/src/modules/extra/m_ssl_openssl.cpp @@ -78,6 +78,7 @@ #endif #ifdef _WIN32 +# define timegm _mkgmtime # pragma comment(lib, "libcrypto.lib") # pragma comment(lib, "libssl.lib") #endif @@ -700,10 +701,16 @@ class OpenSSLIOHook : public SSLIOHook certinfo->fingerprint = BinToHex(md, n); } - if ((ASN1_UTCTIME_cmp_time_t(X509_getm_notAfter(cert), ServerInstance->Time()) == -1) || (ASN1_UTCTIME_cmp_time_t(X509_getm_notBefore(cert), ServerInstance->Time()) == 0)) - { - certinfo->error = "Not activated, or expired certificate"; - } + certinfo->activation = GetTime(X509_getm_notBefore(cert)); + certinfo->expiration = GetTime(X509_getm_notAfter(cert)); + + int activated = ASN1_UTCTIME_cmp_time_t(X509_getm_notBefore(cert), ServerInstance->Time()); + if (activated != -1 && activated != 0) + certinfo->error = "Certificate not activated"; + + int expired = ASN1_UTCTIME_cmp_time_t(X509_getm_notAfter(cert), ServerInstance->Time()); + if (expired != 0 && expired != 1) + certinfo->error = "Certificate has expired"; X509_free(cert); } @@ -718,6 +725,23 @@ class OpenSSLIOHook : public SSLIOHook out[pos] = ' '; } + static time_t GetTime(ASN1_TIME* x509time) + { +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + if (!x509time) + return 0; + + struct tm ts; + if (!ASN1_TIME_to_tm(x509time, &ts)) + return 0; + + return timegm(&ts); +#else + // OpenSSL 1.1 is required for ASN_TIME_to_tm. + return 0; +#endif + } + void SSLInfoCallback(int where, int rc) { if ((where & SSL_CB_HANDSHAKE_START) && (status == STATUS_OPEN)) -- cgit v1.3.1-10-gc9f91 From 4e1d7b84f5415e9901ec18c0ed4cb012376e4859 Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Wed, 1 Mar 2023 20:07:27 +0000 Subject: Warn users when their client certificate is about to expire. Closes #1938. --- docs/conf/modules.conf.example | 6 ++++-- src/modules/m_sslinfo.cpp | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example index 18386146b..874a93210 100644 --- a/docs/conf/modules.conf.example +++ b/docs/conf/modules.conf.example @@ -2318,9 +2318,11 @@ # If you want to prevent users from viewing TLS (SSL) certificate information # and fingerprints of other users, set operonly to yes. You can also set hash # to an IANA Hash Function Textual Name to use the SSL fingerprint sent by a -# WebIRC gateway (requires the cgiirc module). +# WebIRC gateway (requires the cgiirc module) and warnexpiring to warn users +# when their client certificate is about to expire. # +# hash="sha-256" +# warnexpiring="1w"> #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# # mbedTLS TLS (SSL) module: Adds support for TLS (SSL) connections using mbedTLS. diff --git a/src/modules/m_sslinfo.cpp b/src/modules/m_sslinfo.cpp index f7f970a9d..309a99a64 100644 --- a/src/modules/m_sslinfo.cpp +++ b/src/modules/m_sslinfo.cpp @@ -253,6 +253,7 @@ class ModuleSSLInfo private: CommandSSLInfo cmd; std::string hash; + unsigned long warnexpiring; bool MatchFP(ssl_cert* const cert, const std::string& fp) const { @@ -273,6 +274,7 @@ class ModuleSSLInfo ConfigTag* tag = ServerInstance->Config->ConfValue("sslinfo"); cmd.operonlyfp = tag->getBool("operonly"); hash = tag->getString("hash"); + warnexpiring = tag->getDuration("warnexpiring", 0, 0, 60*60*24*365); } Version GetVersion() CXX11_OVERRIDE @@ -386,6 +388,19 @@ class ModuleSSLInfo if (do_login) user->Oper(ifo); } + + if (!warnexpiring || !cert->GetExpirationTime()) + return; + + if (ServerInstance->Time() > cert->GetExpirationTime()) + { + user->WriteNotice("*** Your TLS (SSL) client certificate has expired."); + } + else if (static_cast(ServerInstance->Time() + warnexpiring) > cert->GetExpirationTime()) + { + const std::string duration = InspIRCd::DurationString(cert->GetExpirationTime() - ServerInstance->Time()); + user->WriteNotice("*** Your TLS (SSL) client certificate expires in " + duration + "."); + } } ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE -- cgit v1.3.1-10-gc9f91 From 45b871b789ce6a4b82f77f0ec364459080b52dd2 Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Fri, 21 Apr 2023 12:36:10 +0100 Subject: Fix an off by one error in the rmode module. --- src/modules/m_rmode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/modules/m_rmode.cpp b/src/modules/m_rmode.cpp index f39e9db06..14019a243 100644 --- a/src/modules/m_rmode.cpp +++ b/src/modules/m_rmode.cpp @@ -47,7 +47,7 @@ class CommandRMode : public Command ModeHandler* mh = ServerInstance->Modes->FindMode(modeletter, MODETYPE_CHANNEL); if (mh == NULL || parameters[1].size() > 1) { - user->WriteNumeric(ERR_UNKNOWNMODE, parameters[0], "is not a recognised channel mode."); + user->WriteNumeric(ERR_UNKNOWNMODE, parameters[1], "is not a recognised channel mode."); return CMD_FAILURE; } -- cgit v1.3.1-10-gc9f91