mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-22 10:42:52 +01:00
Add very initial snomasks
This commit is contained in:
parent
1afd3b8f78
commit
fd793d6adb
@ -9,6 +9,7 @@ New release of Oragono!
|
||||
|
||||
### Config Changes
|
||||
* Added `debug` section containing additional debug settings.
|
||||
* Added `modes` key on oper config, for setting modes on oper-up.
|
||||
* Added ability to log to `stdout` in logger methods.
|
||||
|
||||
### Security
|
||||
@ -16,6 +17,7 @@ New release of Oragono!
|
||||
### Added
|
||||
* Added ability to log to stdout.
|
||||
* Added ability to use StackImpact profiling.
|
||||
* Added initial server notice masks (snomasks).
|
||||
|
||||
### Changed
|
||||
* Socket code rewritten to be a lot faster and safer.
|
||||
|
@ -94,6 +94,7 @@ type OperConfig struct {
|
||||
Vhost string
|
||||
WhoisLine string `yaml:"whois-line"`
|
||||
Password string
|
||||
Modes string
|
||||
}
|
||||
|
||||
func (conf *OperConfig) PasswordBytes() []byte {
|
||||
@ -323,6 +324,7 @@ type Oper struct {
|
||||
WhoisLine string
|
||||
Vhost string
|
||||
Pass []byte
|
||||
Modes string
|
||||
}
|
||||
|
||||
// Operators returns a map of operator configs from the given OperClass and config.
|
||||
@ -349,6 +351,7 @@ func (conf *Config) Operators(oc *map[string]OperClass) (map[string]Oper, error)
|
||||
} else {
|
||||
oper.WhoisLine = class.WhoisLine
|
||||
}
|
||||
oper.Modes = strings.TrimSpace(opConf.Modes)
|
||||
|
||||
// successful, attach to list of opers
|
||||
operators[name] = oper
|
||||
|
131
irc/modes.go
131
irc/modes.go
@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/DanielOaks/girc-go/ircmsg"
|
||||
"github.com/DanielOaks/oragono/irc/sno"
|
||||
"github.com/tidwall/buntdb"
|
||||
)
|
||||
|
||||
@ -97,7 +98,7 @@ const (
|
||||
LocalOperator Mode = 'O'
|
||||
Operator Mode = 'o'
|
||||
Restricted Mode = 'r'
|
||||
ServerNotice Mode = 's' // deprecated
|
||||
ServerNotice Mode = 's'
|
||||
TLS Mode = 'Z'
|
||||
UserRoleplaying Mode = 'E'
|
||||
WallOps Mode = 'w'
|
||||
@ -105,7 +106,7 @@ const (
|
||||
|
||||
var (
|
||||
SupportedUserModes = Modes{
|
||||
Away, Invisible, Operator, UserRoleplaying,
|
||||
Away, Invisible, Operator, ServerNotice, UserRoleplaying,
|
||||
}
|
||||
// supportedUserModesString acts as a cache for when we introduce users
|
||||
supportedUserModesString = SupportedUserModes.String()
|
||||
@ -210,15 +211,77 @@ func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
return umodeHandler(server, client, msg)
|
||||
}
|
||||
|
||||
// ParseUserModeChanges returns the valid changes, and the list of unknown chars.
|
||||
func ParseUserModeChanges(params ...string) (ModeChanges, map[rune]bool) {
|
||||
changes := make(ModeChanges, 0)
|
||||
unknown := make(map[rune]bool)
|
||||
|
||||
if 0 < len(params) {
|
||||
modeArg := params[0]
|
||||
op := ModeOp(modeArg[0])
|
||||
if (op == Add) || (op == Remove) {
|
||||
modeArg = modeArg[1:]
|
||||
} else {
|
||||
unknown[rune(modeArg[0])] = true
|
||||
return changes, unknown
|
||||
}
|
||||
|
||||
skipArgs := 1
|
||||
|
||||
for _, mode := range modeArg {
|
||||
if mode == '-' || mode == '+' {
|
||||
op = ModeOp(mode)
|
||||
continue
|
||||
}
|
||||
change := ModeChange{
|
||||
mode: Mode(mode),
|
||||
op: op,
|
||||
}
|
||||
|
||||
// put arg into modechange if needed
|
||||
switch Mode(mode) {
|
||||
case ServerNotice:
|
||||
// always require arg
|
||||
if len(params) > skipArgs {
|
||||
change.arg = params[skipArgs]
|
||||
skipArgs++
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
var isKnown bool
|
||||
for _, supportedMode := range SupportedUserModes {
|
||||
if rune(supportedMode) == mode {
|
||||
isKnown = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isKnown {
|
||||
unknown[mode] = true
|
||||
continue
|
||||
}
|
||||
|
||||
changes = append(changes, change)
|
||||
}
|
||||
}
|
||||
|
||||
return changes, unknown
|
||||
}
|
||||
|
||||
// applyUserModeChanges applies the given changes, and returns the applied changes.
|
||||
func (client *Client) applyUserModeChanges(changes ModeChanges) ModeChanges {
|
||||
func (client *Client) applyUserModeChanges(force bool, changes ModeChanges) ModeChanges {
|
||||
applied := make(ModeChanges, 0)
|
||||
|
||||
for _, change := range changes {
|
||||
switch change.mode {
|
||||
case Invisible, ServerNotice, WallOps, UserRoleplaying:
|
||||
case Invisible, WallOps, UserRoleplaying, Operator, LocalOperator:
|
||||
switch change.op {
|
||||
case Add:
|
||||
if !force && (change.mode == Operator || change.mode == LocalOperator) {
|
||||
continue
|
||||
}
|
||||
|
||||
if client.flags[change.mode] {
|
||||
continue
|
||||
}
|
||||
@ -233,12 +296,21 @@ func (client *Client) applyUserModeChanges(changes ModeChanges) ModeChanges {
|
||||
applied = append(applied, change)
|
||||
}
|
||||
|
||||
case Operator, LocalOperator:
|
||||
if change.op == Remove {
|
||||
if !client.flags[change.mode] {
|
||||
continue
|
||||
case ServerNotice:
|
||||
if !client.flags[Operator] {
|
||||
continue
|
||||
}
|
||||
var masks []sno.Mask
|
||||
if change.op == Add || change.op == Remove {
|
||||
for _, char := range change.arg {
|
||||
masks = append(masks, sno.Mask(char))
|
||||
}
|
||||
delete(client.flags, change.mode)
|
||||
}
|
||||
if change.op == Add {
|
||||
client.server.snomasks.AddMasks(client, masks...)
|
||||
applied = append(applied, change)
|
||||
} else if change.op == Remove {
|
||||
client.server.snomasks.RemoveMasks(client, masks...)
|
||||
applied = append(applied, change)
|
||||
}
|
||||
}
|
||||
@ -272,38 +344,36 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// assemble changes
|
||||
changes := make(ModeChanges, 0)
|
||||
// applied mode changes
|
||||
applied := make(ModeChanges, 0)
|
||||
|
||||
if len(msg.Params) > 1 {
|
||||
modeArg := msg.Params[1]
|
||||
op := ModeOp(modeArg[0])
|
||||
if (op == Add) || (op == Remove) {
|
||||
modeArg = modeArg[1:]
|
||||
} else {
|
||||
client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(modeArg[0]), "is an unknown mode character to me")
|
||||
if 1 < len(msg.Params) {
|
||||
// parse out real mode changes
|
||||
params := msg.Params[1:]
|
||||
changes, unknown := ParseUserModeChanges(params...)
|
||||
|
||||
// alert for unknown mode changes
|
||||
for char := range unknown {
|
||||
client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(char), "is an unknown mode character to me")
|
||||
}
|
||||
if len(unknown) == 1 && len(changes) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, mode := range modeArg {
|
||||
if mode == '-' || mode == '+' {
|
||||
op = ModeOp(mode)
|
||||
continue
|
||||
}
|
||||
changes = append(changes, ModeChange{
|
||||
mode: Mode(mode),
|
||||
op: op,
|
||||
})
|
||||
}
|
||||
|
||||
applied = target.applyUserModeChanges(changes)
|
||||
// apply mode changes
|
||||
applied = target.applyUserModeChanges(msg.Command == "SAMODE", changes)
|
||||
}
|
||||
|
||||
if len(applied) > 0 {
|
||||
client.Send(nil, client.nickMaskString, "MODE", target.nick, applied.String())
|
||||
} else if client == target {
|
||||
client.Send(nil, target.nickMaskString, RPL_UMODEIS, target.nick, target.ModeString())
|
||||
if client.flags[LocalOperator] || client.flags[Operator] {
|
||||
masks := server.snomasks.String(client)
|
||||
if 0 < len(masks) {
|
||||
client.Send(nil, target.nickMaskString, RPL_SNOMASKIS, target.nick, masks, "Server notice masks")
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -372,6 +442,7 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
|
||||
}
|
||||
if !isKnown {
|
||||
unknown[mode] = true
|
||||
continue
|
||||
}
|
||||
|
||||
changes = append(changes, change)
|
||||
|
@ -17,6 +17,7 @@ const (
|
||||
RPL_CREATED = "003"
|
||||
RPL_MYINFO = "004"
|
||||
RPL_ISUPPORT = "005"
|
||||
RPL_SNOMASKIS = "008"
|
||||
RPL_BOUNCE = "010"
|
||||
RPL_TRACELINK = "200"
|
||||
RPL_TRACECONNECTING = "201"
|
||||
|
@ -23,8 +23,10 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/DanielOaks/girc-go/ircfmt"
|
||||
"github.com/DanielOaks/girc-go/ircmsg"
|
||||
"github.com/DanielOaks/oragono/irc/logger"
|
||||
"github.com/DanielOaks/oragono/irc/sno"
|
||||
"github.com/tidwall/buntdb"
|
||||
)
|
||||
|
||||
@ -123,6 +125,7 @@ type Server struct {
|
||||
rehashSignal chan os.Signal
|
||||
restAPI *RestAPIConfig
|
||||
signals chan os.Signal
|
||||
snomasks *SnoManager
|
||||
store *buntdb.DB
|
||||
stsEnabled bool
|
||||
whoWas *WhoWasList
|
||||
@ -233,6 +236,7 @@ func NewServer(configFilename string, config *Config, logger *logger.Manager) (*
|
||||
rehashSignal: make(chan os.Signal, 1),
|
||||
restAPI: &config.Server.RestAPI,
|
||||
signals: make(chan os.Signal, len(ServerExitSignals)),
|
||||
snomasks: NewSnoManager(),
|
||||
stsEnabled: config.Server.STS.Enabled,
|
||||
whoWas: NewWhoWasList(config.Limits.WhowasEntries),
|
||||
}
|
||||
@ -474,6 +478,7 @@ func (server *Server) Run() {
|
||||
}
|
||||
|
||||
server.logger.Debug("localconnect-ip", fmt.Sprintf("Client connecting from %v", ipaddr))
|
||||
// prolly don't need to alert snomasks on this, only on connection reg
|
||||
|
||||
go NewClient(server, conn.Conn, conn.IsTLS)
|
||||
continue
|
||||
@ -664,6 +669,7 @@ func (server *Server) tryRegister(c *Client) {
|
||||
|
||||
// continue registration
|
||||
server.logger.Debug("localconnect", fmt.Sprintf("Client registered [%s] [u:%s] [r:%s]", c.nick, c.username, c.realname))
|
||||
server.snomasks.Send(sno.LocalConnects, fmt.Sprintf(ircfmt.Unescape("Client registered $c[grey][$r%s$c[grey]] [u:$r%s$c[grey]] [h:$r%s$c[grey]] [r:$r%s$c[grey]]"), c.nick, c.username, c.rawHostname, c.realname))
|
||||
c.Register()
|
||||
|
||||
// send welcome text
|
||||
@ -1263,13 +1269,27 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
client.updateNickMask()
|
||||
}
|
||||
|
||||
// set new modes
|
||||
var applied ModeChanges
|
||||
if 0 < len(server.operators[name].Modes) {
|
||||
modeChanges, unknownChanges := ParseUserModeChanges(strings.Split(server.operators[name].Modes, " ")...)
|
||||
applied = client.applyUserModeChanges(true, modeChanges)
|
||||
if 0 < len(unknownChanges) {
|
||||
var runes string
|
||||
for r := range unknownChanges {
|
||||
runes += string(r)
|
||||
}
|
||||
client.Notice(fmt.Sprintf("Could not apply mode changes: +%s", runes))
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
|
||||
applied = append(applied, ModeChange{
|
||||
mode: Operator,
|
||||
op: Add,
|
||||
}}
|
||||
client.Send(nil, server.name, "MODE", client.nick, modech.String())
|
||||
})
|
||||
client.Send(nil, server.name, "MODE", client.nick, applied.String())
|
||||
return false
|
||||
}
|
||||
|
||||
|
35
irc/sno/constants.go
Normal file
35
irc/sno/constants.go
Normal file
@ -0,0 +1,35 @@
|
||||
// Package sno holds Server Notice masks for easy reference.
|
||||
package sno
|
||||
|
||||
// Mask is a type of server notice mask.
|
||||
type Mask rune
|
||||
|
||||
// Notice mask types
|
||||
const (
|
||||
LocalAccouncements Mask = 'a'
|
||||
LocalConnects Mask = 'c'
|
||||
LocalChannels Mask = 'j'
|
||||
LocalKills Mask = 'k'
|
||||
LocalNicks Mask = 'n'
|
||||
LocalOpers Mask = 'o'
|
||||
LocalQuits Mask = 'q'
|
||||
Stats Mask = 't'
|
||||
LocalAccounts Mask = 'u'
|
||||
LocalXline Mask = 'x'
|
||||
)
|
||||
|
||||
var (
|
||||
// NoticeMaskNames has readable names for our snomask types.
|
||||
NoticeMaskNames = map[Mask]string{
|
||||
LocalAccouncements: "ANNOUNCEMENT",
|
||||
LocalConnects: "CONNECT",
|
||||
LocalChannels: "CHANNEL",
|
||||
LocalKills: "KILL",
|
||||
LocalNicks: "NICK",
|
||||
LocalOpers: "OPER",
|
||||
LocalQuits: "QUIT",
|
||||
Stats: "STATS",
|
||||
LocalAccounts: "ACCOUNT",
|
||||
LocalXline: "XLINE",
|
||||
}
|
||||
)
|
117
irc/snomanager.go
Normal file
117
irc/snomanager.go
Normal file
@ -0,0 +1,117 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/DanielOaks/girc-go/ircfmt"
|
||||
"github.com/DanielOaks/oragono/irc/sno"
|
||||
)
|
||||
|
||||
// SnoManager keeps track of which clients to send snomasks to.
|
||||
type SnoManager struct {
|
||||
sendListMutex sync.RWMutex
|
||||
sendLists map[sno.Mask]map[*Client]bool
|
||||
}
|
||||
|
||||
// NewSnoManager returns a new SnoManager
|
||||
func NewSnoManager() *SnoManager {
|
||||
var m SnoManager
|
||||
m.sendLists = make(map[sno.Mask]map[*Client]bool)
|
||||
return &m
|
||||
}
|
||||
|
||||
// AddMasks adds the given snomasks to the client.
|
||||
func (m *SnoManager) AddMasks(client *Client, masks ...sno.Mask) {
|
||||
m.sendListMutex.Lock()
|
||||
defer m.sendListMutex.Unlock()
|
||||
|
||||
for _, mask := range masks {
|
||||
currentClientList := m.sendLists[mask]
|
||||
|
||||
if currentClientList == nil {
|
||||
currentClientList = map[*Client]bool{}
|
||||
}
|
||||
|
||||
currentClientList[client] = true
|
||||
|
||||
m.sendLists[mask] = currentClientList
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveMasks removes the given snomasks from the client.
|
||||
func (m *SnoManager) RemoveMasks(client *Client, masks ...sno.Mask) {
|
||||
m.sendListMutex.Lock()
|
||||
defer m.sendListMutex.Unlock()
|
||||
|
||||
for _, mask := range masks {
|
||||
currentClientList := m.sendLists[mask]
|
||||
|
||||
if currentClientList == nil || len(currentClientList) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
delete(currentClientList, client)
|
||||
|
||||
m.sendLists[mask] = currentClientList
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveClient removes the given client from all of our lists.
|
||||
func (m *SnoManager) RemoveClient(client *Client) {
|
||||
m.sendListMutex.Lock()
|
||||
defer m.sendListMutex.Unlock()
|
||||
|
||||
for mask := range m.sendLists {
|
||||
currentClientList := m.sendLists[mask]
|
||||
|
||||
if currentClientList == nil || len(currentClientList) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
delete(currentClientList, client)
|
||||
|
||||
m.sendLists[mask] = currentClientList
|
||||
}
|
||||
}
|
||||
|
||||
// Send sends the given snomask to all users signed up for it.
|
||||
func (m *SnoManager) Send(mask sno.Mask, content string) {
|
||||
m.sendListMutex.RLock()
|
||||
defer m.sendListMutex.RUnlock()
|
||||
|
||||
currentClientList := m.sendLists[mask]
|
||||
|
||||
if currentClientList == nil || len(currentClientList) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// make the message
|
||||
name := sno.NoticeMaskNames[mask]
|
||||
if name == "" {
|
||||
name = string(mask)
|
||||
}
|
||||
message := fmt.Sprintf(ircfmt.Unescape("$c[grey]-$r%s$c[grey]-$c %s"), name, content)
|
||||
|
||||
// send it out
|
||||
for client := range currentClientList {
|
||||
client.Notice(message)
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the snomasks currently enabled.
|
||||
func (m *SnoManager) String(client *Client) string {
|
||||
m.sendListMutex.RLock()
|
||||
defer m.sendListMutex.RUnlock()
|
||||
|
||||
var masks string
|
||||
for mask, clients := range m.sendLists {
|
||||
for c := range clients {
|
||||
if c == client {
|
||||
masks += string(mask)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return masks
|
||||
}
|
@ -196,6 +196,9 @@ opers:
|
||||
# custom hostname
|
||||
vhost: "n"
|
||||
|
||||
# modes are the modes to auto-set upon opering-up
|
||||
modes: +is acjknoqtux
|
||||
|
||||
# password to login with /OPER command
|
||||
# generated using "oragono genpasswd"
|
||||
password: JDJhJDA0JE1vZmwxZC9YTXBhZ3RWT2xBbkNwZnV3R2N6VFUwQUI0RUJRVXRBRHliZVVoa0VYMnlIaGsu
|
||||
|
Loading…
Reference in New Issue
Block a user