package main import ( "crypto/sha256" "encoding/hex" "fmt" "log" "strings" "time" ) // ServicesManager handles all network services type ServicesManager struct { server *Server config *ServicesConfig nickServ *NickServ chanServ *ChanServ operServ *OperServ memoServ *MemoServ enabled bool } // NickServ handles nickname registration and authentication type NickServ struct { manager *ServicesManager nick string accounts map[string]*NickAccount } // ChanServ handles channel registration and management type ChanServ struct { manager *ServicesManager nick string channels map[string]*RegisteredChannel } // OperServ handles network operator services type OperServ struct { manager *ServicesManager nick string } // MemoServ handles user-to-user messages type MemoServ struct { manager *ServicesManager nick string memos map[string][]*Memo } // NickAccount represents a registered nickname type NickAccount struct { Nick string `json:"nick"` PasswordHash string `json:"password_hash"` Email string `json:"email"` RegisterTime time.Time `json:"register_time"` LastSeen time.Time `json:"last_seen"` Settings map[string]string `json:"settings"` AccessList []string `json:"access_list"` Flags []string `json:"flags"` } // RegisteredChannel represents a registered channel type RegisteredChannelServices struct { Name string `json:"name"` Founder string `json:"founder"` RegisterTime time.Time `json:"register_time"` Topic string `json:"topic"` Modes string `json:"modes"` AccessList map[string]*ChannelAccess `json:"access_list"` Settings map[string]string `json:"settings"` AutoModes map[string]string `json:"auto_modes"` } // ChannelAccess represents access levels for channels - using the one from database.go // type ChannelAccess struct { // Nick string `json:"nick"` // Level int `json:"level"` // 0=banned, 1=voice, 2=halfop, 3=op, 4=sop, 5=founder // SetBy string `json:"set_by"` // SetTime time.Time `json:"set_time"` // LastUsed time.Time `json:"last_used"` // } // Memo represents a user memo type Memo struct { From string `json:"from"` To string `json:"to"` Message string `json:"message"` Time time.Time `json:"time"` Read bool `json:"read"` } // ServicesConfig configuration for services type ServicesConfig struct { Enable bool `json:"enable"` NickServ NickServConfig `json:"nickserv"` ChanServ ChanServConfig `json:"chanserv"` OperServ OperServConfig `json:"operserv"` MemoServ MemoServConfig `json:"memoserv"` Database DatabaseConfig `json:"database"` } type NickServConfig struct { Enable bool `json:"enable"` Nick string `json:"nick"` User string `json:"user"` Host string `json:"host"` Realname string `json:"realname"` ExpireTime int `json:"expire_time"` // Days until unused nicks expire IdentifyTimeout int `json:"identify_timeout"` // Seconds to identify before kill EmailVerify bool `json:"email_verify"` RestrictReg bool `json:"restrict_registration"` } type ChanServConfig struct { Enable bool `json:"enable"` Nick string `json:"nick"` User string `json:"user"` Host string `json:"host"` Realname string `json:"realname"` ExpireTime int `json:"expire_time"` // Days until unused channels expire MaxChannels int `json:"max_channels"` // Max channels per user AutoDeop bool `json:"auto_deop"` // Auto-deop users without access } type OperServConfig struct { Enable bool `json:"enable"` Nick string `json:"nick"` User string `json:"user"` Host string `json:"host"` Realname string `json:"realname"` } type MemoServConfig struct { Enable bool `json:"enable"` Nick string `json:"nick"` User string `json:"user"` Host string `json:"host"` Realname string `json:"realname"` MaxMemos int `json:"max_memos"` MemoExpire int `json:"memo_expire"` // Days until memos expire } // NewServicesManager creates a new services manager func NewServicesManager(server *Server, config *ServicesConfig) *ServicesManager { sm := &ServicesManager{ server: server, config: config, enabled: config.Enable, } if config.NickServ.Enable { sm.nickServ = &NickServ{ manager: sm, nick: config.NickServ.Nick, accounts: make(map[string]*NickAccount), } } if config.ChanServ.Enable { sm.chanServ = &ChanServ{ manager: sm, nick: config.ChanServ.Nick, channels: make(map[string]*RegisteredChannel), } } if config.OperServ.Enable { sm.operServ = &OperServ{ manager: sm, nick: config.OperServ.Nick, } } if config.MemoServ.Enable { sm.memoServ = &MemoServ{ manager: sm, nick: config.MemoServ.Nick, memos: make(map[string][]*Memo), } } return sm } // Start initializes and starts all enabled services func (sm *ServicesManager) Start() error { if !sm.enabled { return nil } log.Println("Starting TechIRCd Services...") // Create service clients if sm.nickServ != nil { sm.createServiceClient(sm.config.NickServ.Nick, sm.config.NickServ.User, sm.config.NickServ.Host, sm.config.NickServ.Realname) } if sm.chanServ != nil { sm.createServiceClient(sm.config.ChanServ.Nick, sm.config.ChanServ.User, sm.config.ChanServ.Host, sm.config.ChanServ.Realname) } if sm.operServ != nil { sm.createServiceClient(sm.config.OperServ.Nick, sm.config.OperServ.User, sm.config.OperServ.Host, sm.config.OperServ.Realname) } if sm.memoServ != nil { sm.createServiceClient(sm.config.MemoServ.Nick, sm.config.MemoServ.User, sm.config.MemoServ.Host, sm.config.MemoServ.Realname) } log.Println("Services started successfully") return nil } // createServiceClient creates a virtual client for a service func (sm *ServicesManager) createServiceClient(nick, user, host, realname string) { // Create a virtual service client that appears as a regular user serviceClient := &Client{ nick: nick, user: user, host: host, realname: realname, server: sm.server, modes: make(map[rune]bool), channels: make(map[string]*Channel), capabilities: make(map[string]bool), } // Set service modes serviceClient.modes['S'] = true // Service mode serviceClient.modes['o'] = true // Operator mode // Add to server sm.server.clients[strings.ToLower(nick)] = serviceClient // Send introduction to network sm.server.BroadcastToServers(fmt.Sprintf(":%s NICK %s", sm.server.config.Server.Name, nick)) } // HandleMessage processes messages directed to services func (sm *ServicesManager) HandleMessage(from *Client, target, message string) bool { if !sm.enabled { return false } lowerTarget := strings.ToLower(target) parts := strings.Fields(message) if len(parts) == 0 { return false } command := strings.ToUpper(parts[0]) switch lowerTarget { case strings.ToLower(sm.config.NickServ.Nick): return sm.handleNickServCommand(from, command, parts[1:]) case strings.ToLower(sm.config.ChanServ.Nick): return sm.handleChanServCommand(from) case strings.ToLower(sm.config.OperServ.Nick): return sm.handleOperServCommand(from) case strings.ToLower(sm.config.MemoServ.Nick): return sm.handleMemoServCommand(from) } return false } // NickServ command handlers func (sm *ServicesManager) handleNickServCommand(from *Client, command string, args []string) bool { switch command { case "REGISTER": return sm.handleNickServRegister(from, args) case "IDENTIFY": return sm.handleNickServIdentify(from, args) case "INFO": return sm.handleNickServInfo(from, args) case "DROP": return sm.handleNickServDrop(from, args) case "SET": return sm.handleNickServSet(from) case "ACCESS": return sm.handleNickServAccess(from) case "HELP": return sm.handleNickServHelp(from) default: sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, fmt.Sprintf("Unknown command %s. Type /msg %s HELP for help.", command, sm.config.NickServ.Nick)) return true } } func (sm *ServicesManager) handleNickServRegister(from *Client, args []string) bool { if len(args) < 2 { sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, "Syntax: REGISTER ") return true } nick := strings.ToLower(from.nick) password := args[0] email := args[1] // Check if nick is already registered if _, exists := sm.nickServ.accounts[nick]; exists { sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, "This nickname is already registered.") return true } // Hash password hash := sha256.Sum256([]byte(password)) passwordHash := hex.EncodeToString(hash[:]) // Create account account := &NickAccount{ Nick: from.nick, PasswordHash: passwordHash, Email: email, RegisterTime: time.Now(), LastSeen: time.Now(), Settings: make(map[string]string), AccessList: []string{}, Flags: []string{}, } sm.nickServ.accounts[nick] = account // Set user as identified from.account = from.nick sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, fmt.Sprintf("Nickname %s has been registered successfully.", from.nick)) return true } func (sm *ServicesManager) handleNickServIdentify(from *Client, args []string) bool { if len(args) < 1 { sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, "Syntax: IDENTIFY ") return true } nick := strings.ToLower(from.nick) password := args[0] account, exists := sm.nickServ.accounts[nick] if !exists { sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, "This nickname is not registered.") return true } // Check password hash := sha256.Sum256([]byte(password)) passwordHash := hex.EncodeToString(hash[:]) if account.PasswordHash != passwordHash { sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, "Invalid password.") return true } // Set user as identified from.account = from.nick account.LastSeen = time.Now() sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, fmt.Sprintf("You are now identified for %s.", from.nick)) return true } func (sm *ServicesManager) handleNickServInfo(from *Client, args []string) bool { var targetNick string if len(args) > 0 { targetNick = args[0] } else { targetNick = from.nick } nick := strings.ToLower(targetNick) account, exists := sm.nickServ.accounts[nick] if !exists { sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, fmt.Sprintf("The nickname %s is not registered.", targetNick)) return true } sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, fmt.Sprintf("Information for %s:", account.Nick)) sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, fmt.Sprintf("Registered: %s", account.RegisterTime.Format("Jan 02, 2006 15:04:05 MST"))) sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, fmt.Sprintf("Last seen: %s", account.LastSeen.Format("Jan 02, 2006 15:04:05 MST"))) return true } func (sm *ServicesManager) handleNickServDrop(from *Client, _ []string) bool { nick := strings.ToLower(from.nick) if from.account != from.nick { sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, "You must be identified to drop your nickname.") return true } delete(sm.nickServ.accounts, nick) from.account = "" sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, fmt.Sprintf("Nickname %s has been dropped.", from.nick)) return true } func (sm *ServicesManager) handleNickServSet(from *Client) bool { // Handle SET commands (EMAIL, PASSWORD, etc.) sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, "SET command not yet implemented.") return true } func (sm *ServicesManager) handleNickServAccess(from *Client) bool { // Handle ACCESS commands (ADD, DEL, LIST) sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, "ACCESS command not yet implemented.") return true } func (sm *ServicesManager) handleNickServHelp(from *Client) bool { help := []string{ "*** NickServ Help ***", "REGISTER - Register your nickname", "IDENTIFY - Identify to your nickname", "INFO [nick] - Show information about a nickname", "DROP - Drop your nickname registration", "SET - Change settings", "ACCESS - Manage access list", "HELP - Show this help", } for _, line := range help { sm.sendServiceNotice(sm.config.NickServ.Nick, from.nick, line) } return true } // ChanServ command handlers (placeholder) func (sm *ServicesManager) handleChanServCommand(from *Client) bool { sm.sendServiceNotice(sm.config.ChanServ.Nick, from.nick, "ChanServ commands not yet implemented.") return true } // OperServ command handlers (placeholder) func (sm *ServicesManager) handleOperServCommand(from *Client) bool { sm.sendServiceNotice(sm.config.OperServ.Nick, from.nick, "OperServ commands not yet implemented.") return true } // MemoServ command handlers (placeholder) func (sm *ServicesManager) handleMemoServCommand(from *Client) bool { sm.sendServiceNotice(sm.config.MemoServ.Nick, from.nick, "MemoServ commands not yet implemented.") return true } // sendServiceNotice sends a notice from a service to a user func (sm *ServicesManager) sendServiceNotice(from, to, message string) { if client := sm.server.GetClient(to); client != nil { client.SendMessage(fmt.Sprintf(":%s NOTICE %s :%s", from, to, message)) } } // IsServiceNick checks if a nick is a service func (sm *ServicesManager) IsServiceNick(nick string) bool { if !sm.enabled { return false } lowerNick := strings.ToLower(nick) return lowerNick == strings.ToLower(sm.config.NickServ.Nick) || lowerNick == strings.ToLower(sm.config.ChanServ.Nick) || lowerNick == strings.ToLower(sm.config.OperServ.Nick) || lowerNick == strings.ToLower(sm.config.MemoServ.Nick) } // GetUserAccount returns the account for a nick func (sm *ServicesManager) GetUserAccount(nick string) *NickAccount { if sm.nickServ == nil { return nil } return sm.nickServ.accounts[strings.ToLower(nick)] } // IsIdentified checks if a user is identified func (sm *ServicesManager) IsIdentified(nick string) bool { if client := sm.server.GetClient(nick); client != nil { return client.account != "" } return false }