340 lines
9.5 KiB
Go
340 lines
9.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
)
|
|
|
|
// OperClass defines an operator class with specific permissions
|
|
type OperClass struct {
|
|
Name string `json:"name"`
|
|
Rank int `json:"rank"` // Higher number = higher rank
|
|
Description string `json:"description"`
|
|
Permissions []string `json:"permissions"`
|
|
Inherits string `json:"inherits"` // Inherit permissions from another class
|
|
Color string `json:"color"` // Color for display purposes
|
|
Symbol string `json:"symbol"` // Symbol to display (*, @, &, etc.)
|
|
}
|
|
|
|
// Oper defines an individual operator
|
|
type Oper struct {
|
|
Name string `json:"name"`
|
|
Password string `json:"password"`
|
|
Host string `json:"host"`
|
|
Class string `json:"class"`
|
|
Flags []string `json:"flags"` // Additional per-user flags
|
|
MaxClients int `json:"max_clients"` // Max clients this oper can handle
|
|
Expires string `json:"expires"` // Expiration date (optional)
|
|
Contact string `json:"contact"` // Contact information
|
|
LastSeen string `json:"last_seen"` // Last time this oper was online
|
|
}
|
|
|
|
// RankNames defines custom names for rank levels
|
|
type RankNames struct {
|
|
Rank1 string `json:"rank_1"` // Default: Helper
|
|
Rank2 string `json:"rank_2"` // Default: Moderator
|
|
Rank3 string `json:"rank_3"` // Default: Operator
|
|
Rank4 string `json:"rank_4"` // Default: Administrator
|
|
Rank5 string `json:"rank_5"` // Default: Owner
|
|
// Support for custom ranks beyond 5
|
|
CustomRanks map[string]int `json:"custom_ranks"` // "CustomName": 6
|
|
}
|
|
|
|
// OperConfig holds the complete operator configuration
|
|
type OperConfig struct {
|
|
Classes []OperClass `json:"classes"`
|
|
Opers []Oper `json:"opers"`
|
|
RankNames RankNames `json:"rank_names"`
|
|
Settings struct {
|
|
RequireSSL bool `json:"require_ssl"`
|
|
MaxFailedAttempts int `json:"max_failed_attempts"`
|
|
LockoutDuration int `json:"lockout_duration_minutes"`
|
|
AllowedCommands []string `json:"allowed_commands"`
|
|
LogOperActions bool `json:"log_oper_actions"`
|
|
NotifyOnOperUp bool `json:"notify_on_oper_up"`
|
|
AutoExpireInactive int `json:"auto_expire_inactive_days"`
|
|
RequireTwoFactor bool `json:"require_two_factor"`
|
|
} `json:"settings"`
|
|
}
|
|
|
|
// LoadOperConfig loads operator configuration from file
|
|
func LoadOperConfig(filename string) (*OperConfig, error) {
|
|
data, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read oper config file: %v", err)
|
|
}
|
|
|
|
var config OperConfig
|
|
if err := json.Unmarshal(data, &config); err != nil {
|
|
return nil, fmt.Errorf("failed to parse oper config file: %v", err)
|
|
}
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
// SaveOperConfig saves operator configuration to file
|
|
func SaveOperConfig(config *OperConfig, filename string) error {
|
|
data, err := json.MarshalIndent(config, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal oper config: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(filename, data, 0644); err != nil {
|
|
return fmt.Errorf("failed to write oper config file: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetOperClass returns the operator class by name
|
|
func (oc *OperConfig) GetOperClass(className string) *OperClass {
|
|
for i := range oc.Classes {
|
|
if oc.Classes[i].Name == className {
|
|
return &oc.Classes[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetOper returns the operator by name
|
|
func (oc *OperConfig) GetOper(operName string) *Oper {
|
|
for i := range oc.Opers {
|
|
if oc.Opers[i].Name == operName {
|
|
return &oc.Opers[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetOperPermissions returns all permissions for an operator (including inherited)
|
|
func (oc *OperConfig) GetOperPermissions(operName string) []string {
|
|
oper := oc.GetOper(operName)
|
|
if oper == nil {
|
|
return nil
|
|
}
|
|
|
|
class := oc.GetOperClass(oper.Class)
|
|
if class == nil {
|
|
return oper.Flags
|
|
}
|
|
|
|
permissions := make(map[string]bool)
|
|
|
|
// Add class permissions
|
|
for _, perm := range class.Permissions {
|
|
permissions[perm] = true
|
|
}
|
|
|
|
// Add inherited permissions
|
|
if class.Inherits != "" {
|
|
inherited := oc.GetOperClass(class.Inherits)
|
|
if inherited != nil {
|
|
for _, perm := range inherited.Permissions {
|
|
permissions[perm] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add individual flags
|
|
for _, flag := range oper.Flags {
|
|
permissions[flag] = true
|
|
}
|
|
|
|
// Convert back to slice
|
|
result := make([]string, 0, len(permissions))
|
|
for perm := range permissions {
|
|
result = append(result, perm)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// GetRankName returns the custom name for a rank level
|
|
func (oc *OperConfig) GetRankName(rank int) string {
|
|
switch rank {
|
|
case 1:
|
|
if oc.RankNames.Rank1 != "" {
|
|
return oc.RankNames.Rank1
|
|
}
|
|
return "Helper"
|
|
case 2:
|
|
if oc.RankNames.Rank2 != "" {
|
|
return oc.RankNames.Rank2
|
|
}
|
|
return "Moderator"
|
|
case 3:
|
|
if oc.RankNames.Rank3 != "" {
|
|
return oc.RankNames.Rank3
|
|
}
|
|
return "Operator"
|
|
case 4:
|
|
if oc.RankNames.Rank4 != "" {
|
|
return oc.RankNames.Rank4
|
|
}
|
|
return "Administrator"
|
|
case 5:
|
|
if oc.RankNames.Rank5 != "" {
|
|
return oc.RankNames.Rank5
|
|
}
|
|
return "Owner"
|
|
default:
|
|
// Check custom ranks
|
|
for name, rankNum := range oc.RankNames.CustomRanks {
|
|
if rankNum == rank {
|
|
return name
|
|
}
|
|
}
|
|
return fmt.Sprintf("Rank %d", rank)
|
|
}
|
|
}
|
|
|
|
// GetOperRankName returns the rank name for an operator
|
|
func (oc *OperConfig) GetOperRankName(operName string) string {
|
|
oper := oc.GetOper(operName)
|
|
if oper == nil {
|
|
return "Unknown"
|
|
}
|
|
|
|
class := oc.GetOperClass(oper.Class)
|
|
if class == nil {
|
|
return "Unknown"
|
|
}
|
|
|
|
return oc.GetRankName(class.Rank)
|
|
}
|
|
|
|
// SetCustomRankName allows adding custom rank names at runtime
|
|
func (oc *OperConfig) SetCustomRankName(name string, rank int) {
|
|
if oc.RankNames.CustomRanks == nil {
|
|
oc.RankNames.CustomRanks = make(map[string]int)
|
|
}
|
|
oc.RankNames.CustomRanks[name] = rank
|
|
}
|
|
|
|
// HasPermission checks if an operator has a specific permission
|
|
func (oc *OperConfig) HasPermission(operName, permission string) bool {
|
|
permissions := oc.GetOperPermissions(operName)
|
|
for _, perm := range permissions {
|
|
if perm == permission || perm == "*" { // * grants all permissions
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetOperRank returns the rank of an operator
|
|
func (oc *OperConfig) GetOperRank(operName string) int {
|
|
oper := oc.GetOper(operName)
|
|
if oper == nil {
|
|
return 0
|
|
}
|
|
|
|
class := oc.GetOperClass(oper.Class)
|
|
if class == nil {
|
|
return 0
|
|
}
|
|
|
|
return class.Rank
|
|
}
|
|
|
|
// CanOperateOn checks if oper1 can perform actions on oper2 (based on rank)
|
|
func (oc *OperConfig) CanOperateOn(oper1Name, oper2Name string) bool {
|
|
rank1 := oc.GetOperRank(oper1Name)
|
|
rank2 := oc.GetOperRank(oper2Name)
|
|
|
|
// Higher rank can operate on lower rank
|
|
// Same rank cannot operate on each other (unless they have override permission)
|
|
return rank1 > rank2 || oc.HasPermission(oper1Name, "override_rank")
|
|
}
|
|
|
|
// DefaultOperConfig returns a default operator configuration
|
|
func DefaultOperConfig() *OperConfig {
|
|
return &OperConfig{
|
|
Classes: []OperClass{
|
|
{
|
|
Name: "helper",
|
|
Rank: 1,
|
|
Description: "Helper - Basic moderation commands",
|
|
Permissions: []string{"kick", "topic", "mode_channel"},
|
|
Color: "green",
|
|
Symbol: "%",
|
|
},
|
|
{
|
|
Name: "moderator",
|
|
Rank: 2,
|
|
Description: "Moderator - Channel and user management",
|
|
Permissions: []string{"ban", "unban", "kick", "mute", "topic", "mode_channel", "mode_user", "who_override"},
|
|
Inherits: "helper",
|
|
Color: "blue",
|
|
Symbol: "@",
|
|
},
|
|
{
|
|
Name: "operator",
|
|
Rank: 3,
|
|
Description: "Operator - Server management commands",
|
|
Permissions: []string{"kill", "gline", "rehash", "connect", "squit", "wallops", "operwall"},
|
|
Inherits: "moderator",
|
|
Color: "red",
|
|
Symbol: "*",
|
|
},
|
|
{
|
|
Name: "admin",
|
|
Rank: 4,
|
|
Description: "Administrator - Full server control",
|
|
Permissions: []string{"*"}, // All permissions
|
|
Color: "purple",
|
|
Symbol: "&",
|
|
},
|
|
{
|
|
Name: "owner",
|
|
Rank: 5,
|
|
Description: "Server Owner - Ultimate authority",
|
|
Permissions: []string{"*", "override_rank", "shutdown", "restart"},
|
|
Color: "gold",
|
|
Symbol: "~",
|
|
},
|
|
},
|
|
Opers: []Oper{
|
|
{
|
|
Name: "admin",
|
|
Password: "changeme",
|
|
Host: "*@localhost",
|
|
Class: "admin",
|
|
MaxClients: 1000,
|
|
Contact: "admin@example.com",
|
|
},
|
|
},
|
|
RankNames: RankNames{
|
|
Rank1: "Helper", // Customizable: "Support Staff", "Junior Mod", etc.
|
|
Rank2: "Moderator", // Customizable: "Channel Mod", "Guard", etc.
|
|
Rank3: "Operator", // Customizable: "IRC Operator", "Senior Staff", etc.
|
|
Rank4: "Administrator", // Customizable: "Admin", "Server Admin", etc.
|
|
Rank5: "Owner", // Customizable: "Root", "Founder", etc.
|
|
CustomRanks: map[string]int{
|
|
// Example: "Super Admin": 6,
|
|
// Example: "Network Founder": 10,
|
|
},
|
|
},
|
|
Settings: struct {
|
|
RequireSSL bool `json:"require_ssl"`
|
|
MaxFailedAttempts int `json:"max_failed_attempts"`
|
|
LockoutDuration int `json:"lockout_duration_minutes"`
|
|
AllowedCommands []string `json:"allowed_commands"`
|
|
LogOperActions bool `json:"log_oper_actions"`
|
|
NotifyOnOperUp bool `json:"notify_on_oper_up"`
|
|
AutoExpireInactive int `json:"auto_expire_inactive_days"`
|
|
RequireTwoFactor bool `json:"require_two_factor"`
|
|
}{
|
|
RequireSSL: false,
|
|
MaxFailedAttempts: 3,
|
|
LockoutDuration: 30,
|
|
AllowedCommands: []string{"OPER", "KILL", "GLINE", "REHASH", "WALLOPS"},
|
|
LogOperActions: true,
|
|
NotifyOnOperUp: true,
|
|
AutoExpireInactive: 365,
|
|
RequireTwoFactor: false,
|
|
},
|
|
}
|
|
}
|