diff options
-rw-r--r-- | .golangci.yml | 1 | ||||
-rw-r--r-- | ghost.go | 190 | ||||
-rw-r--r-- | main.go | 4 | ||||
-rw-r--r-- | types.go | 7 |
4 files changed, 179 insertions, 23 deletions
diff --git a/.golangci.yml b/.golangci.yml index c535757..cbee598 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -32,3 +32,4 @@ linters-settings: - golang.org/x/net/proxy - google.golang.org/genai - golang.org/x/net/html + - github.com/ergochat/irc-go/ircmsg @@ -3,49 +3,193 @@ package main import ( "bufio" "crypto/tls" + "errors" "io" "log" "net" + "os" "sync" ircgomsg "github.com/ergochat/irc-go/ircmsg" + "golang.org/x/net/proxy" ) -func ghost(serverAddress, listenAddress string) { - listener, err := net.Listen("tcp", listenAddress) - if err != nil { - log.Fatalf("Failed to listen on %s: %v", listenAddress, err) +func RunGhost(ghostNetwork GhostNetwork, name string) { + var listener net.Listener + + var err error + + if ghostNetwork.ServerKey == "" || ghostNetwork.ServerCert == "" { + log.Printf("Ghost %s: either one or both of ServerKey and ServerCert were not provided. ghosty will not be listening on TLS.", name) + + listener, err = net.Listen("tcp", ghostNetwork.ListenAddress) + if err != nil { + log.Fatalf("Ghost %s: Failed to listen on %s: %v", name, ghostNetwork.ListenAddress, err) + } + } else { + tlsCert, err := os.ReadFile(ghostNetwork.ServerCert) + if err != nil { + log.Fatalf("Ghost %s: Failed to read TLS certificate file: %v", name, err) + } + + tlsKey, err := os.ReadFile(ghostNetwork.ServerKey) + if err != nil { + log.Fatalf("Ghost %s: Failed to read TLS key file: %v", name, err) + } + + cert, err := tls.X509KeyPair(tlsCert, tlsKey) + if err != nil { + log.Fatalf("Ghost %s: Failed to load TLS key pair: %v", name, err) + } + + listener, err = tls.Listen("tcp", ghostNetwork.ListenAddress, &tls.Config{ + MinVersion: tls.VersionTLS13, + Certificates: []tls.Certificate{cert}, + }) + if err != nil { + log.Fatalf("Ghost %s: Failed to listen on %s: %v", name, ghostNetwork.ListenAddress, err) + } } + defer listener.Close() - log.Printf("IRC Bouncer listening on %s", listenAddress) - log.Printf("Connecting clients to IRC server: %s", serverAddress) + log.Printf("Ghost %s: IRC Bouncer listening on %s", name, ghostNetwork.ListenAddress) + log.Printf("Ghost %s: Connecting clients to IRC server: %s", name, ghostNetwork.ServerAddress) for { clientConn, err := listener.Accept() if err != nil { - log.Printf("Failed to accept client connection: %v", err) + log.Printf("Ghost %s: Failed to accept client connection: %v", name, err) continue } - go handleClientConnection(clientConn, serverAddress) + go handleClientConnection(clientConn, ghostNetwork, name) + } +} + +func proxyConnectinoTLS(clientConn net.Conn, ghostNetwork GhostNetwork, name string) *tls.Conn { + dialer, err := proxy.SOCKS5("tcp", ghostNetwork.UpstreamProxy, nil, proxy.Direct) + if err != nil { + log.Fatalf("Ghost %s: Failed to create SOCKS5 dialer: %v", name, err) + } + + proxyCon, err := dialer.Dial("tcp", ghostNetwork.ServerAddress) + if err != nil { + log.Printf("Ghost %s: Failed to connect to IRC server %s via proxy %s: %v", name, ghostNetwork.ServerAddress, ghostNetwork.UpstreamProxy, err) + clientConn.Close() + + return nil + } + + var tlsConfig *tls.Config + + if ghostNetwork.CertPath != "" && ghostNetwork.KeyPath != "" { + clientCert, err := tls.LoadX509KeyPair(ghostNetwork.CertPath, ghostNetwork.KeyPath) + if err != nil { + log.Fatalf("Ghost %s: Failed to load client TLS key pair: %v", name, err) + } + + tlsConfig = &tls.Config{ + ServerName: ghostNetwork.ServerName, + Certificates: []tls.Certificate{clientCert}, + InsecureSkipVerify: ghostNetwork.SkipTLSVerify, + MinVersion: tls.VersionTLS13, + } + } else { + tlsConfig = &tls.Config{ + ServerName: ghostNetwork.ServerName, + InsecureSkipVerify: ghostNetwork.SkipTLSVerify, + MinVersion: tls.VersionTLS13, + } + } + + conn := tls.Client(proxyCon, tlsConfig) + + err = conn.Handshake() + if err != nil { + log.Printf("Ghost %s: TLS handshake with IRC server %s failed: %v", name, ghostNetwork.ServerAddress, err) + clientConn.Close() + + return nil + } + + return conn +} + +func connectionTLS(clientConn net.Conn, ghostNetwork GhostNetwork, name string) *tls.Conn { + clientCert, err := tls.LoadX509KeyPair(ghostNetwork.CertPath, ghostNetwork.KeyPath) + if err != nil { + log.Fatalf("Ghost %s: Failed to load client TLS key pair: %v", name, err) + } + + tlsConfig := &tls.Config{ + ServerName: ghostNetwork.ServerName, + Certificates: []tls.Certificate{clientCert}, + InsecureSkipVerify: ghostNetwork.SkipTLSVerify, + MinVersion: tls.VersionTLS13, + } + + conn, err := tls.Dial("tcp", ghostNetwork.ServerAddress, tlsConfig) + if err != nil { + log.Printf("Ghost %s: Failed to connect to IRC server %s with TLS: %v", name, ghostNetwork.ServerAddress, err) + clientConn.Close() + + return nil } + + return conn } -func handleClientConnection(clientConn net.Conn, serverAddress string) { - log.Printf("Client connected from: %s", clientConn.RemoteAddr()) +func proxyConnection(clientConn net.Conn, ghostNetwork GhostNetwork, name string) net.Conn { + dialer, err := proxy.SOCKS5("tcp", ghostNetwork.UpstreamProxy, nil, proxy.Direct) + if err != nil { + log.Fatalf("Ghost %s: Failed to create SOCKS5 dialer: %v", name, err) + } - // ircServerConn, err := net.Dial("tcp", serverAddress) - ircServerConn, err := tls.Dial("tcp", serverAddress, nil) + proxyCon, err := dialer.Dial("tcp", ghostNetwork.ServerAddress) if err != nil { - log.Printf("Failed to connect to IRC server %s: %v", serverAddress, err) + log.Printf("Ghost %s: Failed to connect to IRC server %s via proxy %s: %v", name, ghostNetwork.ServerAddress, ghostNetwork.UpstreamProxy, err) clientConn.Close() + return nil + } + + return proxyCon +} + +func handleClientConnection(clientConn net.Conn, ghostNetwork GhostNetwork, name string) { + log.Printf("Ghost %s: Client connected from: %s", name, clientConn.RemoteAddr()) + + var ircServerConn net.Conn + + var err error + + if ghostNetwork.UpstreamProxy == "" { + if ghostNetwork.UseTLS { + ircServerConn = connectionTLS(clientConn, ghostNetwork, name) + } else { + ircServerConn, err = net.Dial("tcp", ghostNetwork.ServerAddress) + if err != nil { + log.Printf("Ghost %s: Failed to connect to IRC server %s: %v", name, ghostNetwork.ServerAddress, err) + clientConn.Close() + + return + } + } + } else { + if ghostNetwork.UseTLS { + ircServerConn = proxyConnectinoTLS(clientConn, ghostNetwork, name) + } else { + ircServerConn = proxyConnection(clientConn, ghostNetwork, name) + } + } + + if ircServerConn == nil { return } - log.Printf("Successfully connected to IRC server: %s", serverAddress) + log.Printf("Ghost %s: Successfully connected to IRC server: %s", name, ghostNetwork.ServerAddress) done := make(chan struct{}) @@ -59,23 +203,23 @@ func handleClientConnection(clientConn net.Conn, serverAddress string) { }) } - go relay(clientConn, ircServerConn, closeConnections) + go relay(ghostNetwork.LogRaw, clientConn, ircServerConn, closeConnections, name) - go relay(ircServerConn, clientConn, closeConnections) + go relay(ghostNetwork.LogRaw, ircServerConn, clientConn, closeConnections, name) <-done - log.Printf("Connection closed for client: %s", clientConn.RemoteAddr()) } -func relay(src, dest net.Conn, closer func()) { +func relay(logRaw bool, src, dest net.Conn, closer func(), name string) { reader := bufio.NewReader(src) for { line, err := reader.ReadString('\n') if err != nil { - if err != io.EOF { - log.Printf("Relay read error on: %v", err) + if !errors.Is(err, io.EOF) { + log.Printf("Ghost %s: Relay read error on: %v", name, err) } + closer() return @@ -83,7 +227,7 @@ func relay(src, dest net.Conn, closer func()) { msg, err := ircgomsg.ParseLine(line) if err != nil { - log.Printf("Failed to parse IRC message: %v", err) + log.Printf("Ghost %s: Failed to parse IRC message: %v", name, err) } if msg.Command == "PRIVMSG" { @@ -92,7 +236,9 @@ func relay(src, dest net.Conn, closer func()) { if msg.Command == "MSG" { } - log.Println(msg) + if logRaw { + log.Printf("Ghost %s: %v", name, msg) + } _, err = io.WriteString(dest, line) if err != nil { @@ -1008,6 +1008,10 @@ func main() { } } + for k, v := range config.Ghost { + go RunGhost(v, k) + } + if *prof { go func() { err := http.ListenAndServe(":6060", nil) @@ -339,7 +339,11 @@ type GhostRuleSet struct { } type GhostNetwork struct { - ServerAddress string `toml:"serverAddress"` + ServerCert string `toml:"serverCert"` + ServerKey string `toml:"serverKey"` + ServerAddress string `toml:"ServerAddress"` + ServerName string `toml:"serverName"` + UpstreamProxy string `toml:"upstreamProxy"` UseTLS bool `toml:"useTLS"` SkipTLSVerify bool `toml:"skipTLSVerify"` Nick string `toml:"nick"` @@ -353,4 +357,5 @@ type GhostNetwork struct { Instructions []string `toml:"instructions"` Prompt string `toml:"prompt"` GhostRuleSets []GhostRuleSet `toml:"ghostRuleSets"` + LogRaw bool `toml:"logRaw"` } |