summaryrefslogtreecommitdiff
path: root/deprecated-webircgateway/pkg/webircgateway/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'deprecated-webircgateway/pkg/webircgateway/client.go')
-rw-r--r--deprecated-webircgateway/pkg/webircgateway/client.go741
1 files changed, 0 insertions, 741 deletions
diff --git a/deprecated-webircgateway/pkg/webircgateway/client.go b/deprecated-webircgateway/pkg/webircgateway/client.go
deleted file mode 100644
index 43d3fe7..0000000
--- a/deprecated-webircgateway/pkg/webircgateway/client.go
+++ /dev/null
@@ -1,741 +0,0 @@
-package webircgateway
-
-import (
- "bufio"
- "crypto/tls"
- "errors"
- "fmt"
- "io"
- "net"
- "runtime/debug"
- "strconv"
- "strings"
- "sync"
- "sync/atomic"
- "syscall"
- "time"
-
- "golang.org/x/time/rate"
-
- "github.com/kiwiirc/webircgateway/pkg/dnsbl"
- "github.com/kiwiirc/webircgateway/pkg/irc"
- "github.com/kiwiirc/webircgateway/pkg/proxy"
-)
-
-const (
- // ClientStateIdle - Client connected and just sat there
- ClientStateIdle = "idle"
- // ClientStateConnecting - Connecting upstream
- ClientStateConnecting = "connecting"
- // ClientStateRegistering - Registering to the IRC network
- ClientStateRegistering = "registering"
- // ClientStateConnected - Connected upstream
- ClientStateConnected = "connected"
- // ClientStateEnding - Client is ending its connection
- ClientStateEnding = "ending"
-)
-
-type ClientSignal [3]string
-
-// Client - Connecting client struct
-type Client struct {
- Gateway *Gateway
- Id uint64
- State string
- EndWG sync.WaitGroup
- shuttingDownLock sync.Mutex
- shuttingDown bool
- SeenQuit bool
- Recv chan string
- ThrottledRecv *ThrottledStringChannel
- upstream io.ReadWriteCloser
- UpstreamRecv chan string
- UpstreamSend chan string
- UpstreamStarted bool
- UpstreamConfig *ConfigUpstream
- RemoteAddr string
- RemoteHostname string
- RemotePort int
- DestHost string
- DestPort int
- DestTLS bool
- IrcState *irc.State
- Encoding string
- // Tags get passed upstream via the WEBIRC command
- Tags map[string]string
- // Captchas may be needed to verify a client
- RequiresVerification bool
- Verified bool
- SentPass bool
- // Signals for the transport to make use of (data, connection state, etc)
- Signals chan ClientSignal
- Features struct {
- Messagetags bool
- Metadata bool
- ExtJwt bool
- }
- // The specific message-tags CAP that the client has requested if we are wrapping it
- RequestedMessageTagsCap string
- // Prefix used by the server when sending its own messages
- ServerMessagePrefix irc.Mask
-}
-
-var nextClientID uint64 = 1
-
-// NewClient - Makes a new client
-func NewClient(gateway *Gateway) *Client {
- thisID := atomic.AddUint64(&nextClientID, 1)
-
- recv := make(chan string, 50)
- c := &Client{
- Gateway: gateway,
- Id: thisID,
- State: ClientStateIdle,
- Recv: recv,
- ThrottledRecv: NewThrottledStringChannel(recv, rate.NewLimiter(rate.Inf, 1)),
- UpstreamSend: make(chan string, 50),
- UpstreamRecv: make(chan string, 50),
- Encoding: "UTF-8",
- Signals: make(chan ClientSignal, 50),
- Tags: make(map[string]string),
- IrcState: irc.NewState(),
- UpstreamConfig: &ConfigUpstream{},
- }
-
- // Auto enable some features by default. They may be disabled later on
- c.Features.ExtJwt = true
-
- c.RequiresVerification = gateway.Config.RequiresVerification
-
- // Handles data to/from the client and upstreams
- go c.clientLineWorker()
-
- // This Add(1) will be ended once the client starts shutting down in StartShutdown()
- c.EndWG.Add(1)
-
- // Add to the clients maps and wait until everything has been marked
- // as completed (several routines add themselves to EndWG so that we can catch
- // when they are all completed)
- gateway.Clients.Set(strconv.FormatUint(c.Id, 10), c)
- go func() {
- c.EndWG.Wait()
- gateway.Clients.Remove(strconv.FormatUint(c.Id, 10))
-
- hook := &HookClientState{
- Client: c,
- Connected: false,
- }
- hook.Dispatch("client.state")
- }()
-
- hook := &HookClientState{
- Client: c,
- Connected: true,
- }
- hook.Dispatch("client.state")
-
- return c
-}
-
-// Log - Log a line of text with context of this client
-func (c *Client) Log(level int, format string, args ...interface{}) {
- prefix := fmt.Sprintf("client:%d ", c.Id)
- c.Gateway.Log(level, prefix+format, args...)
-}
-
-// TrafficLog - Log out raw IRC traffic
-func (c *Client) TrafficLog(isUpstream bool, toGateway bool, traffic string) {
- label := ""
- if isUpstream && toGateway {
- label = "Upstream->"
- } else if isUpstream && !toGateway {
- label = "->Upstream"
- } else if !isUpstream && toGateway {
- label = "Client->"
- } else if !isUpstream && !toGateway {
- label = "->Client"
- }
- c.Log(1, "Traffic (%s) %s", label, traffic)
-}
-
-func (c *Client) Ready() {
- dnsblAction := c.Gateway.Config.DnsblAction
- validAction := dnsblAction == "verify" || dnsblAction == "deny"
- dnsblTookAction := ""
-
- if len(c.Gateway.Config.DnsblServers) > 0 && c.RemoteAddr != "" && !c.Verified && validAction {
- dnsblTookAction = c.checkDnsBl()
- }
-
- if dnsblTookAction == "" && c.Gateway.Config.RequiresVerification && !c.Verified {
- c.SendClientSignal("data", "CAPTCHA NEEDED")
- }
-}
-
-func (c *Client) checkDnsBl() (tookAction string) {
- dnsResult := dnsbl.Lookup(c.Gateway.Config.DnsblServers, c.RemoteAddr)
- if dnsResult.Listed && c.Gateway.Config.DnsblAction == "deny" {
- c.SendIrcError("Blocked by DNSBL")
- c.SendClientSignal("state", "closed", "dnsbl_listed")
- c.StartShutdown("dnsbl")
- tookAction = "deny"
- } else if dnsResult.Listed && c.Gateway.Config.DnsblAction == "verify" {
- c.RequiresVerification = true
- c.SendClientSignal("data", "CAPTCHA NEEDED")
- tookAction = "verify"
- }
-
- return
-}
-
-func (c *Client) IsShuttingDown() bool {
- c.shuttingDownLock.Lock()
- defer c.shuttingDownLock.Unlock()
- return c.shuttingDown
-}
-
-func (c *Client) StartShutdown(reason string) {
- c.shuttingDownLock.Lock()
- defer c.shuttingDownLock.Unlock()
-
- c.Log(1, "StartShutdown(%s) ShuttingDown=%t", reason, c.shuttingDown)
- if !c.shuttingDown {
- c.shuttingDown = true
- c.State = ClientStateEnding
-
- switch reason {
- case "upstream_closed":
- c.Log(2, "Upstream closed the connection")
- case "err_connecting_upstream":
- case "err_no_upstream":
- // Error has been logged already
- case "client_closed":
- c.Log(2, "Client disconnected")
- default:
- c.Log(2, "Closed: %s", reason)
- }
-
- close(c.Signals)
- c.EndWG.Done()
- }
-}
-
-func (c *Client) SendClientSignal(signal string, args ...string) {
- c.shuttingDownLock.Lock()
- defer c.shuttingDownLock.Unlock()
-
- if !c.shuttingDown {
- switch len(args) {
- case 0:
- c.Signals <- ClientSignal{signal}
- case 1:
- c.Signals <- ClientSignal{signal, args[0]}
- case 2:
- c.Signals <- ClientSignal{signal, args[0], args[1]}
- }
- }
-}
-
-func (c *Client) SendIrcError(message string) {
- c.SendClientSignal("data", "ERROR :"+message)
-}
-
-func (c *Client) SendIrcFail(params ...string) {
- failMessage := irc.Message{
- Command: "FAIL",
- Params: params,
- }
- c.SendClientSignal("data", failMessage.ToLine())
-}
-
-func (c *Client) connectUpstream() {
- client := c
-
- c.UpstreamStarted = true
-
- var upstreamConfig ConfigUpstream
-
- if client.DestHost == "" {
- client.Log(2, "Using configured upstream")
- var err error
- upstreamConfig, err = c.Gateway.findUpstream()
- if err != nil {
- client.Log(3, "No upstreams available")
- client.SendIrcError("The server has not been configured")
- client.StartShutdown("err_no_upstream")
- return
- }
- } else {
- if !c.Gateway.isIrcAddressAllowed(client.DestHost) {
- client.Log(2, "Server %s is not allowed. Closing connection", client.DestHost)
- client.SendIrcError("Not allowed to connect to " + client.DestHost)
- client.SendClientSignal("state", "closed", "err_forbidden")
- client.StartShutdown("err_no_upstream")
- return
- }
-
- client.Log(2, "Using client given upstream")
- upstreamConfig = c.configureUpstream()
- }
-
- c.UpstreamConfig = &upstreamConfig
-
- hook := &HookIrcConnectionPre{
- Client: client,
- UpstreamConfig: &upstreamConfig,
- }
- hook.Dispatch("irc.connection.pre")
- if hook.Halt {
- client.SendClientSignal("state", "closed", "err_forbidden")
- client.StartShutdown("err_connecting_upstream")
- return
- }
-
- client.State = ClientStateConnecting
-
- upstream, upstreamErr := client.makeUpstreamConnection()
- if upstreamErr != nil {
- // Error handling was already managed in makeUpstreamConnection()
- return
- }
-
- client.State = ClientStateRegistering
-
- client.upstream = upstream
- client.readUpstream()
- client.writeWebircLines(upstream)
- client.maybeSendPass(upstream)
- client.SendClientSignal("state", "connected")
-}
-
-func (c *Client) makeUpstreamConnection() (io.ReadWriteCloser, error) {
- client := c
- upstreamConfig := c.UpstreamConfig
-
- var connection io.ReadWriteCloser
-
- if upstreamConfig.Proxy == nil {
- // Connect directly to the IRCd
- dialer := net.Dialer{}
- dialer.Timeout = time.Second * time.Duration(upstreamConfig.Timeout)
-
- if upstreamConfig.LocalAddr != "" {
- parsedIP := net.ParseIP(upstreamConfig.LocalAddr)
- if parsedIP != nil {
- dialer.LocalAddr = &net.TCPAddr{
- IP: parsedIP,
- Port: 0,
- }
- } else {
- client.Log(3, "Failed to parse localaddr for upstream connection \"%s\"", upstreamConfig.LocalAddr)
- }
- }
-
- var conn net.Conn
- var connErr error
- if upstreamConfig.Protocol == "unix" {
- conn, connErr = dialer.Dial("unix", upstreamConfig.Hostname)
- } else {
- upstreamStr := fmt.Sprintf("%s:%d", upstreamConfig.Hostname, upstreamConfig.Port)
- conn, connErr = dialer.Dial(upstreamConfig.Protocol, upstreamStr)
- }
-
- if connErr != nil {
- client.Log(3, "Error connecting to the upstream IRCd. %s", connErr.Error())
- errString := ""
- if errString = typeOfErr(connErr); errString != "" {
- errString = "err_" + errString
- }
- client.SendClientSignal("state", "closed", errString)
- client.StartShutdown("err_connecting_upstream")
- return nil, errors.New("error connecting upstream")
- }
-
- // Add the ports into the identd before possible TLS handshaking. If we do it after then
- // there's a good chance the identd lookup will occur before the handshake has finished
- if c.Gateway.Config.Identd {
- // Keep track of the upstreams local and remote port numbers
- _, lPortStr, _ := net.SplitHostPort(conn.LocalAddr().String())
- client.IrcState.LocalPort, _ = strconv.Atoi(lPortStr)
- _, rPortStr, _ := net.SplitHostPort(conn.RemoteAddr().String())
- client.IrcState.RemotePort, _ = strconv.Atoi(rPortStr)
-
- c.Gateway.identdServ.AddIdent(client.IrcState.LocalPort, client.IrcState.RemotePort, client.IrcState.Username, "")
- }
-
- if upstreamConfig.TLS {
- tlsConfig := &tls.Config{InsecureSkipVerify: true}
- tlsConn := tls.Client(conn, tlsConfig)
- err := tlsConn.Handshake()
- if err != nil {
- client.Log(3, "Error connecting to the upstream IRCd. %s", err.Error())
- client.SendClientSignal("state", "closed", "err_tls")
- client.StartShutdown("err_connecting_upstream")
- return nil, errors.New("error connecting upstream")
- }
-
- conn = net.Conn(tlsConn)
- }
-
- connection = conn
- }
-
- if upstreamConfig.Proxy != nil {
- // Connect to the IRCd via a proxy
- conn := proxy.MakeKiwiProxyConnection()
- conn.DestHost = upstreamConfig.Hostname
- conn.DestPort = upstreamConfig.Port
- conn.DestTLS = upstreamConfig.TLS
- conn.Username = upstreamConfig.Proxy.Username
- conn.ProxyInterface = upstreamConfig.Proxy.Interface
-
- dialErr := conn.Dial(fmt.Sprintf(
- "%s:%d",
- upstreamConfig.Proxy.Hostname,
- upstreamConfig.Proxy.Port,
- ))
-
- if dialErr != nil {
- errString := ""
- if errString = typeOfErr(dialErr); errString != "" {
- errString = "err_" + errString
- } else {
- errString = "err_proxy"
- }
- client.Log(3,
- "Error connecting to the kiwi proxy, %s:%d. %s",
- upstreamConfig.Proxy.Hostname,
- upstreamConfig.Proxy.Port,
- dialErr.Error(),
- )
-
- client.SendClientSignal("state", "closed", errString)
- client.StartShutdown("err_connecting_upstream")
- return nil, errors.New("error connecting upstream")
- }
-
- connection = conn
- }
-
- return connection, nil
-}
-
-func (c *Client) writeWebircLines(upstream io.ReadWriteCloser) {
- // Send any WEBIRC lines
- if c.UpstreamConfig.WebircPassword == "" {
- c.Log(1, "No webirc to send")
- return
- }
-
- gatewayName := "webircgateway"
- if c.Gateway.Config.GatewayName != "" {
- gatewayName = c.Gateway.Config.GatewayName
- }
- if c.UpstreamConfig.GatewayName != "" {
- gatewayName = c.UpstreamConfig.GatewayName
- }
-
- webircTags := c.buildWebircTags()
- if strings.Contains(webircTags, " ") {
- webircTags = ":" + webircTags
- }
-
- clientHostname := c.RemoteHostname
- if c.Gateway.Config.ClientHostname != "" {
- clientHostname = makeClientReplacements(c.Gateway.Config.ClientHostname, c)
- }
-
- remoteAddr := c.RemoteAddr
- // Prefix IPv6 addresses that start with a : so they can be sent as an individual IRC
- // parameter. eg. ::1 would not parse correctly as a parameter, while 0::1 will
- if strings.HasPrefix(remoteAddr, ":") {
- remoteAddr = "0" + remoteAddr
- }
-
- webircLine := fmt.Sprintf(
- "WEBIRC %s %s %s %s %s\n",
- c.UpstreamConfig.WebircPassword,
- gatewayName,
- clientHostname,
- remoteAddr,
- webircTags,
- )
- c.Log(1, "->upstream: %s", webircLine)
- upstream.Write([]byte(webircLine))
-}
-
-func (c *Client) maybeSendPass(upstream io.ReadWriteCloser) {
- if c.UpstreamConfig.ServerPassword == "" {
- return
- }
- c.SentPass = true
- passLine := fmt.Sprintf(
- "PASS %s\n",
- c.UpstreamConfig.ServerPassword,
- )
- c.Log(1, "->upstream: %s", passLine)
- upstream.Write([]byte(passLine))
-}
-
-func (c *Client) processLineToUpstream(data string) {
- client := c
- upstreamConfig := c.UpstreamConfig
-
- if strings.HasPrefix(data, "PASS ") && c.SentPass {
- // Hijack the PASS command if we already sent a pass command
- return
- } else if strings.HasPrefix(data, "USER ") {
- // Hijack the USER command as we may have some overrides
- data = fmt.Sprintf(
- "USER %s 0 * :%s",
- client.IrcState.Username,
- client.IrcState.RealName,
- )
- } else if strings.HasPrefix(strings.ToUpper(data), "QUIT ") {
- client.SeenQuit = true
- }
-
- message, _ := irc.ParseLine(data)
-
- hook := &HookIrcLine{
- Client: client,
- UpstreamConfig: upstreamConfig,
- Line: data,
- Message: message,
- ToServer: true,
- }
- hook.Dispatch("irc.line")
- if hook.Halt {
- return
- }
-
- // Plugins may have modified the data
- data = hook.Line
-
- c.TrafficLog(true, false, data)
- data = utf8ToOther(data, client.Encoding)
- if data == "" {
- client.Log(1, "Failed to encode into '%s'. Dropping data", c.Encoding)
- return
- }
-
- if client.upstream != nil {
- client.upstream.Write([]byte(data + "\r\n"))
- } else {
- client.Log(2, "Tried sending data upstream before connected")
- }
-}
-
-func (c *Client) handleLineFromUpstream(data string) {
- client := c
- upstreamConfig := c.UpstreamConfig
-
- message, _ := irc.ParseLine(data)
-
- hook := &HookIrcLine{
- Client: client,
- UpstreamConfig: upstreamConfig,
- Line: data,
- Message: message,
- ToServer: false,
- }
- hook.Dispatch("irc.line")
- if hook.Halt {
- return
- }
-
- // Plugins may have modified the data
- data = hook.Line
-
- if data == "" {
- return
- }
-
- data = ensureUtf8(data, client.Encoding)
- if data == "" {
- client.Log(1, "Failed to decode as 'UTF-8'. Dropping data")
- return
- }
-
- data = client.ProcessLineFromUpstream(data)
- if data == "" {
- return
- }
-
- client.SendClientSignal("data", data)
-}
-
-func typeOfErr(err error) string {
- if err == nil {
- return ""
- }
-
- if netError, ok := err.(net.Error); ok && netError.Timeout() {
- return "timeout"
- }
-
- switch t := err.(type) {
- case *proxy.ConnError:
- switch t.Type {
- case "conn_reset":
- return ""
- case "conn_refused":
- return "refused"
- case "not_found":
- return "unknown_host"
- case "conn_timeout":
- return "timeout"
- default:
- return ""
- }
-
- case *net.OpError:
- if t.Op == "dial" {
- return "unknown_host"
- } else if t.Op == "read" {
- return "refused"
- }
-
- case syscall.Errno:
- if t == syscall.ECONNREFUSED {
- return "refused"
- }
- }
-
- return ""
-}
-
-func (c *Client) readUpstream() {
- client := c
-
- // Data from upstream to client
- go func() {
- reader := bufio.NewReader(client.upstream)
- for {
- data, err := reader.ReadString('\n')
- if err != nil {
- break
- }
-
- data = strings.Trim(data, "\n\r")
- client.UpstreamRecv <- data
- }
-
- close(client.UpstreamRecv)
- client.upstream.Close()
- client.upstream = nil
-
- if client.IrcState.RemotePort > 0 {
- c.Gateway.identdServ.RemoveIdent(client.IrcState.LocalPort, client.IrcState.RemotePort, "")
- }
- }()
-}
-
-// Handle lines sent from the client
-func (c *Client) clientLineWorker() {
- for {
- shouldQuit, _ := c.handleDataLine()
- if shouldQuit {
- break
- }
-
- }
-
- c.Log(1, "leaving clientLineWorker")
-}
-
-func (c *Client) handleDataLine() (shouldQuit bool, hadErr bool) {
- defer func() {
- if err := recover(); err != nil {
- c.Log(3, fmt.Sprint("Error handling data ", err))
- fmt.Println("Error handling data", err)
- debug.PrintStack()
- shouldQuit = false
- hadErr = true
- }
- }()
-
- // We only want to send data upstream if we have an upstream connection
- upstreamSend := c.UpstreamSend
- if c.upstream == nil {
- upstreamSend = nil
- }
-
- select {
- case clientData, ok := <-c.ThrottledRecv.Output:
- if !ok {
- c.Log(1, "client.Recv closed")
- if !c.SeenQuit && c.Gateway.Config.SendQuitOnClientClose != "" && c.State == ClientStateEnding {
- c.processLineToUpstream("QUIT :" + c.Gateway.Config.SendQuitOnClientClose)
- }
-
- c.StartShutdown("client_closed")
-
- if c.upstream != nil {
- c.upstream.Close()
- }
- return true, false
- }
- c.Log(1, "in c.ThrottledRecv.Output")
- c.TrafficLog(false, true, clientData)
-
- clientLine, err := c.ProcessLineFromClient(clientData)
- if err == nil && clientLine != "" {
- c.UpstreamSend <- clientLine
- }
-
- case line, ok := <-upstreamSend:
- if !ok {
- c.Log(1, "client.UpstreamSend closed")
- return true, false
- }
- c.Log(1, "in .UpstreamSend")
- c.processLineToUpstream(line)
-
- case upstreamData, ok := <-c.UpstreamRecv:
- if !ok {
- c.Log(1, "client.UpstreamRecv closed")
- c.SendClientSignal("state", "closed")
- c.StartShutdown("upstream_closed")
- return true, false
- }
- c.Log(1, "in .UpstreamRecv")
- c.TrafficLog(true, true, upstreamData)
-
- c.handleLineFromUpstream(upstreamData)
- }
-
- return false, false
-}
-
-// configureUpstream - Generate an upstream configuration from the information set on the client instance
-func (c *Client) configureUpstream() ConfigUpstream {
- upstreamConfig := ConfigUpstream{}
- upstreamConfig.Hostname = c.DestHost
- upstreamConfig.Port = c.DestPort
- upstreamConfig.TLS = c.DestTLS
- upstreamConfig.Timeout = c.Gateway.Config.GatewayTimeout
- upstreamConfig.Throttle = c.Gateway.Config.GatewayThrottle
- upstreamConfig.WebircPassword = c.Gateway.findWebircPassword(c.DestHost)
- upstreamConfig.Protocol = c.Gateway.Config.GatewayProtocol
- upstreamConfig.LocalAddr = c.Gateway.Config.GatewayLocalAddr
-
- return upstreamConfig
-}
-
-func (c *Client) buildWebircTags() string {
- str := ""
- for key, val := range c.Tags {
- if str != "" {
- str += " "
- }
-
- if val == "" {
- str += key
- } else {
- str += key + "=" + val
- }
- }
-
- return str
-}