aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorterminaldweller <devi@terminaldweller.com>2025-08-23 23:15:33 +0000
committerterminaldweller <devi@terminaldweller.com>2025-08-23 23:15:33 +0000
commit7b4a951add399fd6e6984d714656902c3d58d222 (patch)
tree5636e4c4e819e4bb4be5eeb84a4f54b62621f3cc
parentghost WIP (diff)
downloadmilla-7b4a951add399fd6e6984d714656902c3d58d222.tar.gz
milla-7b4a951add399fd6e6984d714656902c3d58d222.zip
the plumbing for ghost is done.WIPHEADmain
-rw-r--r--.golangci.yml1
-rw-r--r--ghost.go190
-rw-r--r--main.go4
-rw-r--r--types.go7
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
diff --git a/ghost.go b/ghost.go
index d3f4396..8315625 100644
--- a/ghost.go
+++ b/ghost.go
@@ -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 {
diff --git a/main.go b/main.go
index ae0273b..05b4ff4 100644
--- a/main.go
+++ b/main.go
@@ -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)
diff --git a/types.go b/types.go
index 2939645..5cc7a3c 100644
--- a/types.go
+++ b/types.go
@@ -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"`
}