mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-10 22:19:31 +01:00
Add oper classes, for more finely-grained control
This commit is contained in:
parent
a5395b5fe2
commit
64bdedaee2
@ -10,8 +10,10 @@ New release of Oragono!
|
||||
### Security
|
||||
|
||||
### Added
|
||||
* Operator classes, allowing for more finely-grained permissions for operators.
|
||||
|
||||
### Changed
|
||||
* In the config file, "operator" changed to "opers", and new oper class is required.
|
||||
|
||||
### Removed
|
||||
|
||||
|
@ -37,6 +37,7 @@ type Client struct {
|
||||
capVersion CapVersion
|
||||
certfp string
|
||||
channels ChannelSet
|
||||
class *OperClass
|
||||
ctime time.Time
|
||||
flags map[UserMode]bool
|
||||
isDestroyed bool
|
||||
@ -50,6 +51,7 @@ type Client struct {
|
||||
nickCasefolded string
|
||||
nickMaskString string // cache for nickmask string since it's used with lots of replies
|
||||
nickMaskCasefolded string
|
||||
operName string
|
||||
quitTimer *time.Timer
|
||||
realname string
|
||||
registered bool
|
||||
@ -347,6 +349,12 @@ func (client *Client) destroy() {
|
||||
friends := client.Friends()
|
||||
friends.Remove(client)
|
||||
|
||||
// remove from opers list
|
||||
_, exists := client.server.currentOpers[client]
|
||||
if exists {
|
||||
delete(client.server.currentOpers, client)
|
||||
}
|
||||
|
||||
// alert monitors
|
||||
for _, mClient := range client.server.monitoring[client.nickCasefolded] {
|
||||
mClient.Send(nil, client.server.name, RPL_MONOFFLINE, mClient.nick, client.nick)
|
||||
|
122
irc/config.go
122
irc/config.go
@ -8,6 +8,7 @@ package irc
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
@ -65,6 +66,26 @@ type AccountRegistrationConfig struct {
|
||||
}
|
||||
}
|
||||
|
||||
type OperClassConfig struct {
|
||||
Title string
|
||||
Extends string
|
||||
Capabilities []string
|
||||
}
|
||||
|
||||
type OperConfig struct {
|
||||
Class string
|
||||
Vhost string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (conf *OperConfig) PasswordBytes() []byte {
|
||||
bytes, err := DecodePasswordHash(conf.Password)
|
||||
if err != nil {
|
||||
log.Fatal("decode password error: ", err)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Network struct {
|
||||
Name string
|
||||
@ -92,7 +113,9 @@ type Config struct {
|
||||
Accounts AccountRegistrationConfig
|
||||
}
|
||||
|
||||
Operator map[string]*PassConfig
|
||||
OperClasses map[string]*OperClassConfig `yaml:"oper-classes"`
|
||||
|
||||
Opers map[string]*OperConfig
|
||||
|
||||
Limits struct {
|
||||
NickLen uint `yaml:"nicklen"`
|
||||
@ -105,17 +128,97 @@ type Config struct {
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *Config) Operators() map[string][]byte {
|
||||
operators := make(map[string][]byte)
|
||||
for name, opConf := range conf.Operator {
|
||||
type OperClass struct {
|
||||
Title string
|
||||
Capabilities map[string]bool // map to make lookups much easier
|
||||
}
|
||||
|
||||
func (conf *Config) OperatorClasses() (*map[string]OperClass, error) {
|
||||
ocs := make(map[string]OperClass)
|
||||
|
||||
// loop from no extends to most extended, breaking if we can't add any more
|
||||
lenOfLastOcs := -1
|
||||
for {
|
||||
if lenOfLastOcs == len(ocs) {
|
||||
return nil, errors.New("OperClasses contains a looping dependency, or a class extends from a class that doesn't exist")
|
||||
}
|
||||
lenOfLastOcs = len(ocs)
|
||||
|
||||
var anyMissing bool
|
||||
for name, info := range conf.OperClasses {
|
||||
_, exists := ocs[name]
|
||||
_, extendsExists := ocs[info.Extends]
|
||||
if exists {
|
||||
// class already exists
|
||||
continue
|
||||
} else if len(info.Extends) > 0 && !extendsExists {
|
||||
// class we extend on doesn't exist
|
||||
_, exists := conf.OperClasses[info.Extends]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("Operclass [%s] extends [%s], which doesn't exist", name, info.Extends)
|
||||
}
|
||||
anyMissing = true
|
||||
continue
|
||||
}
|
||||
|
||||
// create new operclass
|
||||
var oc OperClass
|
||||
oc.Capabilities = make(map[string]bool)
|
||||
|
||||
// get inhereted info from other operclasses
|
||||
if len(info.Extends) > 0 {
|
||||
einfo, _ := ocs[info.Extends]
|
||||
|
||||
for capab := range einfo.Capabilities {
|
||||
oc.Capabilities[capab] = true
|
||||
}
|
||||
}
|
||||
|
||||
// add our own info
|
||||
oc.Title = info.Title
|
||||
for _, capab := range info.Capabilities {
|
||||
oc.Capabilities[capab] = true
|
||||
}
|
||||
|
||||
ocs[name] = oc
|
||||
}
|
||||
|
||||
if !anyMissing {
|
||||
// we've got every operclass!
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &ocs, nil
|
||||
}
|
||||
|
||||
type Oper struct {
|
||||
Class *OperClass
|
||||
Pass []byte
|
||||
}
|
||||
|
||||
func (conf *Config) Operators(oc *map[string]OperClass) (map[string]Oper, error) {
|
||||
operators := make(map[string]Oper)
|
||||
for name, opConf := range conf.Opers {
|
||||
var oper Oper
|
||||
|
||||
// oper name
|
||||
name, err := CasefoldName(name)
|
||||
if err == nil {
|
||||
operators[name] = opConf.PasswordBytes()
|
||||
} else {
|
||||
log.Println("Could not casefold oper name:", err.Error())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not casefold oper name: %s", err.Error())
|
||||
}
|
||||
|
||||
oper.Pass = opConf.PasswordBytes()
|
||||
class, exists := (*oc)[opConf.Class]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("Could not load operator [%s] - they use operclass [%s] which does not exist", name, opConf.Class)
|
||||
}
|
||||
return operators
|
||||
oper.Class = &class
|
||||
|
||||
// successful, attach to list of opers
|
||||
operators[name] = oper
|
||||
}
|
||||
return operators, nil
|
||||
}
|
||||
|
||||
func (conf *Config) TLSListeners() map[string]*tls.Config {
|
||||
@ -169,5 +272,6 @@ func LoadConfig(filename string) (config *Config, err error) {
|
||||
if config.Limits.NickLen < 1 || config.Limits.ChannelLen < 2 || config.Limits.AwayLen < 1 || config.Limits.KickLen < 1 || config.Limits.TopicLen < 1 {
|
||||
return nil, errors.New("Limits aren't setup properly, check them and make them sane")
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ type Server struct {
|
||||
commands chan Command
|
||||
configFilename string
|
||||
ctime time.Time
|
||||
currentOpers map[*Client]bool
|
||||
store buntdb.DB
|
||||
idle chan *Client
|
||||
limits Limits
|
||||
@ -78,7 +79,8 @@ type Server struct {
|
||||
nameCasefolded string
|
||||
networkName string
|
||||
newConns chan clientConn
|
||||
operators map[string][]byte
|
||||
operators map[string]Oper
|
||||
operclasses map[string]OperClass
|
||||
password []byte
|
||||
passwords *PasswordManager
|
||||
rehashMutex sync.Mutex
|
||||
@ -115,6 +117,15 @@ func NewServer(configFilename string, config *Config) *Server {
|
||||
SupportedCapabilities[SASL] = true
|
||||
}
|
||||
|
||||
operClasses, err := config.OperatorClasses()
|
||||
if err != nil {
|
||||
log.Fatal("Error loading oper classes:", err.Error())
|
||||
}
|
||||
opers, err := config.Operators(operClasses)
|
||||
if err != nil {
|
||||
log.Fatal("Error loading operators:", err.Error())
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
accounts: make(map[string]*ClientAccount),
|
||||
authenticationEnabled: config.AuthenticationEnabled,
|
||||
@ -123,6 +134,7 @@ func NewServer(configFilename string, config *Config) *Server {
|
||||
commands: make(chan Command),
|
||||
configFilename: configFilename,
|
||||
ctime: time.Now(),
|
||||
currentOpers: make(map[*Client]bool),
|
||||
idle: make(chan *Client),
|
||||
limits: Limits{
|
||||
AwayLen: int(config.Limits.AwayLen),
|
||||
@ -138,7 +150,8 @@ func NewServer(configFilename string, config *Config) *Server {
|
||||
nameCasefolded: casefoldedName,
|
||||
networkName: config.Network.Name,
|
||||
newConns: make(chan clientConn),
|
||||
operators: config.Operators(),
|
||||
operclasses: *operClasses,
|
||||
operators: opers,
|
||||
signals: make(chan os.Signal, len(SERVER_SIGNALS)),
|
||||
rehashSignal: make(chan os.Signal, 1),
|
||||
whoWas: NewWhoWasList(config.Limits.WhowasEntries),
|
||||
@ -874,17 +887,24 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, "Password incorrect")
|
||||
return true
|
||||
}
|
||||
hash := server.operators[name]
|
||||
hash := server.operators[name].Pass
|
||||
password := []byte(msg.Params[1])
|
||||
|
||||
err = ComparePassword(hash, password)
|
||||
|
||||
if (hash == nil) || (err != nil) {
|
||||
fmt.Println("2", hash)
|
||||
client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, "Password incorrect")
|
||||
return true
|
||||
}
|
||||
|
||||
client.flags[Operator] = true
|
||||
client.operName = name
|
||||
client.class = server.operators[name].Class
|
||||
server.currentOpers[client] = true
|
||||
|
||||
//TODO(dan): push out CHGHOST if vhost is applied
|
||||
|
||||
client.Send(nil, server.name, RPL_YOUREOPER, client.nick, "You are now an IRC operator")
|
||||
//TODO(dan): Should this be sent automagically as part of setting the flag/mode?
|
||||
modech := ModeChanges{&ModeChange{
|
||||
@ -906,6 +926,22 @@ func (server *Server) rehash() error {
|
||||
return fmt.Errorf("Error rehashing config file: %s", err.Error())
|
||||
}
|
||||
|
||||
// confirm operator stuff all exists and is fine
|
||||
operclasses, err := config.OperatorClasses()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error rehashing config file: %s", err.Error())
|
||||
}
|
||||
opers, err := config.Operators(operclasses)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error rehashing config file: %s", err.Error())
|
||||
}
|
||||
for client := range server.currentOpers {
|
||||
_, exists := opers[client.operName]
|
||||
if !exists {
|
||||
return fmt.Errorf("Oper [%s] no longer exists (used by client [%s])", client.operName, client.nickMaskString)
|
||||
}
|
||||
}
|
||||
|
||||
// setup new and removed caps
|
||||
addedCaps := make(CapabilitySet)
|
||||
removedCaps := make(CapabilitySet)
|
||||
@ -955,7 +991,8 @@ func (server *Server) rehash() error {
|
||||
NickLen: int(config.Limits.NickLen),
|
||||
TopicLen: int(config.Limits.TopicLen),
|
||||
}
|
||||
server.operators = config.Operators()
|
||||
server.operclasses = *operclasses
|
||||
server.operators = opers
|
||||
server.checkIdent = config.Server.CheckIdent
|
||||
|
||||
// registration
|
||||
|
48
oragono.yaml
48
oragono.yaml
@ -59,10 +59,56 @@ registration:
|
||||
enabled-callbacks:
|
||||
- none # no verification needed, will instantly register successfully
|
||||
|
||||
# operator classes
|
||||
oper-classes:
|
||||
# local operator
|
||||
"local-oper":
|
||||
# title shown in WHOIS
|
||||
title: Local Operator
|
||||
|
||||
# capability names
|
||||
capabilities:
|
||||
- "oper:local_kill"
|
||||
- "oper:local_ban"
|
||||
- "oper:local_unban"
|
||||
|
||||
# network operator
|
||||
"network-oper":
|
||||
# title shown in WHOIS
|
||||
title: Network Operator
|
||||
|
||||
# oper class this extends from
|
||||
extends: "local-oper"
|
||||
|
||||
# capability names
|
||||
capabilities:
|
||||
- "oper:remote_kill"
|
||||
- "oper:remote_ban"
|
||||
- "oper:remote_unban"
|
||||
|
||||
# server admin
|
||||
"server-admin":
|
||||
# title shown in WHOIS
|
||||
title: Server Admin
|
||||
|
||||
# oper class this extends from
|
||||
extends: "local-oper"
|
||||
|
||||
# capability names
|
||||
capabilities:
|
||||
- "oper:rehash"
|
||||
- "oper:die"
|
||||
|
||||
# ircd operators
|
||||
operator:
|
||||
opers:
|
||||
# operator named 'dan'
|
||||
dan:
|
||||
# which capabilities this oper has access to
|
||||
class: "server-admin"
|
||||
|
||||
# custom hostname
|
||||
vhost: "n"
|
||||
|
||||
# password to login with /OPER command
|
||||
# generated using "oragono genpasswd"
|
||||
password: JDJhJDA0JE1vZmwxZC9YTXBhZ3RWT2xBbkNwZnV3R2N6VFUwQUI0RUJRVXRBRHliZVVoa0VYMnlIaGsu
|
||||
|
Loading…
Reference in New Issue
Block a user