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

@ -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

@ -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

@ -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

@ -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