From cbdcd051c63c4ff98dfb4ff45388e722f8d0a2de Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Fri, 27 Mar 2026 13:10:26 +0000 Subject: Switch the extensible system to using shared pointers. --- modules/callerid.cpp | 37 ++++++++++---------- modules/cap.cpp | 4 +-- modules/cloak.cpp | 8 ++--- modules/cloak_custom.cpp | 5 +-- modules/core/core_channel/invite.h | 33 +++++++++--------- modules/dccallow.cpp | 5 +-- modules/extra/geo_maxmind.cpp | 69 +++++++++++++------------------------- modules/extra/ssl_gnutls.cpp | 2 +- modules/extra/ssl_openssl.cpp | 2 +- modules/geoban.cpp | 6 ++-- modules/geoclass.cpp | 6 ++-- modules/haproxy.cpp | 2 +- modules/ident.cpp | 3 +- modules/monitor.cpp | 24 ++++++------- modules/silence.cpp | 24 +++++-------- modules/spanningtree/hmac.cpp | 2 +- modules/sslinfo.cpp | 67 +++++++++--------------------------- 17 files changed, 118 insertions(+), 181 deletions(-) (limited to 'modules') diff --git a/modules/callerid.cpp b/modules/callerid.cpp index 23410a594..f89b1e9aa 100644 --- a/modules/callerid.cpp +++ b/modules/callerid.cpp @@ -85,15 +85,15 @@ struct CallerIDExtInfo final { } - std::string ToHuman(const Extensible* container, void* item) const noexcept override + std::string ToHuman(const Extensible* container, const ExtensionPtr& item) const noexcept override { - callerid_data* dat = static_cast(item); + const auto& dat = std::static_pointer_cast(item); return dat->ToString(true); } - std::string ToInternal(const Extensible* container, void* item) const noexcept override + std::string ToInternal(const Extensible* container, const ExtensionPtr& item) const noexcept override { - callerid_data* dat = static_cast(item); + const auto& dat = std::static_pointer_cast(item); return dat->ToString(false); } @@ -102,10 +102,11 @@ struct CallerIDExtInfo final if (container->extype != this->extype) return; - void* old = GetRaw(container); + auto* old = GetRaw(container); if (old) - this->Delete(nullptr, old); - auto* dat = new callerid_data(); + this->OnDelete(container, *old); + + auto dat = std::make_shared(); SetRaw(container, dat); StringSplitter s(value, ','); @@ -119,7 +120,7 @@ struct CallerIDExtInfo final if (dat->accepting.insert(u).second) { callerid_data* other = this->Get(u, true); - other->wholistsme.push_back(dat); + other->wholistsme.push_back(dat.get()); } } } @@ -127,18 +128,21 @@ struct CallerIDExtInfo final callerid_data* Get(User* user, bool create) { - callerid_data* dat = static_cast(GetRaw(user)); - if (create && !dat) + auto* dat = GetRaw(user); + if (!dat) { - dat = new callerid_data; - SetRaw(user, dat); + if (!create) + return nullptr; + + SetRaw(user, std::make_shared()); + dat = GetRaw(user); } - return dat; + return std::static_pointer_cast(*dat).get(); } - void Delete(Extensible* container, void* item) override + void OnDelete(const Extensible* container, const ExtensionPtr& item) override { - callerid_data* dat = static_cast(item); + const auto& dat = std::static_pointer_cast(item); // We need to walk the list of users on our accept list, and remove ourselves from their wholistsme. for (auto* user : dat->accepting) @@ -150,10 +154,9 @@ struct CallerIDExtInfo final continue; // shouldn't happen, but oh well. } - if (!stdalgo::vector::swaperase(target->wholistsme, dat)) + if (!stdalgo::vector::swaperase(target->wholistsme, dat.get())) ServerInstance->Logs.Debug(MODNAME, "BUG: Inconsistency detected in callerid state, please report (2)"); } - delete dat; } }; diff --git a/modules/cap.cpp b/modules/cap.cpp index 7a34af4dd..0835847ab 100644 --- a/modules/cap.cpp +++ b/modules/cap.cpp @@ -318,12 +318,12 @@ Cap::ExtItem::ExtItem(Module* mod) { } -std::string Cap::ExtItem::ToHuman(const Extensible* container, void* item) const noexcept +std::string Cap::ExtItem::ToHuman(const Extensible* container, const ExtensionPtr& item) const noexcept { return SerializeCaps(container, true); } -std::string Cap::ExtItem::ToInternal(const Extensible* container, void* item) const noexcept +std::string Cap::ExtItem::ToInternal(const Extensible* container, const ExtensionPtr& item) const noexcept { return SerializeCaps(container, false); } diff --git a/modules/cloak.cpp b/modules/cloak.cpp index df903b28f..5c1a94b75 100644 --- a/modules/cloak.cpp +++ b/modules/cloak.cpp @@ -110,9 +110,9 @@ public: } } - std::string ToHuman(const Extensible* container, void* item) const noexcept override + std::string ToHuman(const Extensible* container, const ExtensionPtr& item) const noexcept override { - auto* cloaks = static_cast(item); + const auto& cloaks = std::static_pointer_cast(item); if (cloaks->empty()) return {}; @@ -124,9 +124,9 @@ public: return value; } - std::string ToInternal(const Extensible* container, void* item) const noexcept override + std::string ToInternal(const Extensible* container, const ExtensionPtr& item) const noexcept override { - auto* cloaks = static_cast(item); + const auto& cloaks = std::static_pointer_cast(item); if (cloaks->empty()) return {}; diff --git a/modules/cloak_custom.cpp b/modules/cloak_custom.cpp index 1ca666797..70a68cf0b 100644 --- a/modules/cloak_custom.cpp +++ b/modules/cloak_custom.cpp @@ -67,9 +67,10 @@ public: } } - std::string ToInternal(const Extensible* container, void* item) const noexcept override + std::string ToInternal(const Extensible* container, const ExtensionPtr& item) const noexcept override { - return item ? Percent::Encode(static_cast(item)->ToString()) : std::string(); + const auto& cloak = std::static_pointer_cast(item); + return cloak ? Percent::Encode(cloak->ToString()) : std::string(); } }; diff --git a/modules/core/core_channel/invite.h b/modules/core/core_channel/invite.h index 2313f191f..cebef5a94 100644 --- a/modules/core/core_channel/invite.h +++ b/modules/core/core_channel/invite.h @@ -49,10 +49,10 @@ class Invite::ExtItem final : public ExtensionItem { private: - static std::string ToString(void* item, bool human) + static std::string ToString(const ExtensionPtr& item, bool human) { std::string ret; - Store* store = static_cast*>(item); + const auto& store = std::static_pointer_cast>(item); for (auto* inv : store->invites) inv->Serialize(human, (ExtType == ExtensionType::USER), ret); @@ -70,42 +70,43 @@ public: Store* Get(Extensible* ext, bool create = false) { - Store* store = static_cast*>(GetRaw(ext)); - if ((create) && (!store)) + auto* store = GetRaw(ext); + if (!store) { - store = new Store; - SetRaw(ext, store); + if (!create) + return nullptr; + + SetRaw(ext, std::make_shared>()); + store = GetRaw(ext); } - return store; + return std::static_pointer_cast>(*store).get(); } void Unset(Extensible* ext) { - void* store = UnsetRaw(ext); + auto store = UnsetRaw(ext); if (store) - Delete(ext, store); + OnDelete(ext, store); } - void Delete(Extensible* container, void* item) override + void OnDelete(const Extensible* container, const ExtensionPtr& item) override { - Store* store = static_cast*>(item); - for (typename Store::List::iterator i = store->invites.begin(); i != store->invites.end(); ) + const auto& store = std::static_pointer_cast>(item); + for (auto i = store->invites.begin(); i != store->invites.end(); ) { Invite* inv = *i; // Destructing the Invite invalidates the iterator, so move it now ++i; RemoveInvite(inv, (ExtType != ExtensionType::USER), (ExtType == ExtensionType::USER)); } - - delete store; } - std::string ToHuman(const Extensible* container, void* item) const noexcept override + std::string ToHuman(const Extensible* container, const ExtensionPtr& item) const noexcept override { return ToString(item, true); } - std::string ToInternal(const Extensible* container, void* item) const noexcept override + std::string ToInternal(const Extensible* container, const ExtensionPtr& item) const noexcept override { return ToString(item, false); } diff --git a/modules/dccallow.cpp b/modules/dccallow.cpp index f52e16851..f17919f75 100644 --- a/modules/dccallow.cpp +++ b/modules/dccallow.cpp @@ -168,9 +168,10 @@ public: Set(user, list); } - std::string ToInternal(const Extensible* container, void* item) const noexcept override + std::string ToInternal(const Extensible* container, const ExtensionPtr& item) const noexcept override { - auto* list = static_cast(item); + const auto& list = std::static_pointer_cast(item); + std::string buf; for (const auto& entry : *list) { diff --git a/modules/extra/geo_maxmind.cpp b/modules/extra/geo_maxmind.cpp index 3b1b8f73e..b43ae3072 100644 --- a/modules/extra/geo_maxmind.cpp +++ b/modules/extra/geo_maxmind.cpp @@ -34,45 +34,22 @@ #include "modules/geolocation.h" class GeolocationExtItem final - : public ExtensionItem + : public SimpleExtItem { public: - GeolocationExtItem(Module* parent) - : ExtensionItem(parent, "geolocation", ExtensionType::USER) + GeolocationExtItem(Module* mod) + : SimpleExtItem(mod, "geolocation", ExtensionType::USER) { } - std::string ToHuman(const Extensible* container, void* item) const noexcept override + std::string ToHuman(const Extensible* container, const ExtensionPtr& item) const noexcept override { - Geolocation::Location* location = static_cast(item); + const auto& location = std::static_pointer_cast(item); return location->GetName() + " [" + location->GetCode() + "]"; } - - void Delete(Extensible* container, void* item) override - { - Geolocation::Location* old = static_cast(item); - if (old) - old->refcount_dec(); - } - - Geolocation::Location* Get(const User* user) const - { - return static_cast(GetRaw(user)); - } - - void Set(User* user, Geolocation::Location* value) - { - value->refcount_inc(); - Delete(user, SetRaw(user, value)); - } - - void Unset(User* user) - { - Delete(user, UnsetRaw(user)); - } }; -using LocationMap = insp::flat_map; +using LocationMap = insp::flat_map>; class GeolocationAPIImpl final : public Geolocation::APIBase @@ -88,11 +65,11 @@ public: { } - Geolocation::Location* GetLocation(User* user) override + Geolocation::LocationPtr GetLocation(User* user) override { // If we have the location cached then use that instead. - Geolocation::Location* location = ext.Get(user); - if (location) + auto location = ext.GetPtr(user); + if (!location) return location; // Attempt to locate this user. @@ -105,7 +82,7 @@ public: return location; } - Geolocation::Location* GetLocation(irc::sockets::sockaddrs& sa) override + Geolocation::LocationPtr GetLocation(irc::sockets::sockaddrs& sa) override { // Skip trying to look up a UNIX socket. if (!sa.is_ip()) @@ -127,7 +104,11 @@ public: const std::string code(country_code.utf8_string, country_code.data_size); LocationMap::iterator liter = locations.find(code); if (liter != locations.end()) - return liter->second; + { + auto ptr = liter->second.lock(); + if (ptr) + return ptr; // We have a country already. + } // Attempt to retrieve the country name. MMDB_entry_data_s country_name; @@ -137,7 +118,7 @@ public: // Create a Location object and cache it. const std::string cname(country_name.utf8_string, country_name.data_size); - auto* location = new Geolocation::Location(code, cname); + auto location = std::make_shared(code, cname); locations[code] = location; return location; } @@ -192,20 +173,16 @@ public: { for (LocationMap::iterator iter = geoapi.locations.begin(); iter != geoapi.locations.end(); ) { - Geolocation::Location* location = iter->second; - if (location->GetUseCount()) - { - ServerInstance->Logs.Debug(MODNAME, "Preserving geolocation data for {} ({}) with use count {}... ", - location->GetName(), location->GetCode(), location->GetUseCount()); - iter++; - } - else + auto location = iter->second.lock(); + if (!location) { - ServerInstance->Logs.Debug(MODNAME, "Deleting unused geolocation data for {} ({})", - location->GetName(), location->GetCode()); - delete location; iter = geoapi.locations.erase(iter); + continue; // Entry expired. } + + ServerInstance->Logs.Debug(MODNAME, "Preserving geolocation data for {} ({}) with use count {}... ", + location->GetName(), location->GetCode(), location.use_count()); + iter++; } geoapi.locations.shrink_to_fit(); } diff --git a/modules/extra/ssl_gnutls.cpp b/modules/extra/ssl_gnutls.cpp index 0b976fad5..9602b5728 100644 --- a/modules/extra/ssl_gnutls.cpp +++ b/modules/extra/ssl_gnutls.cpp @@ -653,7 +653,7 @@ private: void VerifyCertificate() { - auto* certinfo = new ssl_cert(); + auto certinfo = std::make_shared(); this->certificate = certinfo; unsigned int certstatus; diff --git a/modules/extra/ssl_openssl.cpp b/modules/extra/ssl_openssl.cpp index 96b4f8c09..4c3a736e7 100644 --- a/modules/extra/ssl_openssl.cpp +++ b/modules/extra/ssl_openssl.cpp @@ -548,7 +548,7 @@ private: void VerifyCertificate() { - auto* certinfo = new ssl_cert(); + auto certinfo = std::make_shared(); this->certificate = certinfo; auto* cert = SSL_get1_peer_certificate(sess); diff --git a/modules/geoban.cpp b/modules/geoban.cpp index 125a522b1..ff64bcf6c 100644 --- a/modules/geoban.cpp +++ b/modules/geoban.cpp @@ -39,7 +39,7 @@ public: bool IsMatch(ListModeBase* lm, User* user, Channel* channel, const std::string& text, const ExtBan::MatchConfig& config) override { - Geolocation::Location* location = geoapi ? geoapi->GetLocation(user) : nullptr; + const auto location = geoapi ? geoapi->GetLocation(user) : nullptr; const std::string code = location ? location->GetCode() : "XX"; // Does this user match against the ban? @@ -71,7 +71,7 @@ public: if (!request.flags['G']) return MOD_RES_PASSTHRU; - Geolocation::Location* location = geoapi ? geoapi->GetLocation(user) : nullptr; + const auto location = geoapi ? geoapi->GetLocation(user) : nullptr; const std::string code = location ? location->GetCode() : "XX"; return InspIRCd::Match(code, request.matchtext, ascii_case_insensitive_map) ? MOD_RES_ALLOW : MOD_RES_DENY; } @@ -81,7 +81,7 @@ public: if (whois.GetTarget()->server->IsService()) return; - Geolocation::Location* location = geoapi ? geoapi->GetLocation(whois.GetTarget()) : nullptr; + const auto location = geoapi ? geoapi->GetLocation(whois.GetTarget()) : nullptr; if (location) whois.SendLine(RPL_WHOISCOUNTRY, location->GetCode(), "is connecting from " + location->GetName()); else diff --git a/modules/geoclass.cpp b/modules/geoclass.cpp index 0ca587036..4cfcc62d2 100644 --- a/modules/geoclass.cpp +++ b/modules/geoclass.cpp @@ -46,7 +46,7 @@ public: // If we can't find the location of this user then we can't assign // them to a location-specific connect class. - Geolocation::Location* location = geoapi ? geoapi->GetLocation(user) : nullptr; + const auto location = geoapi ? geoapi->GetLocation(user) : nullptr; const std::string code = location ? location->GetCode() : "XX"; StringSplitter codes(country); @@ -78,14 +78,14 @@ public: return MOD_RES_PASSTHRU; // Counter for the number of users in each country. - std::map counts; + std::map counts; // Counter for the number of users in an unknown country. size_t unknown = 0; for (auto* user : ServerInstance->Users.GetLocalUsers()) { - Geolocation::Location* location = geoapi ? geoapi->GetLocation(user) : nullptr; + const auto location = geoapi ? geoapi->GetLocation(user) : nullptr; if (location) counts[location]++; else diff --git a/modules/haproxy.cpp b/modules/haproxy.cpp index 20159b89a..c6eff4a34 100644 --- a/modules/haproxy.cpp +++ b/modules/haproxy.cpp @@ -210,7 +210,7 @@ private: // Create a fake ssl_cert for the user. Ideally we should use the user's // TLS client certificate here but as of 2018-10-16 this is not forwarded // by HAProxy. - auto* cert = new ssl_cert(); + auto cert = std::make_shared(); cert->error = "HAProxy does not forward client TLS certificates"; cert->invalid = true; cert->revoked = true; diff --git a/modules/ident.cpp b/modules/ident.cpp index 97e504e83..da84227c2 100644 --- a/modules/ident.cpp +++ b/modules/ident.cpp @@ -342,8 +342,7 @@ public: try { - isock = new IdentRequestSocket(this, user); - socket.Set(user, isock); + socket.SetFwd(user, this, user); } catch (const ModuleException& e) { diff --git a/modules/monitor.cpp b/modules/monitor.cpp index 5255c79b2..9c14db837 100644 --- a/modules/monitor.cpp +++ b/modules/monitor.cpp @@ -74,24 +74,27 @@ class IRCv3::Monitor::Manager final ExtData* Get(User* user, bool create = false) { - ExtData* extdata = static_cast(GetRaw(user)); - if ((!extdata) && (create)) + auto* extdata = GetRaw(user); + if (!extdata) { - extdata = new ExtData; - SetRaw(user, extdata); + if (!create) + return nullptr; + + SetRaw(user, std::make_shared()); + extdata = GetRaw(user); } - return extdata; + return std::static_pointer_cast(*extdata).get(); } void Unset(User* user) { - Delete(user, UnsetRaw(user)); + OnDelete(user, UnsetRaw(user)); } - std::string ToInternal(const Extensible* container, void* item) const noexcept override + std::string ToInternal(const Extensible* container, const ExtensionPtr& item) const noexcept override { std::string ret; - const ExtData* extdata = static_cast(item); + const auto& extdata = std::static_pointer_cast(item); for (const auto* entry : extdata->list) ret.append(entry->GetNick()).push_back(' '); if (!ret.empty()) @@ -100,11 +103,6 @@ class IRCv3::Monitor::Manager final } void FromInternal(Extensible* container, const std::string& value) noexcept override; - - void Delete(Extensible* container, void* item) override - { - delete static_cast(item); - } }; public: diff --git a/modules/silence.cpp b/modules/silence.cpp index 996535673..1ce7d6c67 100644 --- a/modules/silence.cpp +++ b/modules/silence.cpp @@ -224,7 +224,7 @@ public: // Remove the old list and create a new one. Unset(user, false); - SilenceList* list = nullptr; + std::shared_ptr list; StringSplitter ts(value); while (!ts.AtEnd()) @@ -234,7 +234,6 @@ public: { ServerInstance->Logs.Debug(MODNAME, "Oversized silence list received for {}: {}", user->uuid, value); - delete list; return; } @@ -245,7 +244,6 @@ public: { ServerInstance->Logs.Debug(MODNAME, "Malformed silence list received for {}: {}", user->uuid, value); - delete list; return; } @@ -255,13 +253,12 @@ public: { ServerInstance->Logs.Debug(MODNAME, "Malformed silence flags received for {}: {}", user->uuid, flagstr); - delete list; return; } // Store the silence entry. if (!list) - list = new SilenceList(); + list = std::make_shared(); list->emplace(flags, mask); } @@ -270,9 +267,10 @@ public: Set(user, list, false); } - std::string ToInternal(const Extensible* container, void* item) const noexcept override + std::string ToInternal(const Extensible* container, const ExtensionPtr& item) const noexcept override { - auto* list = static_cast(item); + const auto& list = std::static_pointer_cast(item); + std::string buf; for (const auto& entry : *list) { @@ -307,20 +305,14 @@ private: CmdResult AddSilence(LocalUser* user, const std::string& mask, uint32_t flags) { - SilenceList* list = ext.Get(user); - if (list && list->size() > ext.maxsilence) + auto& list = ext.GetRef(user); + if (!list.empty() && list.size() > ext.maxsilence) { user->WriteNumeric(ERR_SILELISTFULL, mask, SilenceEntry::BitsToFlags(flags), "Your SILENCE list is full"); return CmdResult::FAILURE; } - else if (!list) - { - // There is no list; create it. - list = new SilenceList(); - ext.Set(user, list); - } - if (!list->emplace(flags, mask).second) + if (!list.emplace(flags, mask).second) { user->WriteNumeric(ERR_SILENCE, mask, SilenceEntry::BitsToFlags(flags), "The SILENCE entry you specified already exists"); return CmdResult::FAILURE; diff --git a/modules/spanningtree/hmac.cpp b/modules/spanningtree/hmac.cpp index 4a97bddbb..5dc8bd03d 100644 --- a/modules/spanningtree/hmac.cpp +++ b/modules/spanningtree/hmac.cpp @@ -74,7 +74,7 @@ bool TreeSocket::ComparePass(const Link& link, const std::string& theirs) capab->auth_challenge = !capab->ourchallenge.empty() && !capab->theirchallenge.empty(); const auto* sslhook = SSLIOHook::IsSSL(this); - const auto* sslcert = sslhook ? sslhook->GetCertificate() : nullptr; + const auto& sslcert = sslhook ? sslhook->GetCertificate() : nullptr; const auto sslcert_usable = sslcert && sslcert->IsUsable(); const auto fp = sslcert_usable ? sslcert->GetFingerprint() : ""; if (capab->auth_fingerprint) diff --git a/modules/sslinfo.cpp b/modules/sslinfo.cpp index 6916707bf..f1c26baa2 100644 --- a/modules/sslinfo.cpp +++ b/modules/sslinfo.cpp @@ -38,35 +38,14 @@ #include "timeutils.h" class SSLCertExt final - : public ExtensionItem + : public SimpleExtItem { public: - SSLCertExt(Module* parent) - : ExtensionItem(parent, "ssl_cert", ExtensionType::USER) + SSLCertExt(Module* mod) + : SimpleExtItem(mod, "ssl_cert", ExtensionType::USER) { } - ssl_cert* Get(const User* user) const - { - return static_cast(GetRaw(user)); - } - - void Set(User* user, ssl_cert* value, bool sync = true) - { - value->refcount_inc(); - ssl_cert* old = static_cast(SetRaw(user, value)); - if (old && old->refcount_dec()) - delete old; - - if (sync) - Sync(user, value); - } - - void Unset(User* user) - { - Delete(user, UnsetRaw(user)); - } - static std::string GetFlags(const ssl_cert* cert) { std::string ret; @@ -78,16 +57,12 @@ public: return ret; } - std::string ToInternal(const Extensible* container, void* item) const noexcept override + std::string ToInternal(const Extensible* container, const ExtensionPtr& item) const noexcept override { - return ToNetwork(container, item); - } + const auto& cert = std::static_pointer_cast(item); - std::string ToNetwork(const Extensible* container, void* item) const noexcept override - { - const ssl_cert* cert = static_cast(item); std::stringstream value; - value << GetFlags(cert) << " "; + value << GetFlags(cert.get()) << " "; if (cert->GetError().empty()) value << insp::join(cert->GetFingerprints(), ',') << " " << cert->GetDN() << " " << cert->GetIssuer(); @@ -98,17 +73,12 @@ public: } void FromInternal(Extensible* container, const std::string& value) noexcept override - { - FromNetwork(container, value); - } - - void FromNetwork(Extensible* container, const std::string& value) noexcept override { if (container->extype != this->extype) return; - auto* cert = new ssl_cert(); - Set(static_cast(container), cert, false); + auto cert = std::make_shared(); + Set(container, cert, false); std::stringstream s(value); std::string v; @@ -134,13 +104,6 @@ public: getline(s, cert->issuer, '\n'); } } - - void Delete(Extensible* container, void* item) override - { - ssl_cert* old = static_cast(item); - if (old && old->refcount_dec()) - delete old; - } }; class UserCertificateAPIImpl final @@ -172,11 +135,13 @@ public: if (!ssliohook) return nullptr; - cert = ssliohook->GetCertificate(); - if (!cert) - return nullptr; + auto newcert = ssliohook->GetCertificate(); + if (newcert) + { + SetCertificate(user, newcert); + cert = newcert.get(); + } - SetCertificate(user, cert); return cert; } @@ -192,7 +157,7 @@ public: return false; } - void SetCertificate(User* user, ssl_cert* cert) override + void SetCertificate(User* user, const std::shared_ptr& cert) override { ServerInstance->Logs.Debug(MODNAME, "Setting TLS client certificate for {}: {}", user->GetMask(), sslext.ToNetwork(user, cert)); @@ -557,7 +522,7 @@ public: } // Create a fake ssl_cert for the user. - auto* cert = new ssl_cert(); + auto cert = std::make_shared(); cert->dn = "(unknown)"; cert->invalid = false; cert->issuer = "(unknown)"; -- cgit v1.3.1-10-gc9f91