Added all of the existing code
This commit is contained in:
397
linking.go
Normal file
397
linking.go
Normal file
@@ -0,0 +1,397 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user