Files
techircd/services.go

510 lines
14 KiB
Go

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 <password> <email>")
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 <password>")
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 <password> <email> - Register your nickname",
"IDENTIFY <password> - 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
}