summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/lrstanley/girc
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/lrstanley/girc')
-rw-r--r--vendor/github.com/lrstanley/girc/builtin.go6
-rw-r--r--vendor/github.com/lrstanley/girc/cap.go191
-rw-r--r--vendor/github.com/lrstanley/girc/cap_tags.go13
-rw-r--r--vendor/github.com/lrstanley/girc/client.go89
-rw-r--r--vendor/github.com/lrstanley/girc/conn.go108
-rw-r--r--vendor/github.com/lrstanley/girc/constants.go18
-rw-r--r--vendor/github.com/lrstanley/girc/event.go5
-rw-r--r--vendor/github.com/lrstanley/girc/go.mod2
-rw-r--r--vendor/github.com/lrstanley/girc/handler.go15
-rw-r--r--vendor/github.com/lrstanley/girc/state.go66
10 files changed, 416 insertions, 97 deletions
diff --git a/vendor/github.com/lrstanley/girc/builtin.go b/vendor/github.com/lrstanley/girc/builtin.go
index 7f285fda..778a5c68 100644
--- a/vendor/github.com/lrstanley/girc/builtin.go
+++ b/vendor/github.com/lrstanley/girc/builtin.go
@@ -93,7 +93,11 @@ func handleConnect(c *Client, e Event) {
}
time.Sleep(2 * time.Second)
- c.RunHandlers(&Event{Command: CONNECTED, Params: []string{c.Server()}})
+
+ c.mu.RLock()
+ server := c.server()
+ c.mu.RUnlock()
+ c.RunHandlers(&Event{Command: CONNECTED, Params: []string{server}})
}
// nickCollisionHandler helps prevent the client from having conflicting
diff --git a/vendor/github.com/lrstanley/girc/cap.go b/vendor/github.com/lrstanley/girc/cap.go
index 5995233f..38ff210c 100644
--- a/vendor/github.com/lrstanley/girc/cap.go
+++ b/vendor/github.com/lrstanley/girc/cap.go
@@ -5,7 +5,10 @@
package girc
import (
+ "fmt"
+ "strconv"
"strings"
+ "time"
)
// Something not in the list? Depending on the type of capability, you can
@@ -19,13 +22,20 @@ var possibleCap = map[string][]string{
"chghost": nil,
"extended-join": nil,
"invite-notify": nil,
+ "message-tags": nil,
+ "msgid": nil,
"multi-prefix": nil,
"server-time": nil,
"userhost-in-names": nil,
+ // Supported draft versions, some may be duplicated above, this is for backwards
+ // compatibility.
"draft/message-tags-0.2": nil,
"draft/msgid": nil,
+ // sts, sasl, etc are enabled dynamically/depending on client configuration,
+ // so aren't included on this list.
+
// "echo-message" is supported, but it's not enabled by default. This is
// to prevent unwanted confusion and utilize less traffic if it's not needed.
// echo messages aren't sent to girc.PRIVMSG and girc.NOTICE handlers,
@@ -51,6 +61,17 @@ func possibleCapList(c *Client) map[string][]string {
out["sasl"] = nil
}
+ if !c.Config.DisableSTS && !c.Config.SSL {
+ // If fallback supported, and we failed recently, don't try negotiating STS.
+ // ONLY do this fallback if we're expired (primarily useful during the first
+ // sts negotation).
+ if time.Since(c.state.sts.lastFailed) < 5*time.Minute && !c.Config.DisableSTSFallback {
+ c.debug.Println("skipping strict transport policy negotiation; failed within the last 5 minutes")
+ } else {
+ out["sts"] = nil
+ }
+ }
+
for k := range c.Config.SupportedCaps {
out[k] = c.Config.SupportedCaps[k]
}
@@ -62,8 +83,8 @@ func possibleCapList(c *Client) map[string][]string {
return out
}
-func parseCap(raw string) map[string][]string {
- out := make(map[string][]string)
+func parseCap(raw string) map[string]map[string]string {
+ out := make(map[string]map[string]string)
parts := strings.Split(raw, " ")
var val int
@@ -78,7 +99,16 @@ func parseCap(raw string) map[string][]string {
continue
}
- out[parts[i][:val]] = strings.Split(parts[i][val+1:], ",")
+ out[parts[i][:val]] = make(map[string]string)
+ for _, option := range strings.Split(parts[i][val+1:], ",") {
+ j := strings.Index(option, "=")
+
+ if j < 0 {
+ out[parts[i][:val]][option] = ""
+ } else {
+ out[parts[i][:val]][option[:j]] = option[j+1 : len(option)]
+ }
+ }
}
return out
@@ -88,8 +118,15 @@ func parseCap(raw string) map[string][]string {
// This will lock further registration until we have acknowledged (or denied)
// the capabilities.
func handleCAP(c *Client, e Event) {
- if len(e.Params) >= 2 && (e.Params[1] == CAP_NEW || e.Params[1] == CAP_DEL) {
- c.listCAP()
+ c.state.Lock()
+ defer c.state.Unlock()
+
+ if len(e.Params) >= 2 && e.Params[1] == CAP_DEL {
+ caps := parseCap(e.Last())
+ for cap := range caps {
+ // TODO: test the deletion.
+ delete(c.state.enabledCap, cap)
+ }
return
}
@@ -101,27 +138,26 @@ func handleCAP(c *Client, e Event) {
}
possible := possibleCapList(c)
-
- if len(e.Params) >= 3 && e.Params[1] == CAP_LS {
- c.state.Lock()
-
+ // TODO: test the addition.
+ if len(e.Params) >= 3 && (e.Params[1] == CAP_LS || e.Params[1] == CAP_NEW) {
caps := parseCap(e.Last())
- for k := range caps {
- if _, ok := possible[k]; !ok {
+ for capName := range caps {
+ if _, ok := possible[capName]; !ok {
continue
}
- if len(possible[k]) == 0 || len(caps[k]) == 0 {
- c.state.tmpCap = append(c.state.tmpCap, k)
+ if len(possible[capName]) == 0 || len(caps[capName]) == 0 {
+ c.state.tmpCap[capName] = caps[capName]
continue
}
var contains bool
- for i := 0; i < len(caps[k]); i++ {
- for j := 0; j < len(possible[k]); j++ {
- if caps[k][i] == possible[k][j] {
- // Assume we have a matching split value.
+
+ for capAttr := range caps[capName] {
+ for i := 0; i < len(possible[capName]); i++ {
+ if _, ok := caps[capName][capAttr]; ok {
+ // Assuming we have a matching attribute for the capability.
contains = true
goto checkcontains
}
@@ -133,9 +169,8 @@ func handleCAP(c *Client, e Event) {
continue
}
- c.state.tmpCap = append(c.state.tmpCap, k)
+ c.state.tmpCap[capName] = caps[capName]
}
- c.state.Unlock()
// Indicates if this is a multi-line LS. (3 args means it's the
// last LS).
@@ -147,31 +182,113 @@ func handleCAP(c *Client, e Event) {
}
// Let them know which ones we'd like to enable.
- c.write(&Event{Command: CAP, Params: []string{CAP_REQ, strings.Join(c.state.tmpCap, " ")}})
-
- // Re-initialize the tmpCap, so if we get multiple 'CAP LS' requests
- // due to cap-notify, we can re-evaluate what we can support.
- c.state.Lock()
- c.state.tmpCap = []string{}
- c.state.Unlock()
+ reqKeys := make([]string, len(c.state.tmpCap))
+ i := 0
+ for k := range c.state.tmpCap {
+ reqKeys[i] = k
+ i++
+ }
+ c.write(&Event{Command: CAP, Params: []string{CAP_REQ, strings.Join(reqKeys, " ")}})
}
}
if len(e.Params) == 3 && e.Params[1] == CAP_ACK {
- c.state.Lock()
- c.state.enabledCap = strings.Split(e.Last(), " ")
-
- // Do we need to do sasl auth?
- wantsSASL := false
- for i := 0; i < len(c.state.enabledCap); i++ {
- if c.state.enabledCap[i] == "sasl" {
- wantsSASL = true
- break
+ enabled := strings.Split(e.Last(), " ")
+ for _, cap := range enabled {
+ if val, ok := c.state.tmpCap[cap]; ok {
+ c.state.enabledCap[cap] = val
+ } else {
+ c.state.enabledCap[cap] = nil
+ }
+ }
+
+ // Anything client side that needs to be setup post-capability-acknowledgement,
+ // should be done here.
+
+ // Handle STS, and only if it's something specifically we enabled (client
+ // may choose to disable girc automatic STS, and do it themselves).
+ if sts, sok := c.state.enabledCap["sts"]; sok && !c.Config.DisableSTS {
+ var isError bool
+
+ // Some things are updated in the policy depending on if the current
+ // connection is over tls or not.
+ var hasTLSConnection bool
+ if tlsState, _ := c.TLSConnectionState(); tlsState != nil {
+ hasTLSConnection = true
+ }
+
+ // "This key indicates the port number for making a secure connection.
+ // This key’s value MUST be a single port number. If the client is not
+ // already connected securely to the server at the requested hostname,
+ // it MUST close the insecure connection and reconnect securely on the
+ // stated port.
+ //
+ // To enforce an STS upgrade policy, servers MUST send this key to
+ // insecurely connected clients. Servers MAY send this key to securely
+ // connected clients, but it will be ignored."
+ //
+ // See: https://ircv3.net/specs/extensions/sts#the-port-key
+ if !hasTLSConnection {
+ if port, ok := sts["port"]; ok {
+ c.state.sts.upgradePort, _ = strconv.Atoi(port)
+ if c.state.sts.upgradePort < 21 {
+ isError = true
+ }
+ } else {
+ isError = true
+ }
+ }
+
+ // "This key is used on secure connections to indicate how long clients
+ // MUST continue to use secure connections when connecting to the server
+ // at the requested hostname. The value of this key MUST be given as a
+ // single integer which represents the number of seconds until the persistence
+ // policy expires.
+ //
+ // To enforce an STS persistence policy, servers MUST send this key to
+ // securely connected clients. Servers MAY send this key to all clients,
+ // but insecurely connected clients MUST ignore it."
+ //
+ // See: https://ircv3.net/specs/extensions/sts#the-duration-key
+ if hasTLSConnection {
+ if duration, ok := sts["duration"]; ok {
+ c.state.sts.persistenceDuration, _ = strconv.Atoi(duration)
+ c.state.sts.persistenceReceived = time.Now()
+ } else {
+ isError = true
+ }
+ }
+
+ // See: https://ircv3.net/specs/extensions/sts#the-preload-key
+ if hasTLSConnection {
+ if preload, ok := sts["preload"]; ok {
+ c.state.sts.preload, _ = strconv.ParseBool(preload)
+ }
+ }
+
+ if isError {
+ c.rx <- &Event{Command: ERROR, Params: []string{
+ fmt.Sprintf("closing connection: strict transport policy provided by server is invalid; possible MITM? config: %#v", sts),
+ }}
+ return
+ }
+
+ // Only upgrade if not already upgraded.
+ if !hasTLSConnection {
+ c.state.sts.beginUpgrade = true
+
+ c.RunHandlers(&Event{Command: STS_UPGRADE_INIT})
+ c.debug.Println("strict transport security policy provided by server; closing connection to begin upgrade...")
+ c.Close()
+ return
}
}
- c.state.Unlock()
- if wantsSASL {
+ // Re-initialize the tmpCap, so if we get multiple 'CAP LS' requests
+ // due to cap-notify, we can re-evaluate what we can support.
+ c.state.tmpCap = make(map[string]map[string]string)
+
+ if _, ok := c.state.enabledCap["sasl"]; ok && c.Config.SASL != nil {
c.write(&Event{Command: AUTHENTICATE, Params: []string{c.Config.SASL.Method()}})
// Don't "CAP END", since we want to authenticate.
return
diff --git a/vendor/github.com/lrstanley/girc/cap_tags.go b/vendor/github.com/lrstanley/girc/cap_tags.go
index aff10f69..42599f3a 100644
--- a/vendor/github.com/lrstanley/girc/cap_tags.go
+++ b/vendor/github.com/lrstanley/girc/cap_tags.go
@@ -38,7 +38,7 @@ const (
prefixTagValue byte = '='
prefixUserTag byte = '+'
tagSeparator byte = ';'
- maxTagLength int = 511 // 510 + @ and " " (space), though space usually not included.
+ maxTagLength int = 4094 // 4094 + @ and " " (space) = 4096, though space usually not included.
)
// Tags represents the key-value pairs in IRCv3 message tags. The map contains
@@ -55,6 +55,9 @@ type Tags map[string]string
// @aaa=bbb;ccc;example.com/ddd=eee
// NOT:
// @aaa=bbb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me :Hello
+//
+// Technically, there is a length limit of 4096, but the server should reject
+// tag messages longer than this.
func ParseTags(raw string) (t Tags) {
t = make(Tags)
@@ -79,11 +82,11 @@ func ParseTags(raw string) (t Tags) {
}
// Check if tag key or decoded value are invalid.
- if !validTag(parts[i][:hasValue]) || !validTagValue(tagDecoder.Replace(parts[i][hasValue+1:])) {
- continue
- }
+ // if !validTag(parts[i][:hasValue]) || !validTagValue(tagDecoder.Replace(parts[i][hasValue+1:])) {
+ // continue
+ // }
- t[parts[i][:hasValue]] = parts[i][hasValue+1:]
+ t[parts[i][:hasValue]] = tagDecoder.Replace(parts[i][hasValue+1:])
}
return t
diff --git a/vendor/github.com/lrstanley/girc/client.go b/vendor/github.com/lrstanley/girc/client.go
index 038cc6a7..f8035755 100644
--- a/vendor/github.com/lrstanley/girc/client.go
+++ b/vendor/github.com/lrstanley/girc/client.go
@@ -12,8 +12,11 @@ import (
"io"
"io/ioutil"
"log"
+ "net"
+ "os"
"runtime"
"sort"
+ "strconv"
"strings"
"sync"
"time"
@@ -95,6 +98,16 @@ type Config struct {
// configuration (e.g. to not force hostname checking). This only has an
// affect during the dial process.
SSL bool
+ // DisableSTS disables the use of automatic STS connection upgrades
+ // when the server supports STS. STS can also be disabled using the environment
+ // variable "GIRC_DISABLE_STS=true". As many clients may not propagate options
+ // like this back to the user, this allows to directly disable such automatic
+ // functionality.
+ DisableSTS bool
+ // DisableSTSFallback disables the "fallback" to a non-tls connection if the
+ // strict transport policy expires and the first attempt to reconnect back to
+ // the tls version fails.
+ DisableSTSFallback bool
// TLSConfig is an optional user-supplied tls configuration, used during
// socket creation to the server. SSL must be enabled for this to be used.
// This only has an affect during the dial process.
@@ -146,7 +159,7 @@ type Config struct {
// disableTracking disables all channel and user-level tracking. Useful
// for highly embedded scripts with single purposes. This has an exported
- // method which enables this and ensures prop cleanup, see
+ // method which enables this and ensures proper cleanup, see
// Client.DisableTracking().
disableTracking bool
// HandleNickCollide when set, allows the client to handle nick collisions
@@ -247,19 +260,34 @@ func New(config Config) *Client {
c.Config.PingDelay = 600 * time.Second
}
+ envDebug, _ := strconv.ParseBool(os.Getenv("GIRC_DEBUG"))
if c.Config.Debug == nil {
- c.debug = log.New(ioutil.Discard, "", 0)
+ if envDebug {
+ c.debug = log.New(os.Stderr, "debug:", log.Ltime|log.Lshortfile)
+ } else {
+ c.debug = log.New(ioutil.Discard, "", 0)
+ }
} else {
+ if envDebug {
+ if c.Config.Debug != os.Stdout && c.Config.Debug != os.Stderr {
+ c.Config.Debug = io.MultiWriter(os.Stderr, c.Config.Debug)
+ }
+ }
c.debug = log.New(c.Config.Debug, "debug:", log.Ltime|log.Lshortfile)
c.debug.Print("initializing debugging")
}
+ envDisableSTS, _ := strconv.ParseBool((os.Getenv("GIRC_DISABLE_STS")))
+ if envDisableSTS {
+ c.Config.DisableSTS = envDisableSTS
+ }
+
// Setup the caller.
c.Handlers = newCaller(c.debug)
// Give ourselves a new state.
c.state = &state{}
- c.state.reset()
+ c.state.reset(true)
// Register builtin handlers.
c.registerBuiltins()
@@ -323,6 +351,18 @@ func (c *Client) Close() {
c.mu.RUnlock()
}
+// Quit sends a QUIT message to the server with a given reason to close the
+// connection. Underlying this event being sent, Client.Close() is called as well.
+// This is different than just calling Client.Close() in that it provides a reason
+// as to why the connection was closed (for bots to tell users the bot is restarting,
+// or shutting down, etc).
+//
+// NOTE: servers may delay showing of QUIT reasons, until you've been connected to
+// the server for a certain period of time (e.g. 5 minutes). Keep this in mind.
+func (c *Client) Quit(reason string) {
+ c.Send(&Event{Command: QUIT, Params: []string{reason}})
+}
+
// ErrEvent is an error returned when the server (or library) sends an ERROR
// message response. The string returned contains the trailing text from the
// message.
@@ -400,9 +440,21 @@ func (c *Client) DisableTracking() {
c.registerBuiltins()
}
-// Server returns the string representation of host+port pair for net.Conn.
+// Server returns the string representation of host+port pair for the connection.
func (c *Client) Server() string {
- return fmt.Sprintf("%s:%d", c.Config.Server, c.Config.Port)
+ c.state.Lock()
+ defer c.state.Lock()
+
+ return c.server()
+}
+
+// server returns the string representation of host+port pair for net.Conn, and
+// takes into consideration STS. Must lock state mu first!
+func (c *Client) server() string {
+ if c.state.sts.enabled() {
+ return net.JoinHostPort(c.Config.Server, strconv.Itoa(c.state.sts.upgradePort))
+ }
+ return net.JoinHostPort(c.Config.Server, strconv.Itoa(c.Config.Port))
}
// Lifetime returns the amount of time that has passed since the client was
@@ -688,8 +740,9 @@ func (c *Client) HasCapability(name string) (has bool) {
name = strings.ToLower(name)
c.state.RLock()
- for i := 0; i < len(c.state.enabledCap); i++ {
- if strings.ToLower(c.state.enabledCap[i]) == name {
+ for key := range c.state.enabledCap {
+ key = strings.ToLower(key)
+ if key == name {
has = true
break
}
@@ -713,3 +766,25 @@ func (c *Client) panicIfNotTracking() {
panic(fmt.Sprintf("%s used when tracking is disabled (caller %s:%d)", fn.Name(), file, line))
}
+
+func (c *Client) debugLogEvent(e *Event, dropped bool) {
+ var prefix string
+
+ if dropped {
+ prefix = "dropping event (disconnected):"
+ } else {
+ prefix = ">"
+ }
+
+ if e.Sensitive {
+ c.debug.Printf(prefix, " %s ***redacted***", e.Command)
+ } else {
+ c.debug.Print(prefix, " ", StripRaw(e.String()))
+ }
+
+ if c.Config.Out != nil {
+ if pretty, ok := e.Pretty(); ok {
+ fmt.Fprintln(c.Config.Out, StripRaw(pretty))
+ }
+ }
+}
diff --git a/vendor/github.com/lrstanley/girc/conn.go b/vendor/github.com/lrstanley/girc/conn.go
index d9ec6319..441c3e71 100644
--- a/vendor/github.com/lrstanley/girc/conn.go
+++ b/vendor/github.com/lrstanley/girc/conn.go
@@ -58,7 +58,7 @@ type Dialer interface {
}
// newConn sets up and returns a new connection to the server.
-func newConn(conf Config, dialer Dialer, addr string) (*ircConn, error) {
+func newConn(conf Config, dialer Dialer, addr string, sts *strictTransport) (*ircConn, error) {
if err := conf.isValid(); err != nil {
return nil, err
}
@@ -83,13 +83,29 @@ func newConn(conf Config, dialer Dialer, addr string) (*ircConn, error) {
}
if conn, err = dialer.Dial("tcp", addr); err != nil {
+ if sts.enabled() {
+ err = &ErrSTSUpgradeFailed{Err: err}
+ }
+
+ if sts.expired() && !conf.DisableSTSFallback {
+ sts.lastFailed = time.Now()
+ sts.reset()
+ }
return nil, err
}
- if conf.SSL {
+ if conf.SSL || sts.enabled() {
var tlsConn net.Conn
tlsConn, err = tlsHandshake(conn, conf.TLSConfig, conf.Server, true)
if err != nil {
+ if sts.enabled() {
+ err = &ErrSTSUpgradeFailed{Err: err}
+ }
+
+ if sts.expired() && !conf.DisableSTSFallback {
+ sts.lastFailed = time.Now()
+ sts.reset()
+ }
return nil, err
}
@@ -245,6 +261,7 @@ func (c *Client) MockConnect(conn net.Conn) error {
}
func (c *Client) internalConnect(mock net.Conn, dialer Dialer) error {
+startConn:
// We want to be the only one handling connects/disconnects right now.
c.mu.Lock()
@@ -253,13 +270,20 @@ func (c *Client) internalConnect(mock net.Conn, dialer Dialer) error {
}
// Reset the state.
- c.state.reset()
+ c.state.reset(false)
+
+ addr := c.server()
if mock == nil {
// Validate info, and actually make the connection.
- c.debug.Printf("connecting to %s...", c.Server())
- conn, err := newConn(c.Config, dialer, c.Server())
+ c.debug.Printf("connecting to %s... (sts: %v, config-ssl: %v)", addr, c.state.sts.enabled(), c.Config.SSL)
+ conn, err := newConn(c.Config, dialer, addr, &c.state.sts)
if err != nil {
+ if _, ok := err.(*ErrSTSUpgradeFailed); ok {
+ if !c.state.sts.enabled() {
+ c.RunHandlers(&Event{Command: STS_ERR_FALLBACK})
+ }
+ }
c.mu.Unlock()
return err
}
@@ -312,16 +336,18 @@ func (c *Client) internalConnect(mock net.Conn, dialer Dialer) error {
c.write(&Event{Command: USER, Params: []string{c.Config.User, "*", "*", c.Config.Name}})
// Send a virtual event allowing hooks for successful socket connection.
- c.RunHandlers(&Event{Command: INITIALIZED, Params: []string{c.Server()}})
+ c.RunHandlers(&Event{Command: INITIALIZED, Params: []string{addr}})
// Wait for the first error.
var result error
select {
case <-ctx.Done():
- c.debug.Print("received request to close, beginning clean up")
- c.RunHandlers(&Event{Command: CLOSED, Params: []string{c.Server()}})
+ if !c.state.sts.beginUpgrade {
+ c.debug.Print("received request to close, beginning clean up")
+ }
+ c.RunHandlers(&Event{Command: CLOSED, Params: []string{addr}})
case err := <-errs:
- c.debug.Print("received error, beginning clean up")
+ c.debug.Printf("received error, beginning cleanup: %v", err)
result = err
}
@@ -336,7 +362,7 @@ func (c *Client) internalConnect(mock net.Conn, dialer Dialer) error {
c.conn.mu.Unlock()
c.mu.RUnlock()
- c.RunHandlers(&Event{Command: DISCONNECTED, Params: []string{c.Server()}})
+ c.RunHandlers(&Event{Command: DISCONNECTED, Params: []string{addr}})
// Once we have our error/result, let all other functions know we're done.
c.debug.Print("waiting for all routines to finish")
@@ -350,6 +376,18 @@ func (c *Client) internalConnect(mock net.Conn, dialer Dialer) error {
// clients, not multiple instances of Connect().
c.mu.Lock()
c.conn = nil
+
+ if result == nil {
+ if c.state.sts.beginUpgrade {
+ c.state.sts.beginUpgrade = false
+ c.mu.Unlock()
+ goto startConn
+ }
+
+ if c.state.sts.enabled() {
+ c.state.sts.persistenceReceived = time.Now()
+ }
+ }
c.mu.Unlock()
return result
@@ -392,8 +430,23 @@ func (c *Client) readLoop(ctx context.Context, errs chan error, wg *sync.WaitGro
// Send sends an event to the server. Use Client.RunHandlers() if you are
// simply looking to trigger handlers with an event.
func (c *Client) Send(event *Event) {
+ var delay time.Duration
+
if !c.Config.AllowFlood {
- <-time.After(c.conn.rate(event.Len()))
+ c.mu.RLock()
+
+ // Drop the event early as we're disconnected, this way we don't have to wait
+ // the (potentially long) rate limit delay before dropping.
+ if c.conn == nil {
+ c.debugLogEvent(event, true)
+ c.mu.RUnlock()
+ return
+ }
+
+ c.conn.mu.Lock()
+ delay = c.conn.rate(event.Len())
+ c.conn.mu.Unlock()
+ c.mu.RUnlock()
}
if c.Config.GlobalFormat && len(event.Params) > 0 && event.Params[len(event.Params)-1] != "" &&
@@ -401,12 +454,21 @@ func (c *Client) Send(event *Event) {
event.Params[len(event.Params)-1] = Fmt(event.Params[len(event.Params)-1])
}
+ <-time.After(delay)
c.write(event)
}
// write is the lower level function to write an event. It does not have a
// write-delay when sending events.
func (c *Client) write(event *Event) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ if c.conn == nil {
+ // Drop the event if disconnected.
+ c.debugLogEvent(event, true)
+ return
+ }
c.tx <- event
}
@@ -415,14 +477,10 @@ func (c *Client) write(event *Event) {
func (c *ircConn) rate(chars int) time.Duration {
_time := time.Second + ((time.Duration(chars) * time.Second) / 100)
- c.mu.Lock()
if c.writeDelay += _time - time.Now().Sub(c.lastWrite); c.writeDelay < 0 {
c.writeDelay = 0
}
- c.mu.Unlock()
- c.mu.RLock()
- defer c.mu.RUnlock()
if c.writeDelay > (8 * time.Second) {
return _time
}
@@ -445,7 +503,7 @@ func (c *Client) sendLoop(ctx context.Context, errs chan error, wg *sync.WaitGro
c.state.RLock()
var in bool
for i := 0; i < len(c.state.enabledCap); i++ {
- if c.state.enabledCap[i] == "message-tags" {
+ if _, ok := c.state.enabledCap["message-tags"]; ok {
in = true
break
}
@@ -457,17 +515,7 @@ func (c *Client) sendLoop(ctx context.Context, errs chan error, wg *sync.WaitGro
}
}
- // Log the event.
- if event.Sensitive {
- c.debug.Printf("> %s ***redacted***", event.Command)
- } else {
- c.debug.Print("> ", StripRaw(event.String()))
- }
- if c.Config.Out != nil {
- if pretty, ok := event.Pretty(); ok {
- fmt.Fprintln(c.Config.Out, StripRaw(pretty))
- }
- }
+ c.debugLogEvent(event, false)
c.conn.mu.Lock()
c.conn.lastWrite = time.Now()
@@ -488,6 +536,12 @@ func (c *Client) sendLoop(ctx context.Context, errs chan error, wg *sync.WaitGro
}
}
+ if event.Command == QUIT {
+ c.Close()
+ wg.Done()
+ return
+ }
+
if err != nil {
errs <- err
wg.Done()
diff --git a/vendor/github.com/lrstanley/girc/constants.go b/vendor/github.com/lrstanley/girc/constants.go
index ddea7d0d..a190ef21 100644
--- a/vendor/github.com/lrstanley/girc/constants.go
+++ b/vendor/github.com/lrstanley/girc/constants.go
@@ -21,13 +21,15 @@ const (
// Emulated event commands used to allow easier hooks into the changing
// state of the client.
const (
- UPDATE_STATE = "CLIENT_STATE_UPDATED" // when channel/user state is updated.
- UPDATE_GENERAL = "CLIENT_GENERAL_UPDATED" // when general state (client nick, server name, etc) is updated.
- ALL_EVENTS = "*" // trigger on all events
- CONNECTED = "CLIENT_CONNECTED" // when it's safe to send arbitrary commands (joins, list, who, etc), trailing is host:port
- INITIALIZED = "CLIENT_INIT" // verifies successful socket connection, trailing is host:port
- DISCONNECTED = "CLIENT_DISCONNECTED" // occurs when we're disconnected from the server (user-requested or not)
- CLOSED = "CLIENT_CLOSED" // occurs when Client.Close() has been called
+ UPDATE_STATE = "CLIENT_STATE_UPDATED" // when channel/user state is updated.
+ UPDATE_GENERAL = "CLIENT_GENERAL_UPDATED" // when general state (client nick, server name, etc) is updated.
+ ALL_EVENTS = "*" // trigger on all events
+ CONNECTED = "CLIENT_CONNECTED" // when it's safe to send arbitrary commands (joins, list, who, etc), trailing is host:port
+ INITIALIZED = "CLIENT_INIT" // verifies successful socket connection, trailing is host:port
+ DISCONNECTED = "CLIENT_DISCONNECTED" // occurs when we're disconnected from the server (user-requested or not)
+ CLOSED = "CLIENT_CLOSED" // occurs when Client.Close() has been called
+ STS_UPGRADE_INIT = "STS_UPGRADE_INIT" // when an STS upgrade initially happens.
+ STS_ERR_FALLBACK = "STS_ERR_FALLBACK" // when an STS connection fails and fallbacks are supported.
)
// User/channel prefixes :: RFC1459.
@@ -225,6 +227,7 @@ const (
ERR_NOTOPLEVEL = "413"
ERR_WILDTOPLEVEL = "414"
ERR_BADMASK = "415"
+ ERR_INPUTTOOLONG = "417"
ERR_UNKNOWNCOMMAND = "421"
ERR_NOMOTD = "422"
ERR_NOADMININFO = "423"
@@ -286,6 +289,7 @@ const (
CAP_CHGHOST = "CHGHOST"
CAP_AWAY = "AWAY"
CAP_ACCOUNT = "ACCOUNT"
+ CAP_TAGMSG = "TAGMSG"
)
// Numeric IRC reply mapping for ircv3 :: http://ircv3.net/irc/.
diff --git a/vendor/github.com/lrstanley/girc/event.go b/vendor/github.com/lrstanley/girc/event.go
index d9d22f26..ef4633f7 100644
--- a/vendor/github.com/lrstanley/girc/event.go
+++ b/vendor/github.com/lrstanley/girc/event.go
@@ -49,6 +49,7 @@ func ParseEvent(raw string) (e *Event) {
}
}
raw = raw[i+1:]
+ i = 0
}
if raw[0] == messagePrefix {
@@ -91,7 +92,7 @@ func ParseEvent(raw string) (e *Event) {
if trailerIndex == -1 {
// No trailing argument found, assume the rest is just params.
- e.Params = strings.Split(raw[j:], string(eventSpace))
+ e.Params = strings.Fields(raw[j:])
return e
}
@@ -114,7 +115,7 @@ func ParseEvent(raw string) (e *Event) {
// Check if we need to parse arguments. If so, take everything after the
// command, and right before the trailing prefix, and cut it up.
if i > j {
- e.Params = strings.Split(raw[j:i-1], string(eventSpace))
+ e.Params = strings.Fields(raw[j : i-1])
}
e.Params = append(e.Params, raw[i+1:])
diff --git a/vendor/github.com/lrstanley/girc/go.mod b/vendor/github.com/lrstanley/girc/go.mod
index 57b39ae8..5a4a2aad 100644
--- a/vendor/github.com/lrstanley/girc/go.mod
+++ b/vendor/github.com/lrstanley/girc/go.mod
@@ -1 +1,3 @@
module github.com/lrstanley/girc
+
+go 1.12
diff --git a/vendor/github.com/lrstanley/girc/handler.go b/vendor/github.com/lrstanley/girc/handler.go
index ec717de6..4832262a 100644
--- a/vendor/github.com/lrstanley/girc/handler.go
+++ b/vendor/github.com/lrstanley/girc/handler.go
@@ -431,17 +431,27 @@ func recoverHandlerPanic(client *Client, event *Event, id string, skip int) {
return
}
- var file string
+ var file, function string
var line int
var ok bool
- _, file, line, ok = runtime.Caller(skip)
+ var pcs [10]uintptr
+ frames := runtime.CallersFrames(pcs[:runtime.Callers(skip, pcs[:])])
+ for {
+ frame, _ := frames.Next()
+ file = frame.File
+ line = frame.Line
+ function = frame.Function
+
+ break
+ }
err := &HandlerError{
Event: *event,
ID: id,
File: file,
Line: line,
+ Func: function,
Panic: perr,
Stack: debug.Stack(),
callOk: ok,
@@ -460,6 +470,7 @@ type HandlerError struct {
ID string // ID is the CUID of the handler.
File string // File is the file from where the panic originated.
Line int // Line number where panic originated.
+ Func string // Function name where panic originated.
Panic interface{} // Panic is the error that was passed to panic().
Stack []byte // Stack is the call stack. Note you may have to skip 1 or 2 due to debug functions.
callOk bool
diff --git a/vendor/github.com/lrstanley/girc/state.go b/vendor/github.com/lrstanley/girc/state.go
index 0660a686..d9e72981 100644
--- a/vendor/github.com/lrstanley/girc/state.go
+++ b/vendor/github.com/lrstanley/girc/state.go
@@ -5,6 +5,7 @@
package girc
import (
+ "fmt"
"sort"
"sync"
"time"
@@ -22,27 +23,28 @@ type state struct {
// users represents all of users that we're tracking.
users map[string]*User
// enabledCap are the capabilities which are enabled for this connection.
- enabledCap []string
+ enabledCap map[string]map[string]string
// tmpCap are the capabilties which we share with the server during the
// last capability check. These will get sent once we have received the
// last capability list command from the server.
- tmpCap []string
+ tmpCap map[string]map[string]string
// serverOptions are the standard capabilities and configurations
// supported by the server at connection time. This also includes
// RPL_ISUPPORT entries.
serverOptions map[string]string
// motd is the servers message of the day.
motd string
-}
-// notify sends state change notifications so users can update their refs
-// when state changes.
-func (s *state) notify(c *Client, ntype string) {
- c.RunHandlers(&Event{Command: ntype})
+ // sts are strict transport security configurations, if specified by the
+ // server.
+ //
+ // TODO: ideally, this would be a configurable policy store that the user could
+ // optionally override (to store STS information on disk, memory, etc).
+ sts strictTransport
}
// reset resets the state back to it's original form.
-func (s *state) reset() {
+func (s *state) reset(initial bool) {
s.Lock()
s.nick = ""
s.ident = ""
@@ -50,8 +52,13 @@ func (s *state) reset() {
s.channels = make(map[string]*Channel)
s.users = make(map[string]*User)
s.serverOptions = make(map[string]string)
- s.enabledCap = []string{}
+ s.enabledCap = make(map[string]map[string]string)
+ s.tmpCap = make(map[string]map[string]string)
s.motd = ""
+
+ if initial {
+ s.sts.reset()
+ }
s.Unlock()
}
@@ -500,3 +507,44 @@ func (s *state) renameUser(from, to string) {
}
}
}
+
+type strictTransport struct {
+ beginUpgrade bool
+ upgradePort int
+ persistenceDuration int
+ persistenceReceived time.Time
+ preload bool
+ lastFailed time.Time
+}
+
+func (s *strictTransport) reset() {
+ s.upgradePort = -1
+ s.persistenceDuration = -1
+ s.preload = false
+}
+
+func (s *strictTransport) expired() bool {
+ return int(time.Since(s.persistenceReceived).Seconds()) > s.persistenceDuration
+}
+
+func (s *strictTransport) enabled() bool {
+ return s.upgradePort > 0
+}
+
+// ErrSTSUpgradeFailed is an error that occurs when a connection that was attempted
+// to be upgraded via a strict transport policy, failed. This does not necessarily
+// indicate that STS was to blame, but the underlying connection failed for some
+// reason.
+type ErrSTSUpgradeFailed struct {
+ Err error
+}
+
+func (e ErrSTSUpgradeFailed) Error() string {
+ return fmt.Sprintf("fail to upgrade to secure (sts) connection: %v", e.Err)
+}
+
+// notify sends state change notifications so users can update their refs
+// when state changes.
+func (s *state) notify(c *Client, ntype string) {
+ c.RunHandlers(&Event{Command: ntype})
+}