diff options
Diffstat (limited to '')
| -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"`  } | 
