From 38d09dba2e02a3136ea202cea8b6df58dea19d1d Mon Sep 17 00:00:00 2001 From: Wim Date: Fri, 28 Jul 2017 14:26:26 +0200 Subject: Update vendor (go-irc) --- bridge/irc/irc.go | 2 +- vendor/github.com/42wim/go-ircevent/LICENSE | 27 + vendor/github.com/42wim/go-ircevent/irc.go | 572 +++++++++++++++++++++ .../github.com/42wim/go-ircevent/irc_callback.go | 222 ++++++++ vendor/github.com/42wim/go-ircevent/irc_sasl.go | 53 ++ vendor/github.com/42wim/go-ircevent/irc_struct.go | 76 +++ .../github.com/42wim/go-ircevent/irc_test_fuzz.go | 14 + vendor/github.com/thoj/go-ircevent/LICENSE | 27 - .../thoj/go-ircevent/examples/simple/simple.go | 27 - vendor/github.com/thoj/go-ircevent/irc.go | 483 ----------------- vendor/github.com/thoj/go-ircevent/irc_callback.go | 222 -------- vendor/github.com/thoj/go-ircevent/irc_sasl.go | 54 -- vendor/github.com/thoj/go-ircevent/irc_struct.go | 73 --- .../github.com/thoj/go-ircevent/irc_test_fuzz.go | 14 - vendor/manifest | 16 +- 15 files changed, 973 insertions(+), 909 deletions(-) create mode 100644 vendor/github.com/42wim/go-ircevent/LICENSE create mode 100644 vendor/github.com/42wim/go-ircevent/irc.go create mode 100644 vendor/github.com/42wim/go-ircevent/irc_callback.go create mode 100644 vendor/github.com/42wim/go-ircevent/irc_sasl.go create mode 100644 vendor/github.com/42wim/go-ircevent/irc_struct.go create mode 100644 vendor/github.com/42wim/go-ircevent/irc_test_fuzz.go delete mode 100644 vendor/github.com/thoj/go-ircevent/LICENSE delete mode 100644 vendor/github.com/thoj/go-ircevent/examples/simple/simple.go delete mode 100644 vendor/github.com/thoj/go-ircevent/irc.go delete mode 100644 vendor/github.com/thoj/go-ircevent/irc_callback.go delete mode 100644 vendor/github.com/thoj/go-ircevent/irc_sasl.go delete mode 100644 vendor/github.com/thoj/go-ircevent/irc_struct.go delete mode 100644 vendor/github.com/thoj/go-ircevent/irc_test_fuzz.go diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index dea2a3db..86ed6974 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -3,13 +3,13 @@ package birc import ( "crypto/tls" "fmt" + "github.com/42wim/go-ircevent" "github.com/42wim/matterbridge/bridge/config" log "github.com/Sirupsen/logrus" "github.com/paulrosania/go-charset/charset" _ "github.com/paulrosania/go-charset/data" "github.com/saintfish/chardet" ircm "github.com/sorcix/irc" - "github.com/thoj/go-ircevent" "io" "io/ioutil" "regexp" diff --git a/vendor/github.com/42wim/go-ircevent/LICENSE b/vendor/github.com/42wim/go-ircevent/LICENSE new file mode 100644 index 00000000..d6bf3577 --- /dev/null +++ b/vendor/github.com/42wim/go-ircevent/LICENSE @@ -0,0 +1,27 @@ +// Copyright (c) 2009 Thomas Jager. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/42wim/go-ircevent/irc.go b/vendor/github.com/42wim/go-ircevent/irc.go new file mode 100644 index 00000000..bcfe9ac2 --- /dev/null +++ b/vendor/github.com/42wim/go-ircevent/irc.go @@ -0,0 +1,572 @@ +// Copyright 2009 Thomas Jager All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +This package provides an event based IRC client library. It allows to +register callbacks for the events you need to handle. Its features +include handling standard CTCP, reconnecting on errors and detecting +stones servers. +Details of the IRC protocol can be found in the following RFCs: +https://tools.ietf.org/html/rfc1459 +https://tools.ietf.org/html/rfc2810 +https://tools.ietf.org/html/rfc2811 +https://tools.ietf.org/html/rfc2812 +https://tools.ietf.org/html/rfc2813 +The details of the client-to-client protocol (CTCP) can be found here: http://www.irchelp.org/irchelp/rfc/ctcpspec.html +*/ + +package irc + +import ( + "bufio" + "bytes" + "crypto/tls" + "errors" + "fmt" + "log" + "net" + "os" + "strconv" + "strings" + "time" +) + +const ( + VERSION = "go-ircevent v2.1" +) + +var ErrDisconnected = errors.New("Disconnect Called") + +// Read data from a connection. To be used as a goroutine. +func (irc *Connection) readLoop() { + defer irc.Done() + br := bufio.NewReaderSize(irc.socket, 512) + + errChan := irc.ErrorChan() + + for { + select { + case <-irc.end: + return + default: + // Set a read deadline based on the combined timeout and ping frequency + // We should ALWAYS have received a response from the server within the timeout + // after our own pings + if irc.socket != nil { + irc.socket.SetReadDeadline(time.Now().Add(irc.Timeout + irc.PingFreq)) + } + + msg, err := br.ReadString('\n') + + // We got past our blocking read, so bin timeout + if irc.socket != nil { + var zero time.Time + irc.socket.SetReadDeadline(zero) + } + + if err != nil { + errChan <- err + return + } + + if irc.Debug { + irc.Log.Printf("<-- %s\n", strings.TrimSpace(msg)) + } + + irc.Lock() + irc.lastMessage = time.Now() + irc.Unlock() + event, err := parseToEvent(msg) + event.Connection = irc + if err == nil { + /* XXX: len(args) == 0: args should be empty */ + irc.RunCallbacks(event) + } + } + } +} + +// Unescape tag values as defined in the IRCv3.2 message tags spec +// http://ircv3.net/specs/core/message-tags-3.2.html +func unescapeTagValue(value string) string { + value = strings.Replace(value, "\\:", ";", -1) + value = strings.Replace(value, "\\s", " ", -1) + value = strings.Replace(value, "\\\\", "\\", -1) + value = strings.Replace(value, "\\r", "\r", -1) + value = strings.Replace(value, "\\n", "\n", -1) + return value +} + +//Parse raw irc messages +func parseToEvent(msg string) (*Event, error) { + msg = strings.TrimSuffix(msg, "\n") //Remove \r\n + msg = strings.TrimSuffix(msg, "\r") + event := &Event{Raw: msg} + if len(msg) < 5 { + return nil, errors.New("Malformed msg from server") + } + + if msg[0] == '@' { + // IRCv3 Message Tags + if i := strings.Index(msg, " "); i > -1 { + event.Tags = make(map[string]string) + tags := strings.Split(msg[1:i], ";") + for _, data := range tags { + parts := strings.SplitN(data, "=", 2) + if len(parts) == 1 { + event.Tags[parts[0]] = "" + } else { + event.Tags[parts[0]] = unescapeTagValue(parts[1]) + } + } + msg = msg[i+1 : len(msg)] + } else { + return nil, errors.New("Malformed msg from server") + } + } + + if msg[0] == ':' { + if i := strings.Index(msg, " "); i > -1 { + event.Source = msg[1:i] + msg = msg[i+1 : len(msg)] + + } else { + return nil, errors.New("Malformed msg from server") + } + + if i, j := strings.Index(event.Source, "!"), strings.Index(event.Source, "@"); i > -1 && j > -1 && i < j { + event.Nick = event.Source[0:i] + event.User = event.Source[i+1 : j] + event.Host = event.Source[j+1 : len(event.Source)] + } + } + + split := strings.SplitN(msg, " :", 2) + args := strings.Split(split[0], " ") + event.Code = strings.ToUpper(args[0]) + event.Arguments = args[1:] + if len(split) > 1 { + event.Arguments = append(event.Arguments, split[1]) + } + return event, nil + +} + +// Loop to write to a connection. To be used as a goroutine. +func (irc *Connection) writeLoop() { + defer irc.Done() + errChan := irc.ErrorChan() + for { + select { + case <-irc.end: + return + case b, ok := <-irc.pwrite: + if !ok || b == "" || irc.socket == nil { + return + } + + if irc.Debug { + irc.Log.Printf("--> %s\n", strings.TrimSpace(b)) + } + + // Set a write deadline based on the time out + irc.socket.SetWriteDeadline(time.Now().Add(irc.Timeout)) + + _, err := irc.socket.Write([]byte(b)) + + // Past blocking write, bin timeout + var zero time.Time + irc.socket.SetWriteDeadline(zero) + + if err != nil { + errChan <- err + return + } + } + } +} + +// Pings the server if we have not received any messages for 5 minutes +// to keep the connection alive. To be used as a goroutine. +func (irc *Connection) pingLoop() { + defer irc.Done() + ticker := time.NewTicker(1 * time.Minute) // Tick every minute for monitoring + ticker2 := time.NewTicker(irc.PingFreq) // Tick at the ping frequency. + for { + select { + case <-ticker.C: + //Ping if we haven't received anything from the server within the keep alive period + if time.Since(irc.lastMessage) >= irc.KeepAlive { + irc.SendRawf("PING %d", time.Now().UnixNano()) + } + case <-ticker2.C: + //Ping at the ping frequency + irc.SendRawf("PING %d", time.Now().UnixNano()) + //Try to recapture nickname if it's not as configured. + irc.Lock() + if irc.nick != irc.nickcurrent { + irc.nickcurrent = irc.nick + irc.SendRawf("NICK %s", irc.nick) + } + irc.Unlock() + case <-irc.end: + ticker.Stop() + ticker2.Stop() + return + } + } +} + +func (irc *Connection) isQuitting() bool { + irc.Lock() + defer irc.Unlock() + return irc.quit +} + +// Main loop to control the connection. +func (irc *Connection) Loop() { + errChan := irc.ErrorChan() + for !irc.isQuitting() { + err := <-errChan + close(irc.end) + irc.Wait() + for !irc.isQuitting() { + irc.Log.Printf("Error, disconnected: %s\n", err) + if err = irc.Reconnect(); err != nil { + irc.Log.Printf("Error while reconnecting: %s\n", err) + time.Sleep(60 * time.Second) + } else { + errChan = irc.ErrorChan() + break + } + } + } +} + +// Quit the current connection and disconnect from the server +// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1.6 +func (irc *Connection) Quit() { + quit := "QUIT" + + if irc.QuitMessage != "" { + quit = fmt.Sprintf("QUIT :%s", irc.QuitMessage) + } + + irc.SendRaw(quit) + irc.Lock() + irc.stopped = true + irc.quit = true + irc.Unlock() +} + +// Use the connection to join a given channel. +// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.1 +func (irc *Connection) Join(channel string) { + irc.pwrite <- fmt.Sprintf("JOIN %s\r\n", channel) +} + +// Leave a given channel. +// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.2 +func (irc *Connection) Part(channel string) { + irc.pwrite <- fmt.Sprintf("PART %s\r\n", channel) +} + +// Send a notification to a nickname. This is similar to Privmsg but must not receive replies. +// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.2 +func (irc *Connection) Notice(target, message string) { + irc.pwrite <- fmt.Sprintf("NOTICE %s :%s\r\n", target, message) +} + +// Send a formated notification to a nickname. +// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.2 +func (irc *Connection) Noticef(target, format string, a ...interface{}) { + irc.Notice(target, fmt.Sprintf(format, a...)) +} + +// Send (action) message to a target (channel or nickname). +// No clear RFC on this one... +func (irc *Connection) Action(target, message string) { + irc.pwrite <- fmt.Sprintf("PRIVMSG %s :\001ACTION %s\001\r\n", target, message) +} + +// Send formatted (action) message to a target (channel or nickname). +func (irc *Connection) Actionf(target, format string, a ...interface{}) { + irc.Action(target, fmt.Sprintf(format, a...)) +} + +// Send (private) message to a target (channel or nickname). +// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.1 +func (irc *Connection) Privmsg(target, message string) { + irc.pwrite <- fmt.Sprintf("PRIVMSG %s :%s\r\n", target, message) +} + +// Send formated string to specified target (channel or nickname). +func (irc *Connection) Privmsgf(target, format string, a ...interface{}) { + irc.Privmsg(target, fmt.Sprintf(format, a...)) +} + +// Kick from with . For no message, pass empty string ("") +func (irc *Connection) Kick(user, channel, msg string) { + var cmd bytes.Buffer + cmd.WriteString(fmt.Sprintf("KICK %s %s", channel, user)) + if msg != "" { + cmd.WriteString(fmt.Sprintf(" :%s", msg)) + } + cmd.WriteString("\r\n") + irc.pwrite <- cmd.String() +} + +// Kick all from with . For no message, pass +// empty string ("") +func (irc *Connection) MultiKick(users []string, channel string, msg string) { + var cmd bytes.Buffer + cmd.WriteString(fmt.Sprintf("KICK %s %s", channel, strings.Join(users, ","))) + if msg != "" { + cmd.WriteString(fmt.Sprintf(" :%s", msg)) + } + cmd.WriteString("\r\n") + irc.pwrite <- cmd.String() +} + +// Send raw string. +func (irc *Connection) SendRaw(message string) { + irc.pwrite <- message + "\r\n" +} + +// Send raw formated string. +func (irc *Connection) SendRawf(format string, a ...interface{}) { + irc.SendRaw(fmt.Sprintf(format, a...)) +} + +// Set (new) nickname. +// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1.2 +func (irc *Connection) Nick(n string) { + irc.nick = n + irc.SendRawf("NICK %s", n) +} + +// Determine nick currently used with the connection. +func (irc *Connection) GetNick() string { + return irc.nickcurrent +} + +// Query information about a particular nickname. +// RFC 1459: https://tools.ietf.org/html/rfc1459#section-4.5.2 +func (irc *Connection) Whois(nick string) { + irc.SendRawf("WHOIS %s", nick) +} + +// Query information about a given nickname in the server. +// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.5.1 +func (irc *Connection) Who(nick string) { + irc.SendRawf("WHO %s", nick) +} + +// Set different modes for a target (channel or nickname). +// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.3 +func (irc *Connection) Mode(target string, modestring ...string) { + if len(modestring) > 0 { + mode := strings.Join(modestring, " ") + irc.SendRawf("MODE %s %s", target, mode) + return + } + irc.SendRawf("MODE %s", target) +} + +func (irc *Connection) ErrorChan() chan error { + return irc.Error +} + +// Returns true if the connection is connected to an IRC server. +func (irc *Connection) Connected() bool { + return !irc.stopped +} + +// A disconnect sends all buffered messages (if possible), +// stops all goroutines and then closes the socket. +func (irc *Connection) Disconnect() { + if irc.socket != nil { + irc.socket.Close() + } + irc.ErrorChan() <- ErrDisconnected +} + +// Reconnect to a server using the current connection. +func (irc *Connection) Reconnect() error { + irc.end = make(chan struct{}) + return irc.Connect(irc.Server) +} + +// Connect to a given server using the current connection configuration. +// This function also takes care of identification if a password is provided. +// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1 +func (irc *Connection) Connect(server string) error { + irc.Server = server + // mark Server as stopped since there can be an error during connect + irc.stopped = true + + // make sure everything is ready for connection + if len(irc.Server) == 0 { + return errors.New("empty 'server'") + } + if strings.Count(irc.Server, ":") != 1 { + return errors.New("wrong number of ':' in address") + } + if strings.Index(irc.Server, ":") == 0 { + return errors.New("hostname is missing") + } + if strings.Index(irc.Server, ":") == len(irc.Server)-1 { + return errors.New("port missing") + } + // check for valid range + ports := strings.Split(irc.Server, ":")[1] + port, err := strconv.Atoi(ports) + if err != nil { + return errors.New("extracting port failed") + } + if !((port >= 0) && (port <= 65535)) { + return errors.New("port number outside valid range") + } + if irc.Log == nil { + return errors.New("'Log' points to nil") + } + if len(irc.nick) == 0 { + return errors.New("empty 'nick'") + } + if len(irc.user) == 0 { + return errors.New("empty 'user'") + } + + if irc.UseTLS { + dialer := &net.Dialer{Timeout: irc.Timeout} + irc.socket, err = tls.DialWithDialer(dialer, "tcp", irc.Server, irc.TLSConfig) + } else { + irc.socket, err = net.DialTimeout("tcp", irc.Server, irc.Timeout) + } + if err != nil { + return err + } + + irc.stopped = false + irc.Log.Printf("Connected to %s (%s)\n", irc.Server, irc.socket.RemoteAddr()) + + irc.pwrite = make(chan string, 10) + irc.Error = make(chan error, 2) + irc.Add(3) + go irc.readLoop() + go irc.writeLoop() + go irc.pingLoop() + if len(irc.Password) > 0 { + irc.pwrite <- fmt.Sprintf("PASS %s\r\n", irc.Password) + } + + err = irc.negotiateCaps() + if err != nil { + return err + } + + irc.pwrite <- fmt.Sprintf("NICK %s\r\n", irc.nick) + irc.pwrite <- fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :%s\r\n", irc.user, irc.user) + return nil +} + +// Negotiate IRCv3 capabilities +func (irc *Connection) negotiateCaps() error { + saslResChan := make(chan *SASLResult) + if irc.UseSASL { + irc.RequestCaps = append(irc.RequestCaps, "sasl") + irc.setupSASLCallbacks(saslResChan) + } + + if len(irc.RequestCaps) == 0 { + return nil + } + + cap_chan := make(chan bool, len(irc.RequestCaps)) + irc.AddCallback("CAP", func(e *Event) { + if len(e.Arguments) != 3 { + return + } + command := e.Arguments[1] + + if command == "LS" { + missing_caps := len(irc.RequestCaps) + for _, cap_name := range strings.Split(e.Arguments[2], " ") { + for _, req_cap := range irc.RequestCaps { + if cap_name == req_cap { + irc.pwrite <- fmt.Sprintf("CAP REQ :%s\r\n", cap_name) + missing_caps-- + } + } + } + + for i := 0; i < missing_caps; i++ { + cap_chan <- true + } + } else if command == "ACK" || command == "NAK" { + for _, cap_name := range strings.Split(strings.TrimSpace(e.Arguments[2]), " ") { + if cap_name == "" { + continue + } + + if command == "ACK" { + irc.AcknowledgedCaps = append(irc.AcknowledgedCaps, cap_name) + } + cap_chan <- true + } + } + }) + + irc.pwrite <- "CAP LS\r\n" + + if irc.UseSASL { + select { + case res := <-saslResChan: + if res.Failed { + close(saslResChan) + return res.Err + } + case <-time.After(time.Second * 15): + close(saslResChan) + return errors.New("SASL setup timed out. This shouldn't happen.") + } + } + + // Wait for all capabilities to be ACKed or NAKed before ending negotiation + for i := 0; i < len(irc.RequestCaps); i++ { + <-cap_chan + } + irc.pwrite <- fmt.Sprintf("CAP END\r\n") + + return nil +} + +// Create a connection with the (publicly visible) nickname and username. +// The nickname is later used to address the user. Returns nil if nick +// or user are empty. +func IRC(nick, user string) *Connection { + // catch invalid values + if len(nick) == 0 { + return nil + } + if len(user) == 0 { + return nil + } + + irc := &Connection{ + nick: nick, + nickcurrent: nick, + user: user, + Log: log.New(os.Stdout, "", log.LstdFlags), + end: make(chan struct{}), + Version: VERSION, + KeepAlive: 4 * time.Minute, + Timeout: 1 * time.Minute, + PingFreq: 15 * time.Minute, + SASLMech: "PLAIN", + QuitMessage: "", + } + irc.setupCallbacks() + return irc +} diff --git a/vendor/github.com/42wim/go-ircevent/irc_callback.go b/vendor/github.com/42wim/go-ircevent/irc_callback.go new file mode 100644 index 00000000..d389f731 --- /dev/null +++ b/vendor/github.com/42wim/go-ircevent/irc_callback.go @@ -0,0 +1,222 @@ +package irc + +import ( + "strconv" + "strings" + "time" +) + +// Register a callback to a connection and event code. A callback is a function +// which takes only an Event pointer as parameter. Valid event codes are all +// IRC/CTCP commands and error/response codes. This function returns the ID of +// the registered callback for later management. +func (irc *Connection) AddCallback(eventcode string, callback func(*Event)) int { + eventcode = strings.ToUpper(eventcode) + id := 0 + if _, ok := irc.events[eventcode]; !ok { + irc.events[eventcode] = make(map[int]func(*Event)) + id = 0 + } else { + id = len(irc.events[eventcode]) + } + irc.events[eventcode][id] = callback + return id +} + +// Remove callback i (ID) from the given event code. This functions returns +// true upon success, false if any error occurs. +func (irc *Connection) RemoveCallback(eventcode string, i int) bool { + eventcode = strings.ToUpper(eventcode) + + if event, ok := irc.events[eventcode]; ok { + if _, ok := event[i]; ok { + delete(irc.events[eventcode], i) + return true + } + irc.Log.Printf("Event found, but no callback found at id %d\n", i) + return false + } + + irc.Log.Println("Event not found") + return false +} + +// Remove all callbacks from a given event code. It returns true +// if given event code is found and cleared. +func (irc *Connection) ClearCallback(eventcode string) bool { + eventcode = strings.ToUpper(eventcode) + + if _, ok := irc.events[eventcode]; ok { + irc.events[eventcode] = make(map[int]func(*Event)) + return true + } + + irc.Log.Println("Event not found") + return false +} + +// Replace callback i (ID) associated with a given event code with a new callback function. +func (irc *Connection) ReplaceCallback(eventcode string, i int, callback func(*Event)) { + eventcode = strings.ToUpper(eventcode) + + if event, ok := irc.events[eventcode]; ok { + if _, ok := event[i]; ok { + event[i] = callback + return + } + irc.Log.Printf("Event found, but no callback found at id %d\n", i) + } + irc.Log.Printf("Event not found. Use AddCallBack\n") +} + +// Execute all callbacks associated with a given event. +func (irc *Connection) RunCallbacks(event *Event) { + msg := event.Message() + if event.Code == "PRIVMSG" && len(msg) > 2 && msg[0] == '\x01' { + event.Code = "CTCP" //Unknown CTCP + + if i := strings.LastIndex(msg, "\x01"); i > 0 { + msg = msg[1:i] + } else { + irc.Log.Printf("Invalid CTCP Message: %s\n", strconv.Quote(msg)) + return + } + + if msg == "VERSION" { + event.Code = "CTCP_VERSION" + + } else if msg == "TIME" { + event.Code = "CTCP_TIME" + + } else if strings.HasPrefix(msg, "PING") { + event.Code = "CTCP_PING" + + } else if msg == "USERINFO" { + event.Code = "CTCP_USERINFO" + + } else if msg == "CLIENTINFO" { + event.Code = "CTCP_CLIENTINFO" + + } else if strings.HasPrefix(msg, "ACTION") { + event.Code = "CTCP_ACTION" + if len(msg) > 6 { + msg = msg[7:] + } else { + msg = "" + } + } + + event.Arguments[len(event.Arguments)-1] = msg + } + + if callbacks, ok := irc.events[event.Code]; ok { + if irc.VerboseCallbackHandler { + irc.Log.Printf("%v (%v) >> %#v\n", event.Code, len(callbacks), event) + } + + for _, callback := range callbacks { + callback(event) + } + } else if irc.VerboseCallbackHandler { + irc.Log.Printf("%v (0) >> %#v\n", event.Code, event) + } + + if callbacks, ok := irc.events["*"]; ok { + if irc.VerboseCallbackHandler { + irc.Log.Printf("%v (0) >> %#v\n", event.Code, event) + } + + for _, callback := range callbacks { + callback(event) + } + } +} + +// Set up some initial callbacks to handle the IRC/CTCP protocol. +func (irc *Connection) setupCallbacks() { + irc.events = make(map[string]map[int]func(*Event)) + + //Handle error events. + irc.AddCallback("ERROR", func(e *Event) { irc.Disconnect() }) + + //Handle ping events + irc.AddCallback("PING", func(e *Event) { irc.SendRaw("PONG :" + e.Message()) }) + + //Version handler + irc.AddCallback("CTCP_VERSION", func(e *Event) { + irc.SendRawf("NOTICE %s :\x01VERSION %s\x01", e.Nick, irc.Version) + }) + + irc.AddCallback("CTCP_USERINFO", func(e *Event) { + irc.SendRawf("NOTICE %s :\x01USERINFO %s\x01", e.Nick, irc.user) + }) + + irc.AddCallback("CTCP_CLIENTINFO", func(e *Event) { + irc.SendRawf("NOTICE %s :\x01CLIENTINFO PING VERSION TIME USERINFO CLIENTINFO\x01", e.Nick) + }) + + irc.AddCallback("CTCP_TIME", func(e *Event) { + ltime := time.Now() + irc.SendRawf("NOTICE %s :\x01TIME %s\x01", e.Nick, ltime.String()) + }) + + irc.AddCallback("CTCP_PING", func(e *Event) { irc.SendRawf("NOTICE %s :\x01%s\x01", e.Nick, e.Message()) }) + + // 437: ERR_UNAVAILRESOURCE " :Nick/channel is temporarily unavailable" + // Add a _ to current nick. If irc.nickcurrent is empty this cannot + // work. It has to be set somewhere first in case the nick is already + // taken or unavailable from the beginning. + irc.AddCallback("437", func(e *Event) { + // If irc.nickcurrent hasn't been set yet, set to irc.nick + if irc.nickcurrent == "" { + irc.nickcurrent = irc.nick + } + + if len(irc.nickcurrent) > 8 { + irc.nickcurrent = "_" + irc.nickcurrent + } else { + irc.nickcurrent = irc.nickcurrent + "_" + } + irc.SendRawf("NICK %s", irc.nickcurrent) + }) + + // 433: ERR_NICKNAMEINUSE " :Nickname is already in use" + // Add a _ to current nick. + irc.AddCallback("433", func(e *Event) { + // If irc.nickcurrent hasn't been set yet, set to irc.nick + if irc.nickcurrent == "" { + irc.nickcurrent = irc.nick + } + + if len(irc.nickcurrent) > 8 { + irc.nickcurrent = "_" + irc.nickcurrent + } else { + irc.nickcurrent = irc.nickcurrent + "_" + } + irc.SendRawf("NICK %s", irc.nickcurrent) + }) + + irc.AddCallback("PONG", func(e *Event) { + ns, _ := strconv.ParseInt(e.Message(), 10, 64) + delta := time.Duration(time.Now().UnixNano() - ns) + if irc.Debug { + irc.Log.Printf("Lag: %.3f s\n", delta.Seconds()) + } + }) + + // NICK Define a nickname. + // Set irc.nickcurrent to the new nick actually used in this connection. + irc.AddCallback("NICK", func(e *Event) { + if e.Nick == irc.nick { + irc.nickcurrent = e.Message() + } + }) + + // 1: RPL_WELCOME "Welcome to the Internet Relay Network !@" + // Set irc.nickcurrent to the actually used nick in this connection. + irc.AddCallback("001", func(e *Event) { + irc.Lock() + irc.nickcurrent = e.Arguments[0] + irc.Unlock() + }) +} diff --git a/vendor/github.com/42wim/go-ircevent/irc_sasl.go b/vendor/github.com/42wim/go-ircevent/irc_sasl.go new file mode 100644 index 00000000..fd9df7ba --- /dev/null +++ b/vendor/github.com/42wim/go-ircevent/irc_sasl.go @@ -0,0 +1,53 @@ +package irc + +import ( + "encoding/base64" + "errors" + "fmt" + "strings" +) + +type SASLResult struct { + Failed bool + Err error +} + +func (irc *Connection) setupSASLCallbacks(result chan<- *SASLResult) { + irc.AddCallback("CAP", func(e *Event) { + if len(e.Arguments) == 3 { + if e.Arguments[1] == "LS" { + if !strings.Contains(e.Arguments[2], "sasl") { + result <- &SASLResult{true, errors.New("no SASL capability " + e.Arguments[2])} + } + } + if e.Arguments[1] == "ACK" { + if irc.SASLMech != "PLAIN" { + result <- &SASLResult{true, errors.New("only PLAIN is supported")} + } + irc.SendRaw("AUTHENTICATE " + irc.SASLMech) + } + } + }) + irc.AddCallback("AUTHENTICATE", func(e *Event) { + str := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s\x00%s\x00%s", irc.SASLLogin, irc.SASLLogin, irc.SASLPassword))) + irc.SendRaw("AUTHENTICATE " + str) + }) + irc.AddCallback("901", func(e *Event) { + irc.SendRaw("CAP END") + irc.SendRaw("QUIT") + result <- &SASLResult{true, errors.New(e.Arguments[1])} + }) + irc.AddCallback("902", func(e *Event) { + irc.SendRaw("CAP END") + irc.SendRaw("QUIT") + result <- &SASLResult{true, errors.New(e.Arguments[1])} + }) + irc.AddCallback("903", func(e *Event) { + result <- &SASLResult{false, nil} + }) + irc.AddCallback("904", func(e *Event) { + irc.SendRaw("CAP END") + irc.SendRaw("QUIT") + result <- &SASLResult{true, errors.New(e.Arguments[1])} + }) +} diff --git a/vendor/github.com/42wim/go-ircevent/irc_struct.go b/vendor/github.com/42wim/go-ircevent/irc_struct.go new file mode 100644 index 00000000..c064cb80 --- /dev/null +++ b/vendor/github.com/42wim/go-ircevent/irc_struct.go @@ -0,0 +1,76 @@ +// Copyright 2009 Thomas Jager All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package irc + +import ( + "crypto/tls" + "log" + "net" + "sync" + "time" +) + +type Connection struct { + sync.Mutex + sync.WaitGroup + Debug bool + Error chan error + Password string + UseTLS bool + UseSASL bool + RequestCaps []string + AcknowledgedCaps []string + SASLLogin string + SASLPassword string + SASLMech string + TLSConfig *tls.Config + Version string + Timeout time.Duration + PingFreq time.Duration + KeepAlive time.Duration + Server string + + socket net.Conn + pwrite chan string + end chan struct{} + + nick string //The nickname we want. + nickcurrent string //The nickname we currently have. + user string + registered bool + events map[string]map[int]func(*Event) + + QuitMessage string + lastMessage time.Time + + VerboseCallbackHandler bool + Log *log.Logger + + stopped bool + quit bool //User called Quit, do not reconnect. +} + +// A struct to represent an event. +type Event struct { + Code string + Raw string + Nick string // + Host string //!@ + Source string // + User string // + Arguments []string + Tags map[string]string + Connection *Connection +} + +// Retrieve the last message from Event arguments. +// This function leaves the arguments untouched and +// returns an empty string if there are none. +func (e *Event) Message() string { + if len(e.Arguments) == 0 { + return "" + } + return e.Arguments[len(e.Arguments)-1] +} diff --git a/vendor/github.com/42wim/go-ircevent/irc_test_fuzz.go b/vendor/github.com/42wim/go-ircevent/irc_test_fuzz.go new file mode 100644 index 00000000..82202e1c --- /dev/null +++ b/vendor/github.com/42wim/go-ircevent/irc_test_fuzz.go @@ -0,0 +1,14 @@ +// +build gofuzz + +package irc + +func Fuzz(data []byte) int { + b := bytes.NewBuffer(data) + event, err := parseToEvent(b.String()) + if err == nil { + irc := IRC("go-eventirc", "go-eventirc") + irc.RunCallbacks(event) + return 1 + } + return 0 +} diff --git a/vendor/github.com/thoj/go-ircevent/LICENSE b/vendor/github.com/thoj/go-ircevent/LICENSE deleted file mode 100644 index d6bf3577..00000000 --- a/vendor/github.com/thoj/go-ircevent/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2009 Thomas Jager. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/thoj/go-ircevent/examples/simple/simple.go b/vendor/github.com/thoj/go-ircevent/examples/simple/simple.go deleted file mode 100644 index f2c27c60..00000000 --- a/vendor/github.com/thoj/go-ircevent/examples/simple/simple.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "github.com/thoj/go-ircevent" - "crypto/tls" - "fmt" -) - -const channel = "#go-eventirc-test"; -const serverssl = "irc.freenode.net:7000" - -func main() { - ircnick1 := "blatiblat" - irccon := irc.IRC(ircnick1, "IRCTestSSL") - irccon.VerboseCallbackHandler = true - irccon.Debug = true - irccon.UseTLS = true - irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true} - irccon.AddCallback("001", func(e *irc.Event) { irccon.Join(channel) }) - irccon.AddCallback("366", func(e *irc.Event) { }) - err := irccon.Connect(serverssl) - if err != nil { - fmt.Printf("Err %s", err ) - return - } - irccon.Loop() -} diff --git a/vendor/github.com/thoj/go-ircevent/irc.go b/vendor/github.com/thoj/go-ircevent/irc.go deleted file mode 100644 index eb9174d3..00000000 --- a/vendor/github.com/thoj/go-ircevent/irc.go +++ /dev/null @@ -1,483 +0,0 @@ -// Copyright 2009 Thomas Jager All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -This package provides an event based IRC client library. It allows to -register callbacks for the events you need to handle. Its features -include handling standard CTCP, reconnecting on errors and detecting -stones servers. -Details of the IRC protocol can be found in the following RFCs: -https://tools.ietf.org/html/rfc1459 -https://tools.ietf.org/html/rfc2810 -https://tools.ietf.org/html/rfc2811 -https://tools.ietf.org/html/rfc2812 -https://tools.ietf.org/html/rfc2813 -The details of the client-to-client protocol (CTCP) can be found here: http://www.irchelp.org/irchelp/rfc/ctcpspec.html -*/ - -package irc - -import ( - "bufio" - "bytes" - "crypto/tls" - "errors" - "fmt" - "log" - "net" - "os" - "strconv" - "strings" - "time" -) - -const ( - VERSION = "go-ircevent v2.1" -) - -var ErrDisconnected = errors.New("Disconnect Called") - -// Read data from a connection. To be used as a goroutine. -func (irc *Connection) readLoop() { - defer irc.Done() - br := bufio.NewReaderSize(irc.socket, 512) - - errChan := irc.ErrorChan() - - for { - select { - case <-irc.end: - return - default: - // Set a read deadline based on the combined timeout and ping frequency - // We should ALWAYS have received a response from the server within the timeout - // after our own pings - if irc.socket != nil { - irc.socket.SetReadDeadline(time.Now().Add(irc.Timeout + irc.PingFreq)) - } - - msg, err := br.ReadString('\n') - - // We got past our blocking read, so bin timeout - if irc.socket != nil { - var zero time.Time - irc.socket.SetReadDeadline(zero) - } - - if err != nil { - errChan <- err - return - } - - if irc.Debug { - irc.Log.Printf("<-- %s\n", strings.TrimSpace(msg)) - } - - irc.Lock() - irc.lastMessage = time.Now() - irc.Unlock() - event, err := parseToEvent(msg) - event.Connection = irc - if err == nil { - /* XXX: len(args) == 0: args should be empty */ - irc.RunCallbacks(event) - } - } - } -} - -//Parse raw irc messages -func parseToEvent(msg string) (*Event, error) { - msg = strings.TrimSuffix(msg, "\n") //Remove \r\n - msg = strings.TrimSuffix(msg, "\r") - event := &Event{Raw: msg} - if len(msg) < 5 { - return nil, errors.New("Malformed msg from server") - } - if msg[0] == ':' { - if i := strings.Index(msg, " "); i > -1 { - event.Source = msg[1:i] - msg = msg[i+1 : len(msg)] - - } else { - return nil, errors.New("Malformed msg from server") - } - - if i, j := strings.Index(event.Source, "!"), strings.Index(event.Source, "@"); i > -1 && j > -1 && i < j { - event.Nick = event.Source[0:i] - event.User = event.Source[i+1 : j] - event.Host = event.Source[j+1 : len(event.Source)] - } - } - - split := strings.SplitN(msg, " :", 2) - args := strings.Split(split[0], " ") - event.Code = strings.ToUpper(args[0]) - event.Arguments = args[1:] - if len(split) > 1 { - event.Arguments = append(event.Arguments, split[1]) - } - return event, nil - -} - -// Loop to write to a connection. To be used as a goroutine. -func (irc *Connection) writeLoop() { - defer irc.Done() - errChan := irc.ErrorChan() - for { - select { - case <-irc.end: - return - case b, ok := <-irc.pwrite: - if !ok || b == "" || irc.socket == nil { - return - } - - if irc.Debug { - irc.Log.Printf("--> %s\n", strings.TrimSpace(b)) - } - - // Set a write deadline based on the time out - irc.socket.SetWriteDeadline(time.Now().Add(irc.Timeout)) - - _, err := irc.socket.Write([]byte(b)) - - // Past blocking write, bin timeout - var zero time.Time - irc.socket.SetWriteDeadline(zero) - - if err != nil { - errChan <- err - return - } - } - } -} - -// Pings the server if we have not received any messages for 5 minutes -// to keep the connection alive. To be used as a goroutine. -func (irc *Connection) pingLoop() { - defer irc.Done() - ticker := time.NewTicker(1 * time.Minute) // Tick every minute for monitoring - ticker2 := time.NewTicker(irc.PingFreq) // Tick at the ping frequency. - for { - select { - case <-ticker.C: - //Ping if we haven't received anything from the server within the keep alive period - if time.Since(irc.lastMessage) >= irc.KeepAlive { - irc.SendRawf("PING %d", time.Now().UnixNano()) - } - case <-ticker2.C: - //Ping at the ping frequency - irc.SendRawf("PING %d", time.Now().UnixNano()) - //Try to recapture nickname if it's not as configured. - irc.Lock() - if irc.nick != irc.nickcurrent { - irc.nickcurrent = irc.nick - irc.SendRawf("NICK %s", irc.nick) - } - irc.Unlock() - case <-irc.end: - ticker.Stop() - ticker2.Stop() - return - } - } -} - -func (irc *Connection) isQuitting() bool { - irc.Lock() - defer irc.Unlock() - return irc.quit -} - -// Main loop to control the connection. -func (irc *Connection) Loop() { - errChan := irc.ErrorChan() - for !irc.isQuitting() { - err := <-errChan - close(irc.end) - irc.Wait() - for !irc.isQuitting() { - irc.Log.Printf("Error, disconnected: %s\n", err) - if err = irc.Reconnect(); err != nil { - irc.Log.Printf("Error while reconnecting: %s\n", err) - time.Sleep(60 * time.Second) - } else { - errChan = irc.ErrorChan() - break - } - } - } -} - -// Quit the current connection and disconnect from the server -// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1.6 -func (irc *Connection) Quit() { - quit := "QUIT" - - if irc.QuitMessage != "" { - quit = fmt.Sprintf("QUIT :%s", irc.QuitMessage) - } - - irc.SendRaw(quit) - irc.Lock() - irc.stopped = true - irc.quit = true - irc.Unlock() -} - -// Use the connection to join a given channel. -// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.1 -func (irc *Connection) Join(channel string) { - irc.pwrite <- fmt.Sprintf("JOIN %s\r\n", channel) -} - -// Leave a given channel. -// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.2 -func (irc *Connection) Part(channel string) { - irc.pwrite <- fmt.Sprintf("PART %s\r\n", channel) -} - -// Send a notification to a nickname. This is similar to Privmsg but must not receive replies. -// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.2 -func (irc *Connection) Notice(target, message string) { - irc.pwrite <- fmt.Sprintf("NOTICE %s :%s\r\n", target, message) -} - -// Send a formated notification to a nickname. -// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.2 -func (irc *Connection) Noticef(target, format string, a ...interface{}) { - irc.Notice(target, fmt.Sprintf(format, a...)) -} - -// Send (action) message to a target (channel or nickname). -// No clear RFC on this one... -func (irc *Connection) Action(target, message string) { - irc.pwrite <- fmt.Sprintf("PRIVMSG %s :\001ACTION %s\001\r\n", target, message) -} - -// Send formatted (action) message to a target (channel or nickname). -func (irc *Connection) Actionf(target, format string, a ...interface{}) { - irc.Action(target, fmt.Sprintf(format, a...)) -} - -// Send (private) message to a target (channel or nickname). -// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.1 -func (irc *Connection) Privmsg(target, message string) { - irc.pwrite <- fmt.Sprintf("PRIVMSG %s :%s\r\n", target, message) -} - -// Send formated string to specified target (channel or nickname). -func (irc *Connection) Privmsgf(target, format string, a ...interface{}) { - irc.Privmsg(target, fmt.Sprintf(format, a...)) -} - -// Kick from with . For no message, pass empty string ("") -func (irc *Connection) Kick(user, channel, msg string) { - var cmd bytes.Buffer - cmd.WriteString(fmt.Sprintf("KICK %s %s", channel, user)) - if msg != "" { - cmd.WriteString(fmt.Sprintf(" :%s", msg)) - } - cmd.WriteString("\r\n") - irc.pwrite <- cmd.String() -} - -// Kick all from with . For no message, pass -// empty string ("") -func (irc *Connection) MultiKick(users []string, channel string, msg string) { - var cmd bytes.Buffer - cmd.WriteString(fmt.Sprintf("KICK %s %s", channel, strings.Join(users, ","))) - if msg != "" { - cmd.WriteString(fmt.Sprintf(" :%s", msg)) - } - cmd.WriteString("\r\n") - irc.pwrite <- cmd.String() -} - -// Send raw string. -func (irc *Connection) SendRaw(message string) { - irc.pwrite <- message + "\r\n" -} - -// Send raw formated string. -func (irc *Connection) SendRawf(format string, a ...interface{}) { - irc.SendRaw(fmt.Sprintf(format, a...)) -} - -// Set (new) nickname. -// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1.2 -func (irc *Connection) Nick(n string) { - irc.nick = n - irc.SendRawf("NICK %s", n) -} - -// Determine nick currently used with the connection. -func (irc *Connection) GetNick() string { - return irc.nickcurrent -} - -// Query information about a particular nickname. -// RFC 1459: https://tools.ietf.org/html/rfc1459#section-4.5.2 -func (irc *Connection) Whois(nick string) { - irc.SendRawf("WHOIS %s", nick) -} - -// Query information about a given nickname in the server. -// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.5.1 -func (irc *Connection) Who(nick string) { - irc.SendRawf("WHO %s", nick) -} - -// Set different modes for a target (channel or nickname). -// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.3 -func (irc *Connection) Mode(target string, modestring ...string) { - if len(modestring) > 0 { - mode := strings.Join(modestring, " ") - irc.SendRawf("MODE %s %s", target, mode) - return - } - irc.SendRawf("MODE %s", target) -} - -func (irc *Connection) ErrorChan() chan error { - return irc.Error -} - -// Returns true if the connection is connected to an IRC server. -func (irc *Connection) Connected() bool { - return !irc.stopped -} - -// A disconnect sends all buffered messages (if possible), -// stops all goroutines and then closes the socket. -func (irc *Connection) Disconnect() { - if irc.socket != nil { - irc.socket.Close() - } - irc.ErrorChan() <- ErrDisconnected -} - -// Reconnect to a server using the current connection. -func (irc *Connection) Reconnect() error { - irc.end = make(chan struct{}) - return irc.Connect(irc.Server) -} - -// Connect to a given server using the current connection configuration. -// This function also takes care of identification if a password is provided. -// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1 -func (irc *Connection) Connect(server string) error { - irc.Server = server - // mark Server as stopped since there can be an error during connect - irc.stopped = true - - // make sure everything is ready for connection - if len(irc.Server) == 0 { - return errors.New("empty 'server'") - } - if strings.Count(irc.Server, ":") != 1 { - return errors.New("wrong number of ':' in address") - } - if strings.Index(irc.Server, ":") == 0 { - return errors.New("hostname is missing") - } - if strings.Index(irc.Server, ":") == len(irc.Server)-1 { - return errors.New("port missing") - } - // check for valid range - ports := strings.Split(irc.Server, ":")[1] - port, err := strconv.Atoi(ports) - if err != nil { - return errors.New("extracting port failed") - } - if !((port >= 0) && (port <= 65535)) { - return errors.New("port number outside valid range") - } - if irc.Log == nil { - return errors.New("'Log' points to nil") - } - if len(irc.nick) == 0 { - return errors.New("empty 'nick'") - } - if len(irc.user) == 0 { - return errors.New("empty 'user'") - } - - if irc.UseTLS { - dialer := &net.Dialer{Timeout: irc.Timeout} - irc.socket, err = tls.DialWithDialer(dialer, "tcp", irc.Server, irc.TLSConfig) - } else { - irc.socket, err = net.DialTimeout("tcp", irc.Server, irc.Timeout) - } - if err != nil { - return err - } - - irc.stopped = false - irc.Log.Printf("Connected to %s (%s)\n", irc.Server, irc.socket.RemoteAddr()) - - irc.pwrite = make(chan string, 10) - irc.Error = make(chan error, 2) - irc.Add(3) - go irc.readLoop() - go irc.writeLoop() - go irc.pingLoop() - if len(irc.Password) > 0 { - irc.pwrite <- fmt.Sprintf("PASS %s\r\n", irc.Password) - } - - resChan := make(chan *SASLResult) - if irc.UseSASL { - irc.setupSASLCallbacks(resChan) - irc.pwrite <- fmt.Sprintf("CAP LS\r\n") - // request SASL - irc.pwrite <- fmt.Sprintf("CAP REQ :sasl\r\n") - // if sasl request doesn't complete in 15 seconds, close chan and timeout - select { - case res := <-resChan: - if res.Failed { - close(resChan) - return res.Err - } - case <-time.After(time.Second * 15): - close(resChan) - return errors.New("SASL setup timed out. This shouldn't happen.") - } - } - irc.pwrite <- fmt.Sprintf("NICK %s\r\n", irc.nick) - irc.pwrite <- fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :%s\r\n", irc.user, irc.user) - return nil -} - -// Create a connection with the (publicly visible) nickname and username. -// The nickname is later used to address the user. Returns nil if nick -// or user are empty. -func IRC(nick, user string) *Connection { - // catch invalid values - if len(nick) == 0 { - return nil - } - if len(user) == 0 { - return nil - } - - irc := &Connection{ - nick: nick, - nickcurrent: nick, - user: user, - Log: log.New(os.Stdout, "", log.LstdFlags), - end: make(chan struct{}), - Version: VERSION, - KeepAlive: 4 * time.Minute, - Timeout: 1 * time.Minute, - PingFreq: 15 * time.Minute, - SASLMech: "PLAIN", - QuitMessage: "", - } - irc.setupCallbacks() - return irc -} diff --git a/vendor/github.com/thoj/go-ircevent/irc_callback.go b/vendor/github.com/thoj/go-ircevent/irc_callback.go deleted file mode 100644 index d389f731..00000000 --- a/vendor/github.com/thoj/go-ircevent/irc_callback.go +++ /dev/null @@ -1,222 +0,0 @@ -package irc - -import ( - "strconv" - "strings" - "time" -) - -// Register a callback to a connection and event code. A callback is a function -// which takes only an Event pointer as parameter. Valid event codes are all -// IRC/CTCP commands and error/response codes. This function returns the ID of -// the registered callback for later management. -func (irc *Connection) AddCallback(eventcode string, callback func(*Event)) int { - eventcode = strings.ToUpper(eventcode) - id := 0 - if _, ok := irc.events[eventcode]; !ok { - irc.events[eventcode] = make(map[int]func(*Event)) - id = 0 - } else { - id = len(irc.events[eventcode]) - } - irc.events[eventcode][id] = callback - return id -} - -// Remove callback i (ID) from the given event code. This functions returns -// true upon success, false if any error occurs. -func (irc *Connection) RemoveCallback(eventcode string, i int) bool { - eventcode = strings.ToUpper(eventcode) - - if event, ok := irc.events[eventcode]; ok { - if _, ok := event[i]; ok { - delete(irc.events[eventcode], i) - return true - } - irc.Log.Printf("Event found, but no callback found at id %d\n", i) - return false - } - - irc.Log.Println("Event not found") - return false -} - -// Remove all callbacks from a given event code. It returns true -// if given event code is found and cleared. -func (irc *Connection) ClearCallback(eventcode string) bool { - eventcode = strings.ToUpper(eventcode) - - if _, ok := irc.events[eventcode]; ok { - irc.events[eventcode] = make(map[int]func(*Event)) - return true - } - - irc.Log.Println("Event not found") - return false -} - -// Replace callback i (ID) associated with a given event code with a new callback function. -func (irc *Connection) ReplaceCallback(eventcode string, i int, callback func(*Event)) { - eventcode = strings.ToUpper(eventcode) - - if event, ok := irc.events[eventcode]; ok { - if _, ok := event[i]; ok { - event[i] = callback - return - } - irc.Log.Printf("Event found, but no callback found at id %d\n", i) - } - irc.Log.Printf("Event not found. Use AddCallBack\n") -} - -// Execute all callbacks associated with a given event. -func (irc *Connection) RunCallbacks(event *Event) { - msg := event.Message() - if event.Code == "PRIVMSG" && len(msg) > 2 && msg[0] == '\x01' { - event.Code = "CTCP" //Unknown CTCP - - if i := strings.LastIndex(msg, "\x01"); i > 0 { - msg = msg[1:i] - } else { - irc.Log.Printf("Invalid CTCP Message: %s\n", strconv.Quote(msg)) - return - } - - if msg == "VERSION" { - event.Code = "CTCP_VERSION" - - } else if msg == "TIME" { - event.Code = "CTCP_TIME" - - } else if strings.HasPrefix(msg, "PING") { - event.Code = "CTCP_PING" - - } else if msg == "USERINFO" { - event.Code = "CTCP_USERINFO" - - } else if msg == "CLIENTINFO" { - event.Code = "CTCP_CLIENTINFO" - - } else if strings.HasPrefix(msg, "ACTION") { - event.Code = "CTCP_ACTION" - if len(msg) > 6 { - msg = msg[7:] - } else { - msg = "" - } - } - - event.Arguments[len(event.Arguments)-1] = msg - } - - if callbacks, ok := irc.events[event.Code]; ok { - if irc.VerboseCallbackHandler { - irc.Log.Printf("%v (%v) >> %#v\n", event.Code, len(callbacks), event) - } - - for _, callback := range callbacks { - callback(event) - } - } else if irc.VerboseCallbackHandler { - irc.Log.Printf("%v (0) >> %#v\n", event.Code, event) - } - - if callbacks, ok := irc.events["*"]; ok { - if irc.VerboseCallbackHandler { - irc.Log.Printf("%v (0) >> %#v\n", event.Code, event) - } - - for _, callback := range callbacks { - callback(event) - } - } -} - -// Set up some initial callbacks to handle the IRC/CTCP protocol. -func (irc *Connection) setupCallbacks() { - irc.events = make(map[string]map[int]func(*Event)) - - //Handle error events. - irc.AddCallback("ERROR", func(e *Event) { irc.Disconnect() }) - - //Handle ping events - irc.AddCallback("PING", func(e *Event) { irc.SendRaw("PONG :" + e.Message()) }) - - //Version handler - irc.AddCallback("CTCP_VERSION", func(e *Event) { - irc.SendRawf("NOTICE %s :\x01VERSION %s\x01", e.Nick, irc.Version) - }) - - irc.AddCallback("CTCP_USERINFO", func(e *Event) { - irc.SendRawf("NOTICE %s :\x01USERINFO %s\x01", e.Nick, irc.user) - }) - - irc.AddCallback("CTCP_CLIENTINFO", func(e *Event) { - irc.SendRawf("NOTICE %s :\x01CLIENTINFO PING VERSION TIME USERINFO CLIENTINFO\x01", e.Nick) - }) - - irc.AddCallback("CTCP_TIME", func(e *Event) { - ltime := time.Now() - irc.SendRawf("NOTICE %s :\x01TIME %s\x01", e.Nick, ltime.String()) - }) - - irc.AddCallback("CTCP_PING", func(e *Event) { irc.SendRawf("NOTICE %s :\x01%s\x01", e.Nick, e.Message()) }) - - // 437: ERR_UNAVAILRESOURCE " :Nick/channel is temporarily unavailable" - // Add a _ to current nick. If irc.nickcurrent is empty this cannot - // work. It has to be set somewhere first in case the nick is already - // taken or unavailable from the beginning. - irc.AddCallback("437", func(e *Event) { - // If irc.nickcurrent hasn't been set yet, set to irc.nick - if irc.nickcurrent == "" { - irc.nickcurrent = irc.nick - } - - if len(irc.nickcurrent) > 8 { - irc.nickcurrent = "_" + irc.nickcurrent - } else { - irc.nickcurrent = irc.nickcurrent + "_" - } - irc.SendRawf("NICK %s", irc.nickcurrent) - }) - - // 433: ERR_NICKNAMEINUSE " :Nickname is already in use" - // Add a _ to current nick. - irc.AddCallback("433", func(e *Event) { - // If irc.nickcurrent hasn't been set yet, set to irc.nick - if irc.nickcurrent == "" { - irc.nickcurrent = irc.nick - } - - if len(irc.nickcurrent) > 8 { - irc.nickcurrent = "_" + irc.nickcurrent - } else { - irc.nickcurrent = irc.nickcurrent + "_" - } - irc.SendRawf("NICK %s", irc.nickcurrent) - }) - - irc.AddCallback("PONG", func(e *Event) { - ns, _ := strconv.ParseInt(e.Message(), 10, 64) - delta := time.Duration(time.Now().UnixNano() - ns) - if irc.Debug { - irc.Log.Printf("Lag: %.3f s\n", delta.Seconds()) - } - }) - - // NICK Define a nickname. - // Set irc.nickcurrent to the new nick actually used in this connection. - irc.AddCallback("NICK", func(e *Event) { - if e.Nick == irc.nick { - irc.nickcurrent = e.Message() - } - }) - - // 1: RPL_WELCOME "Welcome to the Internet Relay Network !@" - // Set irc.nickcurrent to the actually used nick in this connection. - irc.AddCallback("001", func(e *Event) { - irc.Lock() - irc.nickcurrent = e.Arguments[0] - irc.Unlock() - }) -} diff --git a/vendor/github.com/thoj/go-ircevent/irc_sasl.go b/vendor/github.com/thoj/go-ircevent/irc_sasl.go deleted file mode 100644 index e5ff9e38..00000000 --- a/vendor/github.com/thoj/go-ircevent/irc_sasl.go +++ /dev/null @@ -1,54 +0,0 @@ -package irc - -import ( - "encoding/base64" - "errors" - "fmt" - "strings" -) - -type SASLResult struct { - Failed bool - Err error -} - -func (irc *Connection) setupSASLCallbacks(result chan<- *SASLResult) { - irc.AddCallback("CAP", func(e *Event) { - if len(e.Arguments) == 3 { - if e.Arguments[1] == "LS" { - if !strings.Contains(e.Arguments[2], "sasl") { - result <- &SASLResult{true, errors.New("no SASL capability " + e.Arguments[2])} - } - } - if e.Arguments[1] == "ACK" { - if irc.SASLMech != "PLAIN" { - result <- &SASLResult{true, errors.New("only PLAIN is supported")} - } - irc.SendRaw("AUTHENTICATE " + irc.SASLMech) - } - } - }) - irc.AddCallback("AUTHENTICATE", func(e *Event) { - str := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s\x00%s\x00%s", irc.SASLLogin, irc.SASLLogin, irc.SASLPassword))) - irc.SendRaw("AUTHENTICATE " + str) - }) - irc.AddCallback("901", func(e *Event) { - irc.SendRaw("CAP END") - irc.SendRaw("QUIT") - result <- &SASLResult{true, errors.New(e.Arguments[1])} - }) - irc.AddCallback("902", func(e *Event) { - irc.SendRaw("CAP END") - irc.SendRaw("QUIT") - result <- &SASLResult{true, errors.New(e.Arguments[1])} - }) - irc.AddCallback("903", func(e *Event) { - irc.SendRaw("CAP END") - result <- &SASLResult{false, nil} - }) - irc.AddCallback("904", func(e *Event) { - irc.SendRaw("CAP END") - irc.SendRaw("QUIT") - result <- &SASLResult{true, errors.New(e.Arguments[1])} - }) -} diff --git a/vendor/github.com/thoj/go-ircevent/irc_struct.go b/vendor/github.com/thoj/go-ircevent/irc_struct.go deleted file mode 100644 index a188d9d9..00000000 --- a/vendor/github.com/thoj/go-ircevent/irc_struct.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2009 Thomas Jager All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package irc - -import ( - "crypto/tls" - "log" - "net" - "sync" - "time" -) - -type Connection struct { - sync.Mutex - sync.WaitGroup - Debug bool - Error chan error - Password string - UseTLS bool - UseSASL bool - SASLLogin string - SASLPassword string - SASLMech string - TLSConfig *tls.Config - Version string - Timeout time.Duration - PingFreq time.Duration - KeepAlive time.Duration - Server string - - socket net.Conn - pwrite chan string - end chan struct{} - - nick string //The nickname we want. - nickcurrent string //The nickname we currently have. - user string - registered bool - events map[string]map[int]func(*Event) - - QuitMessage string - lastMessage time.Time - - VerboseCallbackHandler bool - Log *log.Logger - - stopped bool - quit bool //User called Quit, do not reconnect. -} - -// A struct to represent an event. -type Event struct { - Code string - Raw string - Nick string // - Host string //!@ - Source string // - User string // - Arguments []string - Connection *Connection -} - -// Retrieve the last message from Event arguments. -// This function leaves the arguments untouched and -// returns an empty string if there are none. -func (e *Event) Message() string { - if len(e.Arguments) == 0 { - return "" - } - return e.Arguments[len(e.Arguments)-1] -} diff --git a/vendor/github.com/thoj/go-ircevent/irc_test_fuzz.go b/vendor/github.com/thoj/go-ircevent/irc_test_fuzz.go deleted file mode 100644 index 82202e1c..00000000 --- a/vendor/github.com/thoj/go-ircevent/irc_test_fuzz.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build gofuzz - -package irc - -func Fuzz(data []byte) int { - b := bytes.NewBuffer(data) - event, err := parseToEvent(b.String()) - if err == nil { - irc := IRC("go-eventirc", "go-eventirc") - irc.RunCallbacks(event) - return 1 - } - return 0 -} diff --git a/vendor/manifest b/vendor/manifest index 3c94d8f7..abd7c121 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -1,6 +1,14 @@ { "version": 0, "dependencies": [ + { + "importpath": "github.com/42wim/go-ircevent", + "repository": "https://github.com/42wim/go-ircevent", + "vcs": "git", + "revision": "d3aec637ae2f2a4f9ff95df55091894d80fa3112", + "branch": "ircv3", + "notests": true + }, { "importpath": "github.com/BurntSushi/toml", "repository": "https://github.com/BurntSushi/toml", @@ -499,14 +507,6 @@ "branch": "master", "notests": true }, - { - "importpath": "github.com/thoj/go-ircevent", - "repository": "https://github.com/thoj/go-ircevent", - "vcs": "git", - "revision": "1b0acb5f2f1b615cfbd4b9f91abb14cb39a18769", - "branch": "master", - "notests": true - }, { "importpath": "github.com/tylerb/graceful", "repository": "https://github.com/tylerb/graceful", -- cgit v1.2.3