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
|
### Security
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
* Operator classes, allowing for more finely-grained permissions for operators.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
* In the config file, "operator" changed to "opers", and new oper class is required.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ type Client struct {
|
|||||||
capVersion CapVersion
|
capVersion CapVersion
|
||||||
certfp string
|
certfp string
|
||||||
channels ChannelSet
|
channels ChannelSet
|
||||||
|
class *OperClass
|
||||||
ctime time.Time
|
ctime time.Time
|
||||||
flags map[UserMode]bool
|
flags map[UserMode]bool
|
||||||
isDestroyed bool
|
isDestroyed bool
|
||||||
@ -50,6 +51,7 @@ type Client struct {
|
|||||||
nickCasefolded string
|
nickCasefolded string
|
||||||
nickMaskString string // cache for nickmask string since it's used with lots of replies
|
nickMaskString string // cache for nickmask string since it's used with lots of replies
|
||||||
nickMaskCasefolded string
|
nickMaskCasefolded string
|
||||||
|
operName string
|
||||||
quitTimer *time.Timer
|
quitTimer *time.Timer
|
||||||
realname string
|
realname string
|
||||||
registered bool
|
registered bool
|
||||||
@ -347,6 +349,12 @@ func (client *Client) destroy() {
|
|||||||
friends := client.Friends()
|
friends := client.Friends()
|
||||||
friends.Remove(client)
|
friends.Remove(client)
|
||||||
|
|
||||||
|
// remove from opers list
|
||||||
|
_, exists := client.server.currentOpers[client]
|
||||||
|
if exists {
|
||||||
|
delete(client.server.currentOpers, client)
|
||||||
|
}
|
||||||
|
|
||||||
// alert monitors
|
// alert monitors
|
||||||
for _, mClient := range client.server.monitoring[client.nickCasefolded] {
|
for _, mClient := range client.server.monitoring[client.nickCasefolded] {
|
||||||
mClient.Send(nil, client.server.name, RPL_MONOFFLINE, mClient.nick, client.nick)
|
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 (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"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 {
|
type Config struct {
|
||||||
Network struct {
|
Network struct {
|
||||||
Name string
|
Name string
|
||||||
@ -92,7 +113,9 @@ type Config struct {
|
|||||||
Accounts AccountRegistrationConfig
|
Accounts AccountRegistrationConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
Operator map[string]*PassConfig
|
OperClasses map[string]*OperClassConfig `yaml:"oper-classes"`
|
||||||
|
|
||||||
|
Opers map[string]*OperConfig
|
||||||
|
|
||||||
Limits struct {
|
Limits struct {
|
||||||
NickLen uint `yaml:"nicklen"`
|
NickLen uint `yaml:"nicklen"`
|
||||||
@ -105,17 +128,97 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conf *Config) Operators() map[string][]byte {
|
type OperClass struct {
|
||||||
operators := make(map[string][]byte)
|
Title string
|
||||||
for name, opConf := range conf.Operator {
|
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)
|
name, err := CasefoldName(name)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
operators[name] = opConf.PasswordBytes()
|
return nil, fmt.Errorf("Could not casefold oper name: %s", err.Error())
|
||||||
} else {
|
|
||||||
log.Println("Could not casefold oper name:", 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 {
|
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 {
|
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 nil, errors.New("Limits aren't setup properly, check them and make them sane")
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,7 @@ type Server struct {
|
|||||||
commands chan Command
|
commands chan Command
|
||||||
configFilename string
|
configFilename string
|
||||||
ctime time.Time
|
ctime time.Time
|
||||||
|
currentOpers map[*Client]bool
|
||||||
store buntdb.DB
|
store buntdb.DB
|
||||||
idle chan *Client
|
idle chan *Client
|
||||||
limits Limits
|
limits Limits
|
||||||
@ -78,7 +79,8 @@ type Server struct {
|
|||||||
nameCasefolded string
|
nameCasefolded string
|
||||||
networkName string
|
networkName string
|
||||||
newConns chan clientConn
|
newConns chan clientConn
|
||||||
operators map[string][]byte
|
operators map[string]Oper
|
||||||
|
operclasses map[string]OperClass
|
||||||
password []byte
|
password []byte
|
||||||
passwords *PasswordManager
|
passwords *PasswordManager
|
||||||
rehashMutex sync.Mutex
|
rehashMutex sync.Mutex
|
||||||
@ -115,6 +117,15 @@ func NewServer(configFilename string, config *Config) *Server {
|
|||||||
SupportedCapabilities[SASL] = true
|
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{
|
server := &Server{
|
||||||
accounts: make(map[string]*ClientAccount),
|
accounts: make(map[string]*ClientAccount),
|
||||||
authenticationEnabled: config.AuthenticationEnabled,
|
authenticationEnabled: config.AuthenticationEnabled,
|
||||||
@ -123,6 +134,7 @@ func NewServer(configFilename string, config *Config) *Server {
|
|||||||
commands: make(chan Command),
|
commands: make(chan Command),
|
||||||
configFilename: configFilename,
|
configFilename: configFilename,
|
||||||
ctime: time.Now(),
|
ctime: time.Now(),
|
||||||
|
currentOpers: make(map[*Client]bool),
|
||||||
idle: make(chan *Client),
|
idle: make(chan *Client),
|
||||||
limits: Limits{
|
limits: Limits{
|
||||||
AwayLen: int(config.Limits.AwayLen),
|
AwayLen: int(config.Limits.AwayLen),
|
||||||
@ -138,7 +150,8 @@ func NewServer(configFilename string, config *Config) *Server {
|
|||||||
nameCasefolded: casefoldedName,
|
nameCasefolded: casefoldedName,
|
||||||
networkName: config.Network.Name,
|
networkName: config.Network.Name,
|
||||||
newConns: make(chan clientConn),
|
newConns: make(chan clientConn),
|
||||||
operators: config.Operators(),
|
operclasses: *operClasses,
|
||||||
|
operators: opers,
|
||||||
signals: make(chan os.Signal, len(SERVER_SIGNALS)),
|
signals: make(chan os.Signal, len(SERVER_SIGNALS)),
|
||||||
rehashSignal: make(chan os.Signal, 1),
|
rehashSignal: make(chan os.Signal, 1),
|
||||||
whoWas: NewWhoWasList(config.Limits.WhowasEntries),
|
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")
|
client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, "Password incorrect")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
hash := server.operators[name]
|
hash := server.operators[name].Pass
|
||||||
password := []byte(msg.Params[1])
|
password := []byte(msg.Params[1])
|
||||||
|
|
||||||
err = ComparePassword(hash, password)
|
err = ComparePassword(hash, password)
|
||||||
|
|
||||||
if (hash == nil) || (err != nil) {
|
if (hash == nil) || (err != nil) {
|
||||||
|
fmt.Println("2", hash)
|
||||||
client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, "Password incorrect")
|
client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, "Password incorrect")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
client.flags[Operator] = 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")
|
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?
|
//TODO(dan): Should this be sent automagically as part of setting the flag/mode?
|
||||||
modech := ModeChanges{&ModeChange{
|
modech := ModeChanges{&ModeChange{
|
||||||
@ -906,6 +926,22 @@ func (server *Server) rehash() error {
|
|||||||
return fmt.Errorf("Error rehashing config file: %s", err.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
|
// setup new and removed caps
|
||||||
addedCaps := make(CapabilitySet)
|
addedCaps := make(CapabilitySet)
|
||||||
removedCaps := make(CapabilitySet)
|
removedCaps := make(CapabilitySet)
|
||||||
@ -955,7 +991,8 @@ func (server *Server) rehash() error {
|
|||||||
NickLen: int(config.Limits.NickLen),
|
NickLen: int(config.Limits.NickLen),
|
||||||
TopicLen: int(config.Limits.TopicLen),
|
TopicLen: int(config.Limits.TopicLen),
|
||||||
}
|
}
|
||||||
server.operators = config.Operators()
|
server.operclasses = *operclasses
|
||||||
|
server.operators = opers
|
||||||
server.checkIdent = config.Server.CheckIdent
|
server.checkIdent = config.Server.CheckIdent
|
||||||
|
|
||||||
// registration
|
// registration
|
||||||
|
48
oragono.yaml
48
oragono.yaml
@ -59,10 +59,56 @@ registration:
|
|||||||
enabled-callbacks:
|
enabled-callbacks:
|
||||||
- none # no verification needed, will instantly register successfully
|
- 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
|
# ircd operators
|
||||||
operator:
|
opers:
|
||||||
# operator named 'dan'
|
# operator named 'dan'
|
||||||
dan:
|
dan:
|
||||||
|
# which capabilities this oper has access to
|
||||||
|
class: "server-admin"
|
||||||
|
|
||||||
|
# custom hostname
|
||||||
|
vhost: "n"
|
||||||
|
|
||||||
# password to login with /OPER command
|
# password to login with /OPER command
|
||||||
# generated using "oragono genpasswd"
|
# generated using "oragono genpasswd"
|
||||||
password: JDJhJDA0JE1vZmwxZC9YTXBhZ3RWT2xBbkNwZnV3R2N6VFUwQUI0RUJRVXRBRHliZVVoa0VYMnlIaGsu
|
password: JDJhJDA0JE1vZmwxZC9YTXBhZ3RWT2xBbkNwZnV3R2N6VFUwQUI0RUJRVXRBRHliZVVoa0VYMnlIaGsu
|
||||||
|
Loading…
Reference in New Issue
Block a user