package main import ( "fmt" "log" "net" "strings" "sync" "time" ) // LinkedServer represents a connection to another IRC server type LinkedServer struct { name string conn net.Conn host string port int password string hub bool description string connected bool lastPing time.Time server *Server mu sync.RWMutex } // LinkConfig represents configuration for server linking type LinkConfig struct { Enable bool `json:"enable"` ServerPort int `json:"server_port"` Password string `json:"password"` Hub bool `json:"hub"` AutoConnect bool `json:"auto_connect"` Links []struct { Name string `json:"name"` Host string `json:"host"` Port int `json:"port"` Password string `json:"password"` AutoConnect bool `json:"auto_connect"` Hub bool `json:"hub"` Description string `json:"description"` } `json:"links"` } // ServerMessage represents a server-to-server message type ServerMessage struct { Prefix string Command string Params []string Source string Target string } // NewLinkedServer creates a new linked server connection func NewLinkedServer(name, host string, port int, password string, hub bool, description string, server *Server) *LinkedServer { return &LinkedServer{ name: name, host: host, port: port, password: password, hub: hub, description: description, server: server, connected: false, lastPing: time.Now(), } } // Connect establishes a connection to the remote server func (ls *LinkedServer) Connect() error { ls.mu.Lock() defer ls.mu.Unlock() if ls.connected { return fmt.Errorf("server %s is already connected", ls.name) } // Connect to remote server var addr string if strings.Contains(ls.host, ":") { // IPv6 address, enclose in brackets addr = fmt.Sprintf("[%s]:%d", ls.host, ls.port) } else { addr = fmt.Sprintf("%s:%d", ls.host, ls.port) } conn, err := net.DialTimeout("tcp", addr, 30*time.Second) if err != nil { return fmt.Errorf("failed to connect to %s: %v", ls.name, err) } ls.conn = conn ls.connected = true log.Printf("Connected to server %s at %s", ls.name, addr) // Start handling the connection go ls.Handle() // Send server introduction ls.SendServerAuth() return nil } // Disconnect closes the connection to the remote server func (ls *LinkedServer) Disconnect() { ls.mu.Lock() defer ls.mu.Unlock() if !ls.connected { return } if ls.conn != nil { ls.conn.Close() ls.conn = nil } ls.connected = false log.Printf("Disconnected from server %s", ls.name) } // SendServerAuth sends authentication to the remote server func (ls *LinkedServer) SendServerAuth() { // Send PASS command ls.SendMessage(fmt.Sprintf("PASS %s", ls.password)) // Send SERVER command ls.SendMessage(fmt.Sprintf("SERVER %s 1 :%s", ls.server.config.Server.Name, ls.server.config.Server.Description)) log.Printf("Sent authentication to server %s", ls.name) } // SendMessage sends a raw message to the linked server func (ls *LinkedServer) SendMessage(message string) error { ls.mu.RLock() defer ls.mu.RUnlock() if !ls.connected || ls.conn == nil { return fmt.Errorf("server %s is not connected", ls.name) } _, err := fmt.Fprintf(ls.conn, "%s\r\n", message) if err != nil { log.Printf("Error sending message to server %s: %v", ls.name, err) ls.connected = false } return err } // Handle processes messages from the linked server func (ls *LinkedServer) Handle() { defer func() { if r := recover(); r != nil { log.Printf("Panic in server link handler for %s: %v", ls.name, r) } ls.Disconnect() }() scanner := net.Conn(ls.conn) buffer := make([]byte, 4096) for ls.connected { // Set read deadline ls.conn.SetReadDeadline(time.Now().Add(5 * time.Minute)) n, err := scanner.Read(buffer) if err != nil { if ls.connected { log.Printf("Read error from server %s: %v", ls.name, err) } break } data := string(buffer[:n]) lines := strings.Split(strings.TrimSpace(data), "\n") for _, line := range lines { line = strings.TrimSpace(line) if line == "" { continue } ls.handleServerMessage(line) } } } // handleServerMessage processes a single message from the linked server func (ls *LinkedServer) handleServerMessage(line string) { log.Printf("Received from server %s: %s", ls.name, line) parts := strings.Fields(line) if len(parts) == 0 { return } var command string var params []string // Parse message prefix if parts[0][0] == ':' { parts = parts[1:] } if len(parts) == 0 { return } command = strings.ToUpper(parts[0]) params = parts[1:] // Handle server-to-server commands switch command { case "PASS": ls.handleServerPass(params) case "SERVER": ls.handleServerIntro(params) case "PING": ls.handleServerPing(params) case "PONG": ls.handleServerPong() case "SQUIT": ls.handleServerQuit(params) case "NICK": ls.handleRemoteNick(params) case "USER": ls.handleRemoteUser(params) case "JOIN": ls.handleRemoteJoin(params) case "PART": ls.handleRemotePart(params) case "QUIT": ls.handleRemoteQuit(params) case "PRIVMSG": ls.handleRemotePrivmsg(params) default: log.Printf("Unknown server command from %s: %s", ls.name, command) } } // handleServerPass handles PASS command from remote server func (ls *LinkedServer) handleServerPass(params []string) { if len(params) < 1 { log.Printf("Invalid PASS from server %s", ls.name) ls.Disconnect() return } password := params[0] if password != ls.password { log.Printf("Invalid password from server %s", ls.name) ls.Disconnect() return } log.Printf("Server %s authenticated successfully", ls.name) } // handleServerIntro handles SERVER command from remote server func (ls *LinkedServer) handleServerIntro(params []string) { if len(params) < 3 { log.Printf("Invalid SERVER from server %s", ls.name) ls.Disconnect() return } serverName := params[0] hopCount := params[1] description := strings.Join(params[2:], " ") if description[0] == ':' { description = description[1:] } log.Printf("Server introduced: %s (hops: %s) - %s", serverName, hopCount, description) // Send our user list and channel list to the remote server ls.sendBurst() } // handleServerPing handles PING from remote server func (ls *LinkedServer) handleServerPing(params []string) { if len(params) < 1 { return } token := params[0] ls.SendMessage(fmt.Sprintf("PONG %s %s", ls.server.config.Server.Name, token)) } // handleServerPong handles PONG from remote server func (ls *LinkedServer) handleServerPong() { ls.mu.Lock() ls.lastPing = time.Now() ls.mu.Unlock() } // handleServerQuit handles SQUIT from remote server func (ls *LinkedServer) handleServerQuit(params []string) { if len(params) >= 1 { reason := strings.Join(params, " ") log.Printf("Server %s quit: %s", ls.name, reason) } ls.Disconnect() } // sendBurst sends initial data to newly connected server func (ls *LinkedServer) sendBurst() { // Send all local users for _, client := range ls.server.GetClients() { if client.IsRegistered() { ls.SendMessage(fmt.Sprintf("NICK %s 1 %d %s %s %s %s :%s", client.Nick(), time.Now().Unix(), client.User(), client.Host(), ls.server.config.Server.Name, client.Nick(), client.Realname())) } } // Send all channels and their members for _, channel := range ls.server.GetChannels() { // Send channel creation ls.SendMessage(fmt.Sprintf("NJOIN %s :", channel.name)) // Send channel members var members []string for _, client := range channel.GetClients() { members = append(members, client.Nick()) } if len(members) > 0 { ls.SendMessage(fmt.Sprintf("NJOIN %s :%s", channel.name, strings.Join(members, " "))) } // Send channel topic if exists if channel.topic != "" { ls.SendMessage(fmt.Sprintf("TOPIC %s :%s", channel.name, channel.topic)) } } // End of burst ls.SendMessage("EOB") log.Printf("Sent burst to server %s", ls.name) } // Remote user handling functions func (ls *LinkedServer) handleRemoteNick(params []string) { // Handle remote NICK command log.Printf("Remote NICK from %s: %s", ls.name, strings.Join(params, " ")) } func (ls *LinkedServer) handleRemoteUser(params []string) { // Handle remote USER command log.Printf("Remote USER from %s: %s", ls.name, strings.Join(params, " ")) } func (ls *LinkedServer) handleRemoteJoin(params []string) { // Handle remote JOIN command log.Printf("Remote JOIN from %s: %s", ls.name, strings.Join(params, " ")) } func (ls *LinkedServer) handleRemotePart(params []string) { // Handle remote PART command log.Printf("Remote PART from %s: %s", ls.name, strings.Join(params, " ")) } func (ls *LinkedServer) handleRemoteQuit(params []string) { // Handle remote QUIT command log.Printf("Remote QUIT from %s: %s", ls.name, strings.Join(params, " ")) } func (ls *LinkedServer) handleRemotePrivmsg(params []string) { // Handle remote PRIVMSG command log.Printf("Remote PRIVMSG from %s: %s", ls.name, strings.Join(params, " ")) } // IsConnected returns whether the server is currently connected func (ls *LinkedServer) IsConnected() bool { ls.mu.RLock() defer ls.mu.RUnlock() return ls.connected } // Name returns the server name func (ls *LinkedServer) Name() string { return ls.name } // Description returns the server description func (ls *LinkedServer) Description() string { return ls.description }