diff --git a/CHANGELOG.md b/CHANGELOG.md index 228da6c2..7237173b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Changelog All notable changes to Ergo will be documented in this file. +## [2.11.1] - 2022-01-22 + +Ergo 2.11.1 is a bugfix release, fixing a denial-of-service issue in our websocket implementation. We regret the oversight. + +This release includes no changes to the config file format or database file format. + +### Security +* Fixed a denial-of-service issue affecting websocket clients (#2039) + ## [2.11.0] - 2022-12-25 We're pleased to be publishing v2.11.0, a new stable release. This is another bugfix release aimed at improving client compatibility and keeping up with the IRCv3 specification process. diff --git a/default.yaml b/default.yaml index ccd9b246..0f3ac31f 100644 --- a/default.yaml +++ b/default.yaml @@ -414,6 +414,7 @@ accounts: # port: 25 # username: "admin" # password: "hunter2" + # implicit-tls: false # TLS from the first byte, typically on port 465 blacklist-regexes: # - ".*@mailinator.com" timeout: 60s diff --git a/irc/email/email.go b/irc/email/email.go index b86b6b6f..4b0834b7 100644 --- a/irc/email/email.go +++ b/irc/email/email.go @@ -24,10 +24,11 @@ var ( ) type MTAConfig struct { - Server string - Port int - Username string - Password string + Server string + Port int + Username string + Password string + ImplicitTLS bool `yaml:"implicit-tls"` } type MailtoConfig struct { @@ -132,11 +133,13 @@ func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) { var addr string var auth smtp.Auth + var implicitTLS bool if !config.DirectSendingEnabled() { addr = fmt.Sprintf("%s:%d", config.MTAReal.Server, config.MTAReal.Port) if config.MTAReal.Username != "" && config.MTAReal.Password != "" { auth = smtp.PlainAuth("", config.MTAReal.Username, config.MTAReal.Password, config.MTAReal.Server) } + implicitTLS = config.MTAReal.ImplicitTLS } else { idx := strings.IndexByte(recipient, '@') if idx == -1 { @@ -149,5 +152,8 @@ func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) { addr = fmt.Sprintf("%s:smtp", mx) } - return smtp.SendMail(addr, auth, config.HeloDomain, config.Sender, []string{recipient}, msg, config.RequireTLS, config.Timeout) + return smtp.SendMail( + addr, auth, config.HeloDomain, config.Sender, []string{recipient}, msg, + config.RequireTLS, implicitTLS, config.Timeout, + ) } diff --git a/irc/ircconn.go b/irc/ircconn.go index 088909a2..a6f391d3 100644 --- a/irc/ircconn.go +++ b/irc/ircconn.go @@ -128,9 +128,9 @@ func (wc IRCWSConn) WriteLines(buffers [][]byte) (err error) { } func (wc IRCWSConn) ReadLine() (line []byte, err error) { - messageType, line, err := wc.conn.ReadMessage() + _, line, err = wc.conn.ReadMessage() if err == nil { - if messageType == websocket.BinaryMessage && !utf8.Valid(line) { + if !utf8.Valid(line) { return line, errInvalidUtf8 } return line, nil diff --git a/irc/smtp/smtp.go b/irc/smtp/smtp.go index 30f6cc83..f43d23d5 100644 --- a/irc/smtp/smtp.go +++ b/irc/smtp/smtp.go @@ -55,14 +55,17 @@ type Client struct { // Dial returns a new Client connected to an SMTP server at addr. // The addr must include a port, as in "mail.example.com:smtp". -func Dial(addr string, timeout time.Duration) (*Client, error) { +func Dial(addr string, timeout time.Duration, implicitTLS bool) (*Client, error) { var conn net.Conn var err error + dialer := net.Dialer{ + Timeout: timeout, + } start := time.Now() - if timeout == 0 { - conn, err = net.Dial("tcp", addr) + if !implicitTLS { + conn, err = dialer.Dial("tcp", addr) } else { - conn, err = net.DialTimeout("tcp", addr, timeout) + conn, err = tls.DialWithDialer(&dialer, "tcp", addr, nil) } if err != nil { return nil, err @@ -338,7 +341,7 @@ var testHookStartTLS func(*tls.Config) // nil, except for tests // functionality. Higher-level packages exist outside of the standard // library. // XXX: modified in Ergo to add `requireTLS`, `heloDomain`, and `timeout` arguments -func SendMail(addr string, a Auth, heloDomain string, from string, to []string, msg []byte, requireTLS bool, timeout time.Duration) error { +func SendMail(addr string, a Auth, heloDomain string, from string, to []string, msg []byte, requireTLS, implicitTLS bool, timeout time.Duration) error { if err := validateLine(from); err != nil { return err } @@ -347,7 +350,7 @@ func SendMail(addr string, a Auth, heloDomain string, from string, to []string, return err } } - c, err := Dial(addr, timeout) + c, err := Dial(addr, timeout, implicitTLS) if err != nil { return err } @@ -355,23 +358,25 @@ func SendMail(addr string, a Auth, heloDomain string, from string, to []string, if err = c.Hello(heloDomain); err != nil { return err } - if ok, _ := c.Extension("STARTTLS"); ok { - var config *tls.Config - if requireTLS { - config = &tls.Config{ServerName: c.serverName} - } else { - // if TLS isn't a hard requirement, don't verify the certificate either, - // since a MITM attacker could just remove the STARTTLS advertisement - config = &tls.Config{InsecureSkipVerify: true} + if !implicitTLS { + if ok, _ := c.Extension("STARTTLS"); ok { + var config *tls.Config + if requireTLS { + config = &tls.Config{ServerName: c.serverName} + } else { + // if TLS isn't a hard requirement, don't verify the certificate either, + // since a MITM attacker could just remove the STARTTLS advertisement + config = &tls.Config{InsecureSkipVerify: true} + } + if testHookStartTLS != nil { + testHookStartTLS(config) + } + if err = c.StartTLS(config); err != nil { + return err + } + } else if requireTLS { + return errors.New("TLS required, but not negotiated") } - if testHookStartTLS != nil { - testHookStartTLS(config) - } - if err = c.StartTLS(config); err != nil { - return err - } - } else if requireTLS { - return errors.New("TLS required, but not negotiated") } if a != nil && c.ext != nil { if _, ok := c.ext["AUTH"]; !ok { diff --git a/irc/version.go b/irc/version.go index 8f92a7c6..bd220a77 100644 --- a/irc/version.go +++ b/irc/version.go @@ -7,7 +7,7 @@ import "fmt" const ( // SemVer is the semantic version of Ergo. - SemVer = "2.11.0" + SemVer = "2.11.1" ) var ( diff --git a/traditional.yaml b/traditional.yaml index ff609cfa..389d5962 100644 --- a/traditional.yaml +++ b/traditional.yaml @@ -387,6 +387,7 @@ accounts: # port: 25 # username: "admin" # password: "hunter2" + # implicit-tls: false # TLS from the first byte, typically on port 465 blacklist-regexes: # - ".*@mailinator.com" timeout: 60s