diff options
| author | 2021-11-24 07:17:50 +0000 | |
|---|---|---|
| committer | 2021-11-24 07:17:50 +0000 | |
| commit | 2ec53e10ee5325df12586c72471efabb07cb9d70 (patch) | |
| tree | 7f06c35178be0c65048d9f2ff30b73b2bebb36a5 | |
| parent | Don't allow routing INFO to remote servers. (diff) | |
| parent | Make parsing websocket proxy headers more robust. (diff) | |
Merge branch 'insp3' into master.
| -rw-r--r-- | docs/conf/helpop.conf.example | 9 | ||||
| -rw-r--r-- | docs/conf/providers/irccloud.conf.example | 3 | ||||
| -rw-r--r-- | include/typedefs.h | 2 | ||||
| -rw-r--r-- | include/users.h | 116 | ||||
| -rw-r--r-- | src/configreader.cpp | 1 | ||||
| -rw-r--r-- | src/modules/m_anticaps.cpp | 6 | ||||
| -rw-r--r-- | src/modules/m_banredirect.cpp | 32 | ||||
| -rw-r--r-- | src/modules/m_httpd_stats.cpp | 4 | ||||
| -rw-r--r-- | src/modules/m_messageflood.cpp | 6 | ||||
| -rw-r--r-- | src/modules/m_repeat.cpp | 7 | ||||
| -rw-r--r-- | src/modules/m_timedbans.cpp | 6 | ||||
| -rw-r--r-- | src/modules/m_websocket.cpp | 20 | ||||
| -rw-r--r-- | src/users.cpp | 39 | ||||
| -rwxr-xr-x | tools/mkdescriptions | 12 | ||||
| -rwxr-xr-x | tools/testssl | 4 |
15 files changed, 140 insertions, 127 deletions
diff --git a/docs/conf/helpop.conf.example b/docs/conf/helpop.conf.example index 86a3e6f4e..d133c3bf3 100644 --- a/docs/conf/helpop.conf.example +++ b/docs/conf/helpop.conf.example @@ -51,9 +51,12 @@ match against the nickname of the network service and the oper type of the network service. "> -<helpop key="sslinfo" title="/SSLINFO <nick>" value=" -Displays information on the TLS connection and certificate of the -target user. +<helpop key="sslinfo" title="/SSLINFO <chan>|<nick>" value=" +If a channel is specified then display TLS connection information +for users in the specified channel. + +If a user is specified then display information on the TLS (SSL) +connection and certificate of the specified user. "> <helpop key="uninvite" title="/UNINVITE <nick> <channel>" value=" diff --git a/docs/conf/providers/irccloud.conf.example b/docs/conf/providers/irccloud.conf.example index 92358c8b6..4fbe3da90 100644 --- a/docs/conf/providers/irccloud.conf.example +++ b/docs/conf/providers/irccloud.conf.example @@ -13,7 +13,8 @@ <connect name="IRCCloud (IPv4)" parent="IRCCloud" - allow="5.254.36.56/29 192.184.8.103 192.184.9.108 192.184.9.112 192.184.10.118 192.184.10.9"> + allow="5.254.36.56/29 192.184.8.103 192.184.9.108 192.184.9.112 192.184.10.118 192.184.10.9" + uniqueusername="yes"> # This is not typically needed as each user has their own IPv6 but if you have # <cidr:ipv6clone> set to a value lower than 128 you will need to enable it. diff --git a/include/typedefs.h b/include/typedefs.h index 1685cef7d..3e70b392d 100644 --- a/include/typedefs.h +++ b/include/typedefs.h @@ -49,7 +49,7 @@ class User; class XLine; class XLineManager; class XLineFactory; -struct ConnectClass; +class ConnectClass; class ModResult; namespace ClientProtocol diff --git a/include/users.h b/include/users.h index 6bb72170a..3b7b7e73f 100644 --- a/include/users.h +++ b/include/users.h @@ -65,100 +65,84 @@ enum RegistrationState { REG_ALL = 7 /* REG_NICKUSER plus next bit along */ }; -/** Holds information relevant to <connect allow> and <connect deny> tags in the config file. - */ -struct CoreExport ConnectClass +/** Represents \<connect> class tags from the server config */ +class CoreExport ConnectClass : public refcountbase { + public: + /** The synthesized (with all inheritance applied) config tag this class was read from. */ std::shared_ptr<ConfigTag> config; - /** Type of line, either CC_ALLOW or CC_DENY - */ - char type; - /** True if this class uses fake lag to manage flood, false if it kills */ - bool fakelag = true; + /** The hosts that this user can connect from. */ + std::vector<std::string> hosts; - /** Connect class name - */ + /** The name of this connect class. */ std::string name; - /** Max time to register the connection in seconds - */ - unsigned long registration_timeout = 0; + /** If non-empty then the password a user must specify in PASS to be assigned to this class. */ + std::string password; - /** Hosts that this connect class can be used by. */ - std::vector<std::string> hosts; + /** If non-empty then the hash algorithm that the password field is hashed with. */ + std::string passwordhash; - /** Number of seconds between pings for this line - */ - unsigned long pingtime = 0; + /** If non-empty then the server ports which a user has to be connecting on. */ + insp::flat_set<int> ports; - /** Maximum size of sendq for users in this class (bytes) - * Users cannot send commands if they go over this limit - */ - unsigned long softsendqmax = 0; + /** The type of class this. */ + char type; - /** Maximum size of sendq for users in this class (bytes) - * Users are killed if they go over this limit - */ - unsigned long hardsendqmax = 0; + /** Whether fake lag is used by this class. */ + bool fakelag:1; - /** Maximum size of recvq for users in this class (bytes) - */ - unsigned long recvqmax = 0; + /** Whether to warn server operators about the limit for this class being reached. */ + bool maxconnwarn:1; - /** Seconds worth of penalty before penalty system activates - */ - unsigned long penaltythreshold = 0; + /** Whether the DNS hostnames of users in this class should be resolved. */ + bool resolvehostnames:1; - /** Maximum rate of commands (units: millicommands per second) */ - unsigned long commandrate = 0; + /** Whether this class is for a shared host where the username (ident) uniquely identifies users. */ + bool uniqueusername:1; - /** Local max when connecting by this connection class - */ - unsigned long maxlocal = 0; + /** Maximum rate of commands (units: millicommands per second). */ + unsigned long commandrate = 0; - /** Global max when connecting by this connection class - */ - unsigned long maxglobal = 0; + /** The maximum number of bytes that users in this class can have in their send queue before they are disconnected. */ + unsigned long hardsendqmax = 0; - /** True if max connections for this class is hit and a warning is wanted - */ - bool maxconnwarn = true; + /** The maximum number of users in this class that can connect to the local server from one host. */ + unsigned long limit = 0; - /** Max channels for this class - */ + /** The maximum number of channels that users in this class can join. */ unsigned long maxchans = 20; - /** How many users may be in this connect class before they are refused? - * (0 = no limit = default) - */ - unsigned long limit = 0; + /** The maximum number of users in this class that can connect to the entire network from one host. */ + unsigned long maxglobal = 0; - /** If set to true, no user DNS lookups are to be performed - */ - bool resolvehostnames = true; + /** The maximum number of users that can be in this class on the local server. */ + unsigned long maxlocal = 0; - /** - * If non-empty the server ports which this user has to be using - */ - insp::flat_set<int> ports; + /** The amount of penalty that a user in this class can have before the penalty system activates. */ + unsigned long penaltythreshold = 0; - /** If non-empty then the password a user must specify in PASS to be assigned to this class. */ - std::string password; + /** The number of seconds between keepalive checks for idle clients in this class. */ + unsigned long pingtime = 0; - /** If non-empty then the hash algorithm that the password field is hashed with. */ - std::string passwordhash; + /** The maximum number of bytes that users in this class can have in their receive queue before they are disconnected. */ + unsigned long recvqmax = 0; - /** Create a new connect class with no settings. - */ + /** The number of seconds that connecting users have to register within in this class. */ + unsigned long registration_timeout = 0; + + /** The maximum number of bytes that users in this class can have in their send queue before their commands stop being processed. */ + unsigned long softsendqmax = 0; + + /** Creates a new connect class from a config tag. */ ConnectClass(std::shared_ptr<ConfigTag> tag, char type, const std::vector<std::string>& masks); - /** Create a new connect class with inherited settings. - */ + /** Creates a new connect class with a parent from a config tag. */ ConnectClass(std::shared_ptr<ConfigTag> tag, char type, const std::vector<std::string>& masks, std::shared_ptr<ConnectClass> parent); - /** Update the settings in this block to match the given block */ - void Update(const std::shared_ptr<ConnectClass> newSettings); + /** Update the settings in this block to match the given class */ + void Update(const std::shared_ptr<ConnectClass> klass); /** Retrieves the name of this connect class. */ const std::string& GetName() const { return name; } diff --git a/src/configreader.cpp b/src/configreader.cpp index ed1463147..86329355e 100644 --- a/src/configreader.cpp +++ b/src/configreader.cpp @@ -274,6 +274,7 @@ void ServerConfig::CrossCheckConnectBlocks(ServerConfig* current) me->maxconnwarn = tag->getBool("maxconnwarn", me->maxconnwarn); me->limit = tag->getUInt("limit", me->limit); me->resolvehostnames = tag->getBool("resolvehostnames", me->resolvehostnames); + me->uniqueusername = tag->getBool("uniqueusername", me->uniqueusername); me->password = tag->getString("password", me->password); me->passwordhash = tag->getString("hash", me->passwordhash); diff --git a/src/modules/m_anticaps.cpp b/src/modules/m_anticaps.cpp index 619d87158..9972aca32 100644 --- a/src/modules/m_anticaps.cpp +++ b/src/modules/m_anticaps.cpp @@ -155,6 +155,7 @@ class ModuleAntiCaps final : public Module { private: + ChanModeReference banmode; CheckExemption::EventProvider exemptionprov; std::bitset<UCHAR_MAX + 1> uppercase; std::bitset<UCHAR_MAX + 1> lowercase; @@ -167,8 +168,8 @@ class ModuleAntiCaps final banmask.append(user->GetDisplayedHost()); Modes::ChangeList changelist; - changelist.push_add(ServerInstance->Modes.FindMode('b', MODETYPE_CHANNEL), banmask); - ServerInstance->Modes.Process(ServerInstance->FakeClient, channel, NULL, changelist); + changelist.push_add(*banmode, banmask); + ServerInstance->Modes.Process(ServerInstance->FakeClient, channel, nullptr, changelist); } void InformUser(Channel* channel, User* user, const std::string& message) @@ -179,6 +180,7 @@ class ModuleAntiCaps final public: ModuleAntiCaps() : Module(VF_VENDOR | VF_COMMON, "Adds channel mode B (anticaps) which allows channels to block messages which are excessively capitalised.") + , banmode(this, "ban") , exemptionprov(this) , mode(this) { diff --git a/src/modules/m_banredirect.cpp b/src/modules/m_banredirect.cpp index cfe813b8b..ac86d63c7 100644 --- a/src/modules/m_banredirect.cpp +++ b/src/modules/m_banredirect.cpp @@ -52,13 +52,14 @@ typedef std::vector<BanRedirectEntry> BanRedirectList; class BanRedirect final : public ModeWatcher { - ChanModeReference ban; public: - SimpleExtItem<BanRedirectList> extItem; + ChanModeReference banmode; + SimpleExtItem<BanRedirectList> redirectlist; + BanRedirect(Module* parent) : ModeWatcher(parent, "ban", MODETYPE_CHANNEL) - , ban(parent, "ban") - , extItem(parent, "banredirect", ExtensionItem::EXT_CHANNEL) + , banmode(parent, "ban") + , redirectlist(parent, "banredirect", ExtensionItem::EXT_CHANNEL) { } @@ -85,7 +86,7 @@ class BanRedirect final if (change.param.find('#') == std::string::npos) return true; - ListModeBase* banlm = static_cast<ListModeBase*>(*ban); + ListModeBase* banlm = static_cast<ListModeBase*>(*banmode); unsigned long maxbans = banlm->GetLimit(channel); ListModeBase::ModeList* list = banlm->GetList(channel); if (list && change.adding && maxbans <= list->size()) @@ -184,11 +185,11 @@ class BanRedirect final if (change.adding) { /* It's a properly valid redirecting ban, and we're adding it */ - redirects = extItem.Get(channel); + redirects = redirectlist.Get(channel); if (!redirects) { redirects = new BanRedirectList; - extItem.Set(channel, redirects); + redirectlist.Set(channel, redirects); } else { @@ -215,7 +216,7 @@ class BanRedirect final else { /* Removing a ban, if there's no extensible there are no redirecting bans and we're fine. */ - redirects = extItem.Get(channel); + redirects = redirectlist.Get(channel); if (redirects) { /* But there were, so we need to remove the matching one if there is one */ @@ -228,7 +229,7 @@ class BanRedirect final if(redirects->empty()) { - extItem.Unset(channel); + redirectlist.Unset(channel); } break; @@ -250,7 +251,7 @@ class ModuleBanRedirect final : public Module { private: - BanRedirect re; + BanRedirect banwatcher; bool nofollow = false; ChanModeReference limitmode; ChanModeReference redirectmode; @@ -258,7 +259,7 @@ class ModuleBanRedirect final public: ModuleBanRedirect() : Module(VF_VENDOR | VF_COMMON, "Allows specifying a channel to redirect a banned user to in the ban mask.") - , re(this) + , banwatcher(this) , limitmode(this, "limit") , redirectmode(this, "redirect") { @@ -269,18 +270,17 @@ class ModuleBanRedirect final if (type == ExtensionItem::EXT_CHANNEL) { Channel* chan = static_cast<Channel*>(item); - BanRedirectList* redirects = re.extItem.Get(chan); + BanRedirectList* redirects = banwatcher.redirectlist.Get(chan); if(redirects) { - ModeHandler* ban = ServerInstance->Modes.FindMode('b', MODETYPE_CHANNEL); Modes::ChangeList changelist; for (auto& redirect : *redirects) - changelist.push_remove(ban, redirect.targetchan.insert(0, redirect.banmask)); + changelist.push_remove(*banwatcher.banmode, redirect.targetchan.insert(0, redirect.banmask)); for (const auto& redirect : *redirects) - changelist.push_add(ban, redirect.banmask); + changelist.push_add(*banwatcher.banmode, redirect.banmask); ServerInstance->Modes.Process(ServerInstance->FakeClient, chan, NULL, changelist, ModeParser::MODE_LOCALONLY); } @@ -291,7 +291,7 @@ class ModuleBanRedirect final { if (!override && chan) { - BanRedirectList* redirects = re.extItem.Get(chan); + BanRedirectList* redirects = banwatcher.redirectlist.Get(chan); if (redirects) { diff --git a/src/modules/m_httpd_stats.cpp b/src/modules/m_httpd_stats.cpp index bcbcfba81..5d35d6fdf 100644 --- a/src/modules/m_httpd_stats.cpp +++ b/src/modules/m_httpd_stats.cpp @@ -210,7 +210,7 @@ namespace Stats { data << "<user>"; data << "<nickname>" << u->nick << "</nickname><uuid>" << u->uuid << "</uuid><realhost>" - << u->GetRealHost() << "</realhost><displayhost>" << u->GetDisplayedHost() << "</displayhost><realname>" + << Sanitize(u->GetRealHost()) << "</realhost><displayhost>" << Sanitize(u->GetDisplayedHost()) << "</displayhost><realname>" << Sanitize(u->GetRealName()) << "</realname><server>" << u->server->GetName() << "</server><signon>" << u->signon << "</signon><age>" << u->age << "</age>"; @@ -229,7 +229,7 @@ namespace Stats << lu->GetClass()->GetName() << "</connectclass><lastmsg>" << lu->idle_lastmsg << "</lastmsg>"; - data << "<ipaddress>" << u->GetIPString() << "</ipaddress>"; + data << "<ipaddress>" << Sanitize(u->GetIPString()) << "</ipaddress>"; DumpMeta(data, u); diff --git a/src/modules/m_messageflood.cpp b/src/modules/m_messageflood.cpp index cab7a9879..4149e9dc8 100644 --- a/src/modules/m_messageflood.cpp +++ b/src/modules/m_messageflood.cpp @@ -115,6 +115,7 @@ class ModuleMsgFlood final , public CTCTags::EventListener { private: + ChanModeReference banmode; CheckExemption::EventProvider exemptionprov; MsgFlood mf; double notice; @@ -125,6 +126,7 @@ private: ModuleMsgFlood() : Module(VF_VENDOR, "Adds channel mode f (flood) which helps protect against spammers which mass-message channels.") , CTCTags::EventListener(this) + , banmode(this, "ban") , exemptionprov(this) , mf(this) { @@ -161,8 +163,8 @@ private: if (f->ban) { Modes::ChangeList changelist; - changelist.push_add(ServerInstance->Modes.FindMode('b', MODETYPE_CHANNEL), "*!*@" + user->GetDisplayedHost()); - ServerInstance->Modes.Process(ServerInstance->FakeClient, dest, NULL, changelist); + changelist.push_add(*banmode, "*!*@" + user->GetDisplayedHost()); + ServerInstance->Modes.Process(ServerInstance->FakeClient, dest, nullptr, changelist); } const std::string kickMessage = "Channel flood triggered (trigger is " + ConvToStr(f->lines) + diff --git a/src/modules/m_repeat.cpp b/src/modules/m_repeat.cpp index f88f2552b..8b47d5c4b 100644 --- a/src/modules/m_repeat.cpp +++ b/src/modules/m_repeat.cpp @@ -356,12 +356,15 @@ class RepeatMode final class RepeatModule final : public Module { + private: + ChanModeReference banmode; CheckExemption::EventProvider exemptionprov; RepeatMode rm; public: RepeatModule() : Module(VF_VENDOR | VF_COMMON, "Adds channel mode E (repeat) which helps protect against spammers which spam the same message repeatedly.") + , banmode(this, "ban") , exemptionprov(this) , rm(this) { @@ -404,8 +407,8 @@ class RepeatModule final if (settings->Action == ChannelSettings::ACT_BAN) { Modes::ChangeList changelist; - changelist.push_add(ServerInstance->Modes.FindMode('b', MODETYPE_CHANNEL), "*!*@" + user->GetDisplayedHost()); - ServerInstance->Modes.Process(ServerInstance->FakeClient, chan, NULL, changelist); + changelist.push_add(*banmode, "*!*@" + user->GetDisplayedHost()); + ServerInstance->Modes.Process(ServerInstance->FakeClient, chan, nullptr, changelist); } memb->chan->KickUser(ServerInstance->FakeClient, user, rm.GetKickMessage()); diff --git a/src/modules/m_timedbans.cpp b/src/modules/m_timedbans.cpp index e6a2712d3..65db00b88 100644 --- a/src/modules/m_timedbans.cpp +++ b/src/modules/m_timedbans.cpp @@ -215,12 +215,14 @@ class ModuleTimedBans final : public Module { private: + ChanModeReference banmode; CommandTban cmd; BanWatcher banwatcher; public: ModuleTimedBans() : Module(VF_VENDOR | VF_COMMON, "Adds the /TBAN command which allows channel operators to add bans which will be expired after the specified period.") + , banmode(this, "ban") , cmd(this) , banwatcher(this) { @@ -261,8 +263,8 @@ class ModuleTimedBans final } Modes::ChangeList setban; - setban.push_remove(ServerInstance->Modes.FindMode('b', MODETYPE_CHANNEL), timedban.mask); - ServerInstance->Modes.Process(ServerInstance->FakeClient, timedban.chan, NULL, setban); + setban.push_remove(*banmode, timedban.mask); + ServerInstance->Modes.Process(ServerInstance->FakeClient, timedban.chan, nullptr, setban); } } diff --git a/src/modules/m_websocket.cpp b/src/modules/m_websocket.cpp index d682d77df..5d7b95c36 100644 --- a/src/modules/m_websocket.cpp +++ b/src/modules/m_websocket.cpp @@ -386,15 +386,23 @@ class WebSocketHook final irc::sockets::sockaddrs realsa(luser->client_sa); HTTPHeaderFinder proxyheader; - if (proxyheader.Find(recvq, "X-Real-IP:", 10, reqend) - && irc::sockets::aptosa(proxyheader.ExtractValue(recvq), realsa.port(), realsa)) + if (proxyheader.Find(recvq, "X-Real-IP:", 10, reqend) || proxyheader.Find(recvq, "X-Forwarded-For:", 16, reqend)) { - // Nothing to do here. + // Attempt to parse the proxy HTTP header. + irc::sockets::aptosa(proxyheader.ExtractValue(recvq), realsa.port(), realsa); } - else if (proxyheader.Find(recvq, "X-Forwarded-For:", 16, reqend) - && irc::sockets::aptosa(proxyheader.ExtractValue(recvq), realsa.port(), realsa)) + else { - // Nothing to do here. + // The proxy header is missing. + FailHandshake(sock, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n", "WebSocket: Received a proxied HTTP request that did not send a real IP address header"); + return -1; + } + + if (realsa.family() == AF_UNSPEC) + { + // The proxy header value contains a malformed value. + FailHandshake(sock, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n", "WebSocket: Received a proxied HTTP request that sent a malformed real IP address"); + return -1; } for (const auto& proxyrange : config.proxyranges) diff --git a/src/users.cpp b/src/users.cpp index f96c95a9d..7d91de2b3 100644 --- a/src/users.cpp +++ b/src/users.cpp @@ -1218,9 +1218,13 @@ const std::string& FakeUser::GetFullRealHost() ConnectClass::ConnectClass(std::shared_ptr<ConfigTag> tag, char t, const std::vector<std::string>& masks) : config(tag) - , type(t) - , name("unnamed") , hosts(masks) + , name("unnamed") + , type(t) + , fakelag(true) + , maxconnwarn(true) + , resolvehostnames(true) + , uniqueusername(false) { } @@ -1255,25 +1259,26 @@ ConnectClass::ConnectClass(std::shared_ptr<ConfigTag> tag, char t, const std::ve void ConnectClass::Update(const std::shared_ptr<ConnectClass> src) { + commandrate = src->commandrate; config = src->config; - type = src->type; fakelag = src->fakelag; - name = src->name; - registration_timeout = src->registration_timeout; - hosts = src->hosts; - pingtime = src->pingtime; - softsendqmax = src->softsendqmax; hardsendqmax = src->hardsendqmax; - recvqmax = src->recvqmax; - penaltythreshold = src->penaltythreshold; - commandrate = src->commandrate; - maxlocal = src->maxlocal; - maxglobal = src->maxglobal; - maxconnwarn = src->maxconnwarn; - maxchans = src->maxchans; + hosts = src->hosts; limit = src->limit; - resolvehostnames = src->resolvehostnames; - ports = src->ports; + maxchans = src->maxchans; + maxconnwarn = src->maxconnwarn; + maxglobal = src->maxglobal; + maxlocal = src->maxlocal; + name = src->name; password = src->password; passwordhash = src->passwordhash; + penaltythreshold = src->penaltythreshold; + pingtime = src->pingtime; + ports = src->ports; + recvqmax = src->recvqmax; + registration_timeout = src->registration_timeout; + resolvehostnames = src->resolvehostnames; + softsendqmax = src->softsendqmax; + type = src->type; + uniqueusername = src->uniqueusername; } diff --git a/tools/mkdescriptions b/tools/mkdescriptions index 83e2f0bda..c8ed9bdb4 100755 --- a/tools/mkdescriptions +++ b/tools/mkdescriptions @@ -22,14 +22,14 @@ use v5.26.0; use strict; use warnings FATAL => qw(all); +use CommonMark (); use File::Basename qw(basename dirname); use File::Spec::Functions qw(catdir catfile rel2abs); use FindBin qw($RealDir); use HTML::FormatText (); use HTML::TreeBuilder (); -use Text::Markdown::Hoedown qw(HOEDOWN_HTML_SKIP_HTML markdown); use Text::Sentence qw(split_sentences); -use YAML::Tiny (); +use YAML qw(LoadFile); use lib dirname $RealDir; use make::common; @@ -50,11 +50,11 @@ for my $module (<$root/src/modules/extra/m_*.cpp>, <$root/src/modules/m_*.cpp>, my $docfile = catfile $docdir, "$1.yml"; print_error "unable to find the module documentation at $docfile!" unless -f $docfile; - my $docdata = YAML::Tiny->read($docfile) or print_error "unable to read from $docfile: $!"; - print_error "unable to find the module description in $docfile!" unless $docdata->[0]->{description}; + my ($docdata, undef, undef) = LoadFile($docfile) or print_error "unable to read from $docfile: $!"; + print_error "unable to find the module description in $docfile!" unless $docdata->{description}; - my $docraw = $docdata->[0]->{description} =~ s/^(?:This module )//r; - my $docrendered = markdown ucfirst $docraw, extensions => HOEDOWN_HTML_SKIP_HTML; + my $docraw = $docdata->{description} =~ s/^(?:This module )//r; + my $docrendered = CommonMark->markdown_to_html(ucfirst $docraw); my $docplain = HTML::FormatText->new(leftmargin => 0, rightmargin => ~0)->format(HTML::TreeBuilder->new->parse($docrendered)); my $description = (split_sentences $docplain)[0] =~ s/"/\\"/gr; diff --git a/tools/testssl b/tools/testssl index 8de70c906..947553e98 100755 --- a/tools/testssl +++ b/tools/testssl @@ -139,8 +139,10 @@ ${\CC_RED}no${\CC_RESET} It appears that something is wrong with your server. Make sure that: - - You are not using an old version of GnuTLS, mbedTLS, or OpenSSL which only + * You are not using an old version of GnuTLS, mbedTLS, or OpenSSL which only supports deprecated algorithms like SSLv3. + * If you are using a self-signed certificate (not recommended) that you passed + the `selfsigned` argument to this script. The error provided by the TLS library was: |
