From c874d8df171512945bfa3a4d55a5304f744bd0c6 Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Fri, 26 Mar 2021 13:17:34 +0000 Subject: [PATCH 01/15] Update README to add shammash as a contributor. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 75081eb..6d7c8ea 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ Contributions gratefully received from: - [soul9](https://github.com/soul9) - [jakebailey](https://github.com/jakebailey) - [stapelberg](https://github.com/stapelberg) + - [shammash](https://github.com/shammash) And thanks to the following for minor doc/fix PRs: From 58c9607dfbf015b1db117a8a900546771399a550 Mon Sep 17 00:00:00 2001 From: Luca Bigliardi Date: Sat, 27 Mar 2021 11:35:19 +0100 Subject: [PATCH 02/15] Fix connection cleanup when context is canceled Signed-off-by: Luca Bigliardi --- client/connection.go | 8 ++++++-- client/connection_test.go | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/client/connection.go b/client/connection.go index f98bc68..43bcc24 100644 --- a/client/connection.go +++ b/client/connection.go @@ -503,13 +503,17 @@ func (conn *Conn) ping(ctx context.Context) { // It pulls Lines from the input channel and dispatches them to any // handlers that have been registered for that IRC verb. func (conn *Conn) runLoop(ctx context.Context) { - defer conn.wg.Done() for { select { case line := <-conn.in: conn.dispatch(line) case <-ctx.Done(): - // control channel closed, bail out + // control channel closed, trigger Cancel() to clean + // things up properly and bail out + + // We can't defer this, because Close() waits for it. + conn.wg.Done() + conn.Close() return } } diff --git a/client/connection_test.go b/client/connection_test.go index c6a3b95..50d32bc 100644 --- a/client/connection_test.go +++ b/client/connection_test.go @@ -103,6 +103,28 @@ func TestEOF(t *testing.T) { } } +func TestCleanupOnContextDone(t *testing.T) { + c, s := setUp(t) + // Since we're not using tearDown() here, manually call Finish() + defer s.ctrl.Finish() + + // Close() triggers DISCONNECT handler after cleaning up the state + // use this as a proxy to check that Close() was indeed called + dcon := callCheck(t) + c.Handle(DISCONNECTED, dcon) + + // Simulate context cancelation using our cancel func + c.die() + + // Verify that disconnected handler was called + dcon.assertWasCalled("Conn did not call disconnected handlers.") + + // Verify that the connection no longer thinks it's connected + if c.Connected() { + t.Errorf("Conn still thinks it's connected to the server.") + } +} + func TestClientAndStateTracking(t *testing.T) { ctrl := gomock.NewController(t) st := state.NewMockTracker(ctrl) From b1565dba18adb1a21ac3fdaa4671b33b78a46310 Mon Sep 17 00:00:00 2001 From: Stefano Date: Thu, 3 Feb 2022 10:09:10 +0100 Subject: [PATCH 03/15] Add section for "Projects using GoIRC" --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 6d7c8ea..687554d 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,11 @@ and `DisableStateTracking()` respectively. Doing this while connected to an IRC server will probably result in an inconsistent state and a lot of warnings to STDERR ;-) +### Projects using GoIRC + +- [xdcc-cli](https://github.com/ostafen/xdcc-cli): A command line tool for searching and downloading files from the IRC network. + + ### Misc. Sorry the documentation is crap. Use the source, Luke. From 54099b85a38bcdd828615a146ece7dfc0a435eb7 Mon Sep 17 00:00:00 2001 From: Stefano Date: Sun, 6 Mar 2022 23:20:06 +0100 Subject: [PATCH 04/15] Implement feature request #77: Support IRCv3 capability negotiation during registration --- client/commands.go | 22 ++++++- client/commands_test.go | 19 ++++++ client/connection.go | 51 +++++++++++---- client/handlers.go | 139 ++++++++++++++++++++++++++++++++++++++++ client/handlers_test.go | 51 +++++++++++++++ 5 files changed, 268 insertions(+), 14 deletions(-) diff --git a/client/commands.go b/client/commands.go index 101c7d3..dac7059 100644 --- a/client/commands.go +++ b/client/commands.go @@ -84,6 +84,23 @@ func splitMessage(msg string, splitLen int) (msgs []string) { return append(msgs, msg) } +func splitArgs(args []string, maxLen int) []string { + res := make([]string, 0) + + i := 0 + for i < len(args) { + currArg := args[i] + i++ + + for i < len(args) && len(currArg)+len(args[i])+1 < maxLen { + currArg += " " + args[i] + i++ + } + res = append(res, currArg) + } + return res +} + // Raw sends a raw line to the server, should really only be used for // debugging purposes but may well come in handy. func (conn *Conn) Raw(rawline string) { @@ -299,6 +316,9 @@ func (conn *Conn) Cap(subcommmand string, capabilities ...string) { if len(capabilities) == 0 { conn.Raw(CAP + " " + subcommmand) } else { - conn.Raw(CAP + " " + subcommmand + " :" + strings.Join(capabilities, " ")) + cmdPrefix := CAP + " " + subcommmand + " :" + for _, args := range splitArgs(capabilities, defaultSplit-len(cmdPrefix)) { + conn.Raw(cmdPrefix + args) + } } } diff --git a/client/commands_test.go b/client/commands_test.go index 15a8a05..25af371 100644 --- a/client/commands_test.go +++ b/client/commands_test.go @@ -2,6 +2,8 @@ package client import ( "reflect" + "strconv" + "strings" "testing" ) @@ -203,3 +205,20 @@ func TestClientCommands(t *testing.T) { c.VHost("user", "pass") s.nc.Expect("VHOST user pass") } + +func TestSplitCommand(t *testing.T) { + nArgs := 100 + + args := make([]string, 0) + for i := 0; i < nArgs; i++ { + args = append(args, "arg"+strconv.Itoa(i)) + } + + for maxLen := 1; maxLen <= defaultSplit; maxLen *= 2 { + for _, argStr := range splitArgs(args, maxLen) { + if len(argStr) > maxLen && len(strings.Split(argStr, " ")) > 1 { + t.Errorf("maxLen = %d, but len(cmd) = %d", maxLen, len(argStr)) + } + } + } +} diff --git a/client/connection.go b/client/connection.go index 43bcc24..8ed5a84 100644 --- a/client/connection.go +++ b/client/connection.go @@ -45,6 +45,12 @@ type Conn struct { out chan string connected bool + // Capabilities supported by the server + supportedCaps *capSet + + // Capabilites currently enabled + currCaps *capSet + // CancelFunc and WaitGroup for goroutines die context.CancelFunc wg sync.WaitGroup @@ -89,6 +95,12 @@ type Config struct { // Passed through to https://golang.org/pkg/net/#Dialer DualStack bool + // Enable IRCv3 capability negotiation. + EnableCapabilityNegotiation bool + + // A list of capabilities to request to the server during registration. + Capabilites []string + // Replaceable function to customise the 433 handler's new nick. // By default an underscore "_" is appended to the current nick. NewNick func(string) string @@ -125,12 +137,13 @@ type Config struct { // name, but these are optional. func NewConfig(nick string, args ...string) *Config { cfg := &Config{ - Me: &state.Nick{Nick: nick}, - PingFreq: 3 * time.Minute, - NewNick: DefaultNewNick, - Recover: (*Conn).LogPanic, // in dispatch.go - SplitLen: defaultSplit, - Timeout: 60 * time.Second, + Me: &state.Nick{Nick: nick}, + PingFreq: 3 * time.Minute, + NewNick: DefaultNewNick, + Recover: (*Conn).LogPanic, // in dispatch.go + SplitLen: defaultSplit, + Timeout: 60 * time.Second, + EnableCapabilityNegotiation: false, } cfg.Me.Ident = "goirc" if len(args) > 0 && args[0] != "" { @@ -204,13 +217,15 @@ func Client(cfg *Config) *Conn { } conn := &Conn{ - cfg: cfg, - dialer: dialer, - intHandlers: handlerSet(), - fgHandlers: handlerSet(), - bgHandlers: handlerSet(), - stRemovers: make([]Remover, 0, len(stHandlers)), - lastsent: time.Now(), + cfg: cfg, + dialer: dialer, + intHandlers: handlerSet(), + fgHandlers: handlerSet(), + bgHandlers: handlerSet(), + stRemovers: make([]Remover, 0, len(stHandlers)), + lastsent: time.Now(), + supportedCaps: capabilitySet(), + currCaps: capabilitySet(), } conn.addIntHandlers() return conn @@ -289,6 +304,16 @@ func (conn *Conn) DisableStateTracking() { } } +// SupportsCapability returns true if the server supports the given capability. +func (conn *Conn) SupportsCapability(cap string) bool { + return conn.supportedCaps.Has(cap) +} + +// HasCapability returns true if the given capability has been acked by the server during negotiation. +func (conn *Conn) HasCapability(cap string) bool { + return conn.currCaps.Has(cap) +} + // Per-connection state initialisation. func (conn *Conn) initialise() { conn.io = nil diff --git a/client/handlers.go b/client/handlers.go index 24165ad..d2317df 100644 --- a/client/handlers.go +++ b/client/handlers.go @@ -4,7 +4,9 @@ package client // to manage tracking an irc connection etc. import ( + "sort" "strings" + "sync" "time" "github.com/fluffle/goirc/logging" @@ -18,8 +20,13 @@ var intHandlers = map[string]HandlerFunc{ CTCP: (*Conn).h_CTCP, NICK: (*Conn).h_NICK, PING: (*Conn).h_PING, + CAP: (*Conn).h_CAP, + "410": (*Conn).h_410, } +// set up the ircv3 capabilities supported by this client which will be requested by default to the server. +var defaultCaps = []string{} + func (conn *Conn) addIntHandlers() { for n, h := range intHandlers { // internal handlers are essential for the IRC client @@ -35,6 +42,10 @@ func (conn *Conn) h_PING(line *Line) { // Handler for initial registration with server once tcp connection is made. func (conn *Conn) h_REGISTER(line *Line) { + if conn.cfg.EnableCapabilityNegotiation { + conn.Cap(CAP_LS) + } + if conn.cfg.Pass != "" { conn.Pass(conn.cfg.Pass) } @@ -42,6 +53,134 @@ func (conn *Conn) h_REGISTER(line *Line) { conn.User(conn.cfg.Me.Ident, conn.cfg.Me.Name) } +func (conn *Conn) getRequestCapabilities() *capSet { + s := capabilitySet() + + // add capabilites supported by the client + s.Add(defaultCaps...) + + // add capabilites requested by the user + s.Add(conn.cfg.Capabilites...) + + return s +} + +func (conn *Conn) negotiateCapabilities(supportedCaps []string) { + conn.supportedCaps.Add(supportedCaps...) + + reqCaps := conn.getRequestCapabilities() + reqCaps.Intersect(conn.supportedCaps) + + if reqCaps.Size() > 0 { + conn.Cap(CAP_REQ, reqCaps.Slice()...) + } else { + conn.Cap(CAP_END) + } +} + +func (conn *Conn) handleCapAck(caps []string) { + for _, cap := range caps { + conn.currCaps.Add(cap) + } + conn.Cap(CAP_END) +} + +func (conn *Conn) handleCapNak(caps []string) { + conn.Cap(CAP_END) +} + +const ( + CAP_LS = "LS" + CAP_REQ = "REQ" + CAP_ACK = "ACK" + CAP_NAK = "NAK" + CAP_END = "END" +) + +type capSet struct { + caps map[string]bool + mu sync.RWMutex +} + +func capabilitySet() *capSet { + return &capSet{ + caps: make(map[string]bool), + } +} + +func (c *capSet) Add(caps ...string) { + c.mu.Lock() + for _, cap := range caps { + if strings.HasPrefix(cap, "-") { + c.caps[cap[1:]] = false + } else { + c.caps[cap] = true + } + } + c.mu.Unlock() +} + +func (c *capSet) Has(cap string) bool { + c.mu.RLock() + defer c.mu.RUnlock() + return c.caps[cap] +} + +// Intersect computes the intersection of two sets. +func (c *capSet) Intersect(other *capSet) { + c.mu.Lock() + + for cap := range c.caps { + if !other.Has(cap) { + delete(c.caps, cap) + } + } + + c.mu.Unlock() +} + +func (c *capSet) Slice() []string { + c.mu.RLock() + defer c.mu.RUnlock() + + capSlice := make([]string, 0, len(c.caps)) + for cap := range c.caps { + capSlice = append(capSlice, cap) + } + + // make output predictable for testing + sort.Strings(capSlice) + return capSlice +} + +func (c *capSet) Size() int { + c.mu.RLock() + defer c.mu.RUnlock() + return len(c.caps) +} + +// This handler is triggered when an invalid cap command is received by the server. +func (conn *Conn) h_410(line *Line) { + logging.Warn("Invalid cap subcommand: ", line.Args[1]) +} + +// Handler for capability negotiation commands. +// Note that even if multiple CAP_END commands may be sent to the server during negotiation, +// only the first will be considered. +func (conn *Conn) h_CAP(line *Line) { + subcommand := line.Args[1] + + caps := strings.Fields(line.Text()) + switch subcommand { + case CAP_LS: + conn.negotiateCapabilities(caps) + case CAP_ACK: + conn.handleCapAck(caps) + case CAP_NAK: + conn.handleCapNak(caps) + } +} + // Handler to trigger a CONNECTED event on receipt of numeric 001 // : 001 :Welcome message !@ func (conn *Conn) h_001(line *Line) { diff --git a/client/handlers_test.go b/client/handlers_test.go index 171e5c9..1e374d5 100644 --- a/client/handlers_test.go +++ b/client/handlers_test.go @@ -456,3 +456,54 @@ func Test671(t *testing.T) { s.st.EXPECT().GetNick("user2").Return(nil) c.h_671(ParseLine(":irc.server.org 671 test user2 :some ignored text")) } + +func TestCap(t *testing.T) { + c, s := setUp(t) + defer s.tearDown() + + c.Config().EnableCapabilityNegotiation = true + c.Config().Capabilites = []string{"cap1", "cap2", "cap3", "cap4"} + + c.h_REGISTER(&Line{Cmd: REGISTER}) + s.nc.Expect("CAP LS") + s.nc.Expect("NICK test") + s.nc.Expect("USER test 12 * :Testing IRC") + + // Ensure that capabilities not supported by the server are not requested + s.nc.Send("CAP * LS :cap2 cap4") + s.nc.Expect("CAP REQ :cap2 cap4") + + s.nc.Send("CAP * ACK :cap2 cap4") + s.nc.Expect("CAP END") + + for _, cap := range []string{"cap2", "cap4"} { + if !c.SupportsCapability(cap) { + t.Fail() + } + + if !c.HasCapability(cap) { + t.Fail() + } + } + + for _, cap := range []string{"cap1", "cap3"} { + if c.HasCapability(cap) { + t.Fail() + } + } + + // test disable capability after registration + s.c.Cap("REQ", "-cap4") + s.nc.Expect("CAP REQ :-cap4") + + s.nc.Send("CAP * ACK :-cap4") + s.nc.Expect("CAP END") + + if !c.HasCapability("cap2") { + t.Fail() + } + + if c.HasCapability("cap4") { + t.Fail() + } +} From 33c2868b34cc48a130904244a85a6e376be6a25b Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Wed, 23 Mar 2022 09:36:35 +0000 Subject: [PATCH 05/15] Add ostafen to contributors. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 687554d..d9cc432 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ Contributions gratefully received from: - [jakebailey](https://github.com/jakebailey) - [stapelberg](https://github.com/stapelberg) - [shammash](https://github.com/shammash) + - [ostefan](https://github.com/ostafen) And thanks to the following for minor doc/fix PRs: From 5b481cf00ad4aba4e021eb1ffe285ec47b75a554 Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Wed, 23 Mar 2022 09:38:41 +0000 Subject: [PATCH 06/15] Update travis for recent go versions. --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index aaef0bd..0b5d58e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,10 @@ language: go go: - - 1.16.2 - - 1.15.10 + - 1.18 + - 1.17.8 + - 1.16.15 + - 1.15.15 sudo : false From bbbcc9aa5b1d2fc1f5050fcb259e933088d41b85 Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Wed, 23 Mar 2022 09:40:06 +0000 Subject: [PATCH 07/15] Noticed typo as soon as I pushed :-( --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d9cc432..7513db1 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ Contributions gratefully received from: - [jakebailey](https://github.com/jakebailey) - [stapelberg](https://github.com/stapelberg) - [shammash](https://github.com/shammash) - - [ostefan](https://github.com/ostafen) + - [ostafen](https://github.com/ostafen) And thanks to the following for minor doc/fix PRs: From e64b5d47c376f2e3e8a48e1a9698c6861f3a38d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Taavi=20V=C3=A4=C3=A4n=C3=A4nen?= Date: Sun, 30 Oct 2022 12:01:33 +0200 Subject: [PATCH 08/15] Add SASL authentication support This hacks together support for IRCv3.1 SASL. Currently only SASL PLAIN is supported, but it's implemented in a way that adding support for other types should not require too many changes to the current code. --- client/commands.go | 6 +++ client/connection.go | 38 ++++++++++------ client/handlers.go | 106 +++++++++++++++++++++++++++++++++++++++---- client/sasl_test.go | 103 +++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 3 +- 6 files changed, 234 insertions(+), 23 deletions(-) create mode 100644 client/sasl_test.go diff --git a/client/commands.go b/client/commands.go index dac7059..f5d4c58 100644 --- a/client/commands.go +++ b/client/commands.go @@ -10,6 +10,7 @@ const ( CONNECTED = "CONNECTED" DISCONNECTED = "DISCONNECTED" ACTION = "ACTION" + AUTHENTICATE = "AUTHENTICATE" AWAY = "AWAY" CAP = "CAP" CTCP = "CTCP" @@ -322,3 +323,8 @@ func (conn *Conn) Cap(subcommmand string, capabilities ...string) { } } } + +// Authenticate send an AUTHENTICATE command to the server. +func (conn *Conn) Authenticate(message string) { + conn.Raw(AUTHENTICATE + " " + message) +} diff --git a/client/connection.go b/client/connection.go index 8ed5a84..aa9f2f5 100644 --- a/client/connection.go +++ b/client/connection.go @@ -13,6 +13,7 @@ import ( "sync" "time" + "github.com/emersion/go-sasl" "github.com/fluffle/goirc/logging" "github.com/fluffle/goirc/state" "golang.org/x/net/proxy" @@ -51,6 +52,9 @@ type Conn struct { // Capabilites currently enabled currCaps *capSet + // SASL internals + saslRemainingData []byte + // CancelFunc and WaitGroup for goroutines die context.CancelFunc wg sync.WaitGroup @@ -101,6 +105,9 @@ type Config struct { // A list of capabilities to request to the server during registration. Capabilites []string + // SASL configuration to use to authenticate the connection. + Sasl sasl.Client + // Replaceable function to customise the 433 handler's new nick. // By default an underscore "_" is appended to the current nick. NewNick func(string) string @@ -216,16 +223,22 @@ func Client(cfg *Config) *Conn { } } + if cfg.Sasl != nil && !cfg.EnableCapabilityNegotiation { + logging.Warn("Enabling capability negotiation as it's required for SASL") + cfg.EnableCapabilityNegotiation = true + } + conn := &Conn{ - cfg: cfg, - dialer: dialer, - intHandlers: handlerSet(), - fgHandlers: handlerSet(), - bgHandlers: handlerSet(), - stRemovers: make([]Remover, 0, len(stHandlers)), - lastsent: time.Now(), - supportedCaps: capabilitySet(), - currCaps: capabilitySet(), + cfg: cfg, + dialer: dialer, + intHandlers: handlerSet(), + fgHandlers: handlerSet(), + bgHandlers: handlerSet(), + stRemovers: make([]Remover, 0, len(stHandlers)), + lastsent: time.Now(), + supportedCaps: capabilitySet(), + currCaps: capabilitySet(), + saslRemainingData: nil, } conn.addIntHandlers() return conn @@ -245,10 +258,9 @@ func (conn *Conn) Connected() bool { // affect client behaviour. To disable flood protection temporarily, // for example, a handler could do: // -// conn.Config().Flood = true -// // Send many lines to the IRC server, risking "excess flood" -// conn.Config().Flood = false -// +// conn.Config().Flood = true +// // Send many lines to the IRC server, risking "excess flood" +// conn.Config().Flood = false func (conn *Conn) Config() *Config { return conn.cfg } diff --git a/client/handlers.go b/client/handlers.go index d2317df..3920c02 100644 --- a/client/handlers.go +++ b/client/handlers.go @@ -9,19 +9,27 @@ import ( "sync" "time" + "encoding/base64" "github.com/fluffle/goirc/logging" ) +// saslCap is the IRCv3 capability used for SASL authentication. +const saslCap = "sasl" + // sets up the internal event handlers to do essential IRC protocol things var intHandlers = map[string]HandlerFunc{ - REGISTER: (*Conn).h_REGISTER, - "001": (*Conn).h_001, - "433": (*Conn).h_433, - CTCP: (*Conn).h_CTCP, - NICK: (*Conn).h_NICK, - PING: (*Conn).h_PING, - CAP: (*Conn).h_CAP, - "410": (*Conn).h_410, + REGISTER: (*Conn).h_REGISTER, + "001": (*Conn).h_001, + "433": (*Conn).h_433, + CTCP: (*Conn).h_CTCP, + NICK: (*Conn).h_NICK, + PING: (*Conn).h_PING, + CAP: (*Conn).h_CAP, + "410": (*Conn).h_410, + AUTHENTICATE: (*Conn).h_AUTHENTICATE, + "903": (*Conn).h_903, + "904": (*Conn).h_904, + "908": (*Conn).h_908, } // set up the ircv3 capabilities supported by this client which will be requested by default to the server. @@ -59,6 +67,11 @@ func (conn *Conn) getRequestCapabilities() *capSet { // add capabilites supported by the client s.Add(defaultCaps...) + if conn.cfg.Sasl != nil { + // add the SASL cap if enabled + s.Add(saslCap) + } + // add capabilites requested by the user s.Add(conn.cfg.Capabilites...) @@ -79,10 +92,31 @@ func (conn *Conn) negotiateCapabilities(supportedCaps []string) { } func (conn *Conn) handleCapAck(caps []string) { + gotSasl := false for _, cap := range caps { conn.currCaps.Add(cap) + + if conn.cfg.Sasl != nil && cap == saslCap { + mech, ir, err := conn.cfg.Sasl.Start() + + if err != nil { + logging.Warn("SASL authentication failed: %v", err) + continue + } + + // TODO: when IRC 3.2 capability negotiation is supported, ensure the + // capability value is used to match the chosen mechanism + + gotSasl = true + conn.saslRemainingData = ir + + conn.Authenticate(mech) + } + } + + if !gotSasl { + conn.Cap(CAP_END) } - conn.Cap(CAP_END) } func (conn *Conn) handleCapNak(caps []string) { @@ -181,6 +215,60 @@ func (conn *Conn) h_CAP(line *Line) { } } +// Handler for SASL authentication +func (conn *Conn) h_AUTHENTICATE(line *Line) { + if conn.cfg.Sasl == nil { + return + } + + if conn.saslRemainingData != nil { + data := "+" // plus sign representing empty data + if len(conn.saslRemainingData) > 0 { + data = base64.StdEncoding.EncodeToString(conn.saslRemainingData) + } + + // TODO: batch data into chunks of 400 bytes per the spec + + conn.Authenticate(data) + conn.saslRemainingData = nil + return + } + + // TODO: handle data over 400 bytes long (which will be chunked into multiple messages per the spec) + challenge, err := base64.StdEncoding.DecodeString(line.Args[0]) + if err != nil { + logging.Error("Failed to decode SASL challenge: %v", err) + return + } + + response, err := conn.cfg.Sasl.Next(challenge) + if err != nil { + logging.Error("Failed to generate response for SASL challenge: %v", err) + return + } + + // TODO: batch data into chunks of 400 bytes per the spec + data := base64.StdEncoding.EncodeToString(response) + conn.Authenticate(data) +} + +// Handler for RPL_SASLSUCCESS. +func (conn *Conn) h_903(line *Line) { + conn.Cap(CAP_END) +} + +// Handler for RPL_SASLFAILURE. +func (conn *Conn) h_904(line *Line) { + logging.Warn("SASL authentication failed") + conn.Cap(CAP_END) +} + +// Handler for RPL_SASLMECHS. +func (conn *Conn) h_908(line *Line) { + logging.Warn("SASL mechanism not supported, supported mechanisms are: %v", line.Args[1]) + conn.Cap(CAP_END) +} + // Handler to trigger a CONNECTED event on receipt of numeric 001 // : 001 :Welcome message !@ func (conn *Conn) h_001(line *Line) { diff --git a/client/sasl_test.go b/client/sasl_test.go new file mode 100644 index 0000000..073a074 --- /dev/null +++ b/client/sasl_test.go @@ -0,0 +1,103 @@ +package client + +import ( + "github.com/emersion/go-sasl" + "testing" +) + +func TestSaslPlainSuccessWorkflow(t *testing.T) { + c, s := setUp(t) + defer s.tearDown() + + c.Config().Sasl = sasl.NewPlainClient("", "example", "password") + c.Config().EnableCapabilityNegotiation = true + + c.h_REGISTER(&Line{Cmd: REGISTER}) + s.nc.Expect("CAP LS") + s.nc.Expect("NICK test") + s.nc.Expect("USER test 12 * :Testing IRC") + s.nc.Send("CAP * LS :sasl foobar") + s.nc.Expect("CAP REQ :sasl") + s.nc.Send("CAP * ACK :sasl") + s.nc.Expect("AUTHENTICATE PLAIN") + s.nc.Send("AUTHENTICATE +") + s.nc.Expect("AUTHENTICATE AGV4YW1wbGUAcGFzc3dvcmQ=") + s.nc.Send("904 test :SASL authentication successful") + s.nc.Expect("CAP END") +} + +func TestSaslPlainWrongPassword(t *testing.T) { + c, s := setUp(t) + defer s.tearDown() + + c.Config().Sasl = sasl.NewPlainClient("", "example", "password") + c.Config().EnableCapabilityNegotiation = true + + c.h_REGISTER(&Line{Cmd: REGISTER}) + s.nc.Expect("CAP LS") + s.nc.Expect("NICK test") + s.nc.Expect("USER test 12 * :Testing IRC") + s.nc.Send("CAP * LS :sasl foobar") + s.nc.Expect("CAP REQ :sasl") + s.nc.Send("CAP * ACK :sasl") + s.nc.Expect("AUTHENTICATE PLAIN") + s.nc.Send("AUTHENTICATE +") + s.nc.Expect("AUTHENTICATE AGV4YW1wbGUAcGFzc3dvcmQ=") + s.nc.Send("904 test :SASL authentication failed") + s.nc.Expect("CAP END") +} + +func TestSaslExternalSuccessWorkflow(t *testing.T) { + c, s := setUp(t) + defer s.tearDown() + + c.Config().Sasl = sasl.NewExternalClient("") + c.Config().EnableCapabilityNegotiation = true + + c.h_REGISTER(&Line{Cmd: REGISTER}) + s.nc.Expect("CAP LS") + s.nc.Expect("NICK test") + s.nc.Expect("USER test 12 * :Testing IRC") + s.nc.Send("CAP * LS :sasl foobar") + s.nc.Expect("CAP REQ :sasl") + s.nc.Send("CAP * ACK :sasl") + s.nc.Expect("AUTHENTICATE EXTERNAL") + s.nc.Send("AUTHENTICATE +") + s.nc.Expect("AUTHENTICATE +") + s.nc.Send("904 test :SASL authentication successful") + s.nc.Expect("CAP END") +} + +func TestSaslNoSaslCap(t *testing.T) { + c, s := setUp(t) + defer s.tearDown() + + c.Config().Sasl = sasl.NewPlainClient("", "example", "password") + c.Config().EnableCapabilityNegotiation = true + + c.h_REGISTER(&Line{Cmd: REGISTER}) + s.nc.Expect("CAP LS") + s.nc.Expect("NICK test") + s.nc.Expect("USER test 12 * :Testing IRC") + s.nc.Send("CAP * LS :foobar") + s.nc.Expect("CAP END") +} + +func TestSaslUnsupportedMechanism(t *testing.T) { + c, s := setUp(t) + defer s.tearDown() + + c.Config().Sasl = sasl.NewPlainClient("", "example", "password") + c.Config().EnableCapabilityNegotiation = true + + c.h_REGISTER(&Line{Cmd: REGISTER}) + s.nc.Expect("CAP LS") + s.nc.Expect("NICK test") + s.nc.Expect("USER test 12 * :Testing IRC") + s.nc.Send("CAP * LS :sasl foobar") + s.nc.Expect("CAP REQ :sasl") + s.nc.Send("CAP * ACK :sasl") + s.nc.Expect("AUTHENTICATE PLAIN") + s.nc.Send("908 test external :are available SASL mechanisms") + s.nc.Expect("CAP END") +} diff --git a/go.mod b/go.mod index c9d6b80..5c388bb 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/fluffle/goirc require ( + github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead github.com/golang/mock v1.5.0 golang.org/x/net v0.0.0-20210119194325-5f4716e94777 ) diff --git a/go.sum b/go.sum index 4a29870..d8b7012 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,10 @@ +github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y= +github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= From 2b7abdce8f4af306fd9e69398868fe1f4d8016f4 Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Mon, 28 Nov 2022 13:40:43 +0000 Subject: [PATCH 09/15] Add @supertassu to README, update copyright dates. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7513db1..5c1108c 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ indebted to Matt Gruen for his work on the re-organisation and channel-based communication structure of `*Conn.send()` and `*Conn.recv()`. I'm sure things could be more asynchronous, still. -This code is (c) 2009-15 Alex Bramley, and released under the same licence terms +This code is (c) 2009-23 Alex Bramley, and released under the same licence terms as Go itself. Contributions gratefully received from: @@ -116,6 +116,7 @@ Contributions gratefully received from: - [stapelberg](https://github.com/stapelberg) - [shammash](https://github.com/shammash) - [ostafen](https://github.com/ostafen) + - [supertassu](https://github.com/supertassu) And thanks to the following for minor doc/fix PRs: From 8b460bc60f91860f9a78e571b55cc7df557e40e7 Mon Sep 17 00:00:00 2001 From: Kobe Housen Date: Fri, 10 Nov 2023 15:55:58 +0000 Subject: [PATCH 10/15] Use make size hint when copying channels in Nick() 70% faster when using a high number of channels Old performance ``` $ go test ./state -bench=. goos: linux goarch: amd64 pkg: github.com/fluffle/goirc/state cpu: AMD Ryzen Threadripper PRO 3945WX 12-Cores BenchmarkNickSingleChan-24 15465226 76.35 ns/op BenchmarkNickManyChan-24 5738 195721 ns/op PASS ok github.com/fluffle/goirc/state 2.433s ``` new performance ``` $ go test ./state -bench=. goos: linux goarch: amd64 pkg: github.com/fluffle/goirc/state cpu: AMD Ryzen Threadripper PRO 3945WX 12-Cores BenchmarkNickSingleChan-24 15022640 79.13 ns/op BenchmarkNickManyChan-24 10000 115104 ns/op PASS ok github.com/fluffle/goirc/state 2.456s ``` --- state/nick.go | 4 +++- state/nick_test.go | 32 +++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/state/nick.go b/state/nick.go index b29d98a..24a03bd 100644 --- a/state/nick.go +++ b/state/nick.go @@ -71,7 +71,7 @@ func (nk *nick) Nick() *Nick { Host: nk.host, Name: nk.name, Modes: nk.modes.Copy(), - Channels: make(map[string]*ChanPrivs), + Channels: make(map[string]*ChanPrivs, len(nk.chans)), } for c, cp := range nk.chans { n.Channels[c.name] = cp.Copy() @@ -157,6 +157,7 @@ func (nm *NickMode) Equals(other *NickMode) bool { } // Returns a string representing the nick. Looks like: +// // Nick: e.g. CowMaster // Hostmask: e.g. moo@cows.org // Real Name: e.g. Steve "CowMaster" Bush @@ -181,6 +182,7 @@ func (nk *nick) String() string { } // Returns a string representing the nick modes. Looks like: +// // +iwx func (nm *NickMode) String() string { if nm == nil { diff --git a/state/nick_test.go b/state/nick_test.go index 1344400..717c8a0 100644 --- a/state/nick_test.go +++ b/state/nick_test.go @@ -1,6 +1,9 @@ package state -import "testing" +import ( + "fmt" + "testing" +) func compareNick(t *testing.T, nk *nick) { n := nk.Nick() @@ -86,3 +89,30 @@ func TestNickParseModes(t *testing.T) { t.Errorf("Modes not flipped correctly by ParseModes.") } } + +func BenchmarkNickSingleChan(b *testing.B) { + nk := newNick("test1") + ch := newChannel("#test1") + cp := new(ChanPrivs) + nk.addChannel(ch, cp) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + nk.Nick() + } +} + +func BenchmarkNickManyChan(b *testing.B) { + const numChans = 1000 + nk := newNick("test1") + for i := 0; i < numChans; i++ { + ch := newChannel(fmt.Sprintf("#test%d", i)) + cp := new(ChanPrivs) + nk.addChannel(ch, cp) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + nk.Nick() + } +} From ceced391f3c7fae6d5b458c4ae102748e7a78c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Solymos?= <15968382+slymas@users.noreply.github.com> Date: Wed, 22 Nov 2023 17:24:19 +0000 Subject: [PATCH 11/15] Allow non-context Dialers Dialers who don't implement DialContext but upgrade past https://github.com/fluffle/goirc/pull/109 (which is 2.5 years old but still) will break. This change falls back to the non-context dial method, where calls may take longer and time out, and goirc would give them a warning as well. --- client/connection.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/client/connection.go b/client/connection.go index aa9f2f5..5ac0918 100644 --- a/client/connection.go +++ b/client/connection.go @@ -413,15 +413,24 @@ func (conn *Conn) internalConnect(ctx context.Context) error { return err } contextProxyDialer, ok := proxyDialer.(proxy.ContextDialer) - if !ok { - return errors.New("Dialer for proxy does not support context") - } - conn.proxyDialer = contextProxyDialer - logging.Info("irc.Connect(): Connecting to %s.", conn.cfg.Server) - if s, err := conn.proxyDialer.DialContext(ctx, "tcp", conn.cfg.Server); err == nil { - conn.sock = s + if ok { + conn.proxyDialer = contextProxyDialer + logging.Info("irc.Connect(): Connecting to %s.", conn.cfg.Server) + if s, err := conn.proxyDialer.DialContext(ctx, "tcp", conn.cfg.Server); err == nil { + conn.sock = s + } else { + return err + } } else { - return err + logging.Warn("Dialer for proxy does not support context, please implement DialContext") + + conn.proxyDialer = proxyDialer + logging.Info("irc.Connect(): Connecting to %s.", conn.cfg.Server) + if s, err := conn.proxyDialer.Dial("tcp", conn.cfg.Server); err == nil { + conn.sock = s + } else { + return err + } } } else { logging.Info("irc.Connect(): Connecting to %s.", conn.cfg.Server) From d655f8950c49f55c15d35a5cf9273ce5deb23a3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Feb 2023 02:17:21 +0000 Subject: [PATCH 12/15] Bump golang.org/x/net from 0.0.0-20210119194325-5f4716e94777 to 0.7.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20210119194325-5f4716e94777 to 0.7.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/commits/v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 4 +++- go.sum | 25 +++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5c388bb..40f2769 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,10 @@ module github.com/fluffle/goirc require ( github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead + github.com/fluffle/golog/logging v0.0.0-20180928190033-7d99e85061cb + github.com/golang/glog v1.0.0 github.com/golang/mock v1.5.0 - golang.org/x/net v0.0.0-20210119194325-5f4716e94777 + golang.org/x/net v0.7.0 ) go 1.13 diff --git a/go.sum b/go.sum index d8b7012..9814963 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,43 @@ github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y= github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= +github.com/fluffle/golog/logging v0.0.0-20180928190033-7d99e85061cb h1:r//eMD/5sdzxVy34UP5fFvRWIL2L8QZtaDgVCKfVQLI= +github.com/fluffle/golog/logging v0.0.0-20180928190033-7d99e85061cb/go.mod h1:w8+az2+kPHMcsaKnTnGapWTNToJK8BogkHiAncvqKsM= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From b1a6e3a286263c22f0a69b3fb4cdb8f57fd50161 Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Thu, 23 Nov 2023 20:44:32 +0000 Subject: [PATCH 13/15] Refactor out proxy dialing code; store contextless proxy.Dialer. --- client/connection.go | 56 ++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/client/connection.go b/client/connection.go index 5ac0918..534ea2a 100644 --- a/client/connection.go +++ b/client/connection.go @@ -4,7 +4,6 @@ import ( "bufio" "context" "crypto/tls" - "errors" "fmt" "io" "net" @@ -13,7 +12,7 @@ import ( "sync" "time" - "github.com/emersion/go-sasl" + sasl "github.com/emersion/go-sasl" "github.com/fluffle/goirc/logging" "github.com/fluffle/goirc/state" "golang.org/x/net/proxy" @@ -39,7 +38,7 @@ type Conn struct { // I/O stuff to server dialer *net.Dialer - proxyDialer proxy.ContextDialer + proxyDialer proxy.Dialer sock net.Conn io *bufio.ReadWriter in chan *Line @@ -404,34 +403,13 @@ func (conn *Conn) internalConnect(ctx context.Context) error { } if conn.cfg.Proxy != "" { - proxyURL, err := url.Parse(conn.cfg.Proxy) + s, err := conn.dialProxy(ctx) if err != nil { + logging.Info("irc.Connect(): Connecting via proxy %q: %v", + conn.cfg.Proxy, err) return err } - proxyDialer, err := proxy.FromURL(proxyURL, conn.dialer) - if err != nil { - return err - } - contextProxyDialer, ok := proxyDialer.(proxy.ContextDialer) - if ok { - conn.proxyDialer = contextProxyDialer - logging.Info("irc.Connect(): Connecting to %s.", conn.cfg.Server) - if s, err := conn.proxyDialer.DialContext(ctx, "tcp", conn.cfg.Server); err == nil { - conn.sock = s - } else { - return err - } - } else { - logging.Warn("Dialer for proxy does not support context, please implement DialContext") - - conn.proxyDialer = proxyDialer - logging.Info("irc.Connect(): Connecting to %s.", conn.cfg.Server) - if s, err := conn.proxyDialer.Dial("tcp", conn.cfg.Server); err == nil { - conn.sock = s - } else { - return err - } - } + conn.sock = s } else { logging.Info("irc.Connect(): Connecting to %s.", conn.cfg.Server) if s, err := conn.dialer.DialContext(ctx, "tcp", conn.cfg.Server); err == nil { @@ -455,6 +433,28 @@ func (conn *Conn) internalConnect(ctx context.Context) error { return nil } +// dialProxy handles dialling via a proxy +func (conn *Conn) dialProxy(ctx context.Context) (net.Conn, error) { + proxyURL, err := url.Parse(conn.cfg.Proxy) + if err != nil { + return nil, fmt.Errorf("parsing url: %v", err) + } + proxyDialer, err := proxy.FromURL(proxyURL, conn.dialer) + if err != nil { + return nil, fmt.Errorf("creating dialer: %v", err) + } + conn.proxyDialer = proxyDialer + contextProxyDialer, ok := proxyDialer.(proxy.ContextDialer) + if ok { + logging.Info("irc.Connect(): Connecting to %s.", conn.cfg.Server) + return contextProxyDialer.DialContext(ctx, "tcp", conn.cfg.Server) + } else { + logging.Warn("Dialer for proxy does not support context, please implement DialContext") + logging.Info("irc.Connect(): Connecting to %s.", conn.cfg.Server) + return conn.proxyDialer.Dial("tcp", conn.cfg.Server) + } +} + // postConnect performs post-connection setup, for ease of testing. func (conn *Conn) postConnect(ctx context.Context, start bool) { conn.io = bufio.NewReadWriter( From 8f19c2305000ef6735826376e52f0252ca46cb7e Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Thu, 23 Nov 2023 20:48:11 +0000 Subject: [PATCH 14/15] bump x/net again, dependabot version was still ancient --- go.mod | 2 +- go.sum | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 40f2769..c87e5fb 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ require ( github.com/fluffle/golog/logging v0.0.0-20180928190033-7d99e85061cb github.com/golang/glog v1.0.0 github.com/golang/mock v1.5.0 - golang.org/x/net v0.7.0 + golang.org/x/net v0.18.0 ) go 1.13 diff --git a/go.sum b/go.sum index 9814963..6b12740 100644 --- a/go.sum +++ b/go.sum @@ -11,17 +11,24 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -29,15 +36,22 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 40e594abf8c403d8b33da0b1d2696e459a126980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Solymos?= <15968382+slymas@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:34:37 +0000 Subject: [PATCH 15/15] Fix NewNick's comment It wasn't updated after fixing https://github.com/fluffle/goirc/issues/108. --- client/connection.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/connection.go b/client/connection.go index 534ea2a..8087a38 100644 --- a/client/connection.go +++ b/client/connection.go @@ -108,7 +108,8 @@ type Config struct { Sasl sasl.Client // Replaceable function to customise the 433 handler's new nick. - // By default an underscore "_" is appended to the current nick. + // By default the current nick's last character is "incremented". + // See DefaultNewNick implementation below for details. NewNick func(string) string // Client->server ping frequency, in seconds. Defaults to 3m.