3
0
mirror of https://github.com/ergochat/ergo.git synced 2025-02-16 21:50:39 +01:00

Add very initial snomasks

This commit is contained in:
Daniel Oaks 2017-05-08 09:15:16 +10:00
parent 1afd3b8f78
commit fd793d6adb
8 changed files with 286 additions and 34 deletions

View File

@ -9,6 +9,7 @@ New release of Oragono!
### Config Changes ### Config Changes
* Added `debug` section containing additional debug settings. * 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. * Added ability to log to `stdout` in logger methods.
### Security ### Security
@ -16,6 +17,7 @@ New release of Oragono!
### Added ### Added
* Added ability to log to stdout. * Added ability to log to stdout.
* Added ability to use StackImpact profiling. * Added ability to use StackImpact profiling.
* Added initial server notice masks (snomasks).
### Changed ### Changed
* Socket code rewritten to be a lot faster and safer. * Socket code rewritten to be a lot faster and safer.

View File

@ -94,6 +94,7 @@ type OperConfig struct {
Vhost string Vhost string
WhoisLine string `yaml:"whois-line"` WhoisLine string `yaml:"whois-line"`
Password string Password string
Modes string
} }
func (conf *OperConfig) PasswordBytes() []byte { func (conf *OperConfig) PasswordBytes() []byte {
@ -323,6 +324,7 @@ type Oper struct {
WhoisLine string WhoisLine string
Vhost string Vhost string
Pass []byte Pass []byte
Modes string
} }
// Operators returns a map of operator configs from the given OperClass and config. // 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 { } else {
oper.WhoisLine = class.WhoisLine oper.WhoisLine = class.WhoisLine
} }
oper.Modes = strings.TrimSpace(opConf.Modes)
// successful, attach to list of opers // successful, attach to list of opers
operators[name] = oper operators[name] = oper

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"github.com/DanielOaks/girc-go/ircmsg" "github.com/DanielOaks/girc-go/ircmsg"
"github.com/DanielOaks/oragono/irc/sno"
"github.com/tidwall/buntdb" "github.com/tidwall/buntdb"
) )
@ -97,7 +98,7 @@ const (
LocalOperator Mode = 'O' LocalOperator Mode = 'O'
Operator Mode = 'o' Operator Mode = 'o'
Restricted Mode = 'r' Restricted Mode = 'r'
ServerNotice Mode = 's' // deprecated ServerNotice Mode = 's'
TLS Mode = 'Z' TLS Mode = 'Z'
UserRoleplaying Mode = 'E' UserRoleplaying Mode = 'E'
WallOps Mode = 'w' WallOps Mode = 'w'
@ -105,7 +106,7 @@ const (
var ( var (
SupportedUserModes = Modes{ SupportedUserModes = Modes{
Away, Invisible, Operator, UserRoleplaying, Away, Invisible, Operator, ServerNotice, UserRoleplaying,
} }
// supportedUserModesString acts as a cache for when we introduce users // supportedUserModesString acts as a cache for when we introduce users
supportedUserModesString = SupportedUserModes.String() supportedUserModesString = SupportedUserModes.String()
@ -210,15 +211,77 @@ func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
return umodeHandler(server, client, msg) 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. // 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) applied := make(ModeChanges, 0)
for _, change := range changes { for _, change := range changes {
switch change.mode { switch change.mode {
case Invisible, ServerNotice, WallOps, UserRoleplaying: case Invisible, WallOps, UserRoleplaying, Operator, LocalOperator:
switch change.op { switch change.op {
case Add: case Add:
if !force && (change.mode == Operator || change.mode == LocalOperator) {
continue
}
if client.flags[change.mode] { if client.flags[change.mode] {
continue continue
} }
@ -233,12 +296,21 @@ func (client *Client) applyUserModeChanges(changes ModeChanges) ModeChanges {
applied = append(applied, change) applied = append(applied, change)
} }
case Operator, LocalOperator: case ServerNotice:
if change.op == Remove { if !client.flags[Operator] {
if !client.flags[change.mode] { continue
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) applied = append(applied, change)
} }
} }
@ -272,38 +344,36 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
return false return false
} }
// assemble changes // applied mode changes
changes := make(ModeChanges, 0)
applied := make(ModeChanges, 0) applied := make(ModeChanges, 0)
if len(msg.Params) > 1 { if 1 < len(msg.Params) {
modeArg := msg.Params[1] // parse out real mode changes
op := ModeOp(modeArg[0]) params := msg.Params[1:]
if (op == Add) || (op == Remove) { changes, unknown := ParseUserModeChanges(params...)
modeArg = modeArg[1:]
} else { // alert for unknown mode changes
client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(modeArg[0]), "is an unknown mode character to me") 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 return false
} }
for _, mode := range modeArg { // apply mode changes
if mode == '-' || mode == '+' { applied = target.applyUserModeChanges(msg.Command == "SAMODE", changes)
op = ModeOp(mode)
continue
}
changes = append(changes, ModeChange{
mode: Mode(mode),
op: op,
})
}
applied = target.applyUserModeChanges(changes)
} }
if len(applied) > 0 { if len(applied) > 0 {
client.Send(nil, client.nickMaskString, "MODE", target.nick, applied.String()) client.Send(nil, client.nickMaskString, "MODE", target.nick, applied.String())
} else if client == target { } else if client == target {
client.Send(nil, target.nickMaskString, RPL_UMODEIS, target.nick, target.ModeString()) 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 return false
} }
@ -372,6 +442,7 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
} }
if !isKnown { if !isKnown {
unknown[mode] = true unknown[mode] = true
continue
} }
changes = append(changes, change) changes = append(changes, change)

View File

@ -17,6 +17,7 @@ const (
RPL_CREATED = "003" RPL_CREATED = "003"
RPL_MYINFO = "004" RPL_MYINFO = "004"
RPL_ISUPPORT = "005" RPL_ISUPPORT = "005"
RPL_SNOMASKIS = "008"
RPL_BOUNCE = "010" RPL_BOUNCE = "010"
RPL_TRACELINK = "200" RPL_TRACELINK = "200"
RPL_TRACECONNECTING = "201" RPL_TRACECONNECTING = "201"

View File

@ -23,8 +23,10 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/DanielOaks/girc-go/ircfmt"
"github.com/DanielOaks/girc-go/ircmsg" "github.com/DanielOaks/girc-go/ircmsg"
"github.com/DanielOaks/oragono/irc/logger" "github.com/DanielOaks/oragono/irc/logger"
"github.com/DanielOaks/oragono/irc/sno"
"github.com/tidwall/buntdb" "github.com/tidwall/buntdb"
) )
@ -123,6 +125,7 @@ type Server struct {
rehashSignal chan os.Signal rehashSignal chan os.Signal
restAPI *RestAPIConfig restAPI *RestAPIConfig
signals chan os.Signal signals chan os.Signal
snomasks *SnoManager
store *buntdb.DB store *buntdb.DB
stsEnabled bool stsEnabled bool
whoWas *WhoWasList whoWas *WhoWasList
@ -233,6 +236,7 @@ func NewServer(configFilename string, config *Config, logger *logger.Manager) (*
rehashSignal: make(chan os.Signal, 1), rehashSignal: make(chan os.Signal, 1),
restAPI: &config.Server.RestAPI, restAPI: &config.Server.RestAPI,
signals: make(chan os.Signal, len(ServerExitSignals)), signals: make(chan os.Signal, len(ServerExitSignals)),
snomasks: NewSnoManager(),
stsEnabled: config.Server.STS.Enabled, stsEnabled: config.Server.STS.Enabled,
whoWas: NewWhoWasList(config.Limits.WhowasEntries), 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)) 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) go NewClient(server, conn.Conn, conn.IsTLS)
continue continue
@ -664,6 +669,7 @@ func (server *Server) tryRegister(c *Client) {
// continue registration // continue registration
server.logger.Debug("localconnect", fmt.Sprintf("Client registered [%s] [u:%s] [r:%s]", c.nick, c.username, c.realname)) 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() c.Register()
// send welcome text // send welcome text
@ -1263,13 +1269,27 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
client.updateNickMask() 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") 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, mode: Operator,
op: Add, op: Add,
}} })
client.Send(nil, server.name, "MODE", client.nick, modech.String()) client.Send(nil, server.name, "MODE", client.nick, applied.String())
return false return false
} }

35
irc/sno/constants.go Normal file
View 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
View 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
}

View File

@ -196,6 +196,9 @@ opers:
# custom hostname # custom hostname
vhost: "n" vhost: "n"
# modes are the modes to auto-set upon opering-up
modes: +is acjknoqtux
# password to login with /OPER command # password to login with /OPER command
# generated using "oragono genpasswd" # generated using "oragono genpasswd"
password: JDJhJDA0JE1vZmwxZC9YTXBhZ3RWT2xBbkNwZnV3R2N6VFUwQUI0RUJRVXRBRHliZVVoa0VYMnlIaGsu password: JDJhJDA0JE1vZmwxZC9YTXBhZ3RWT2xBbkNwZnV3R2N6VFUwQUI0RUJRVXRBRHliZVVoa0VYMnlIaGsu