Merge remote-tracking branch 'origin/master' into websockets_draft.2

This commit is contained in:
Shivaram Lingamneni
2020-04-30 22:16:07 -04:00
20 changed files with 268 additions and 77 deletions
+5
View File
@@ -1174,7 +1174,12 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
var channelsStr string
keepProtections := false
am.server.store.Update(func(tx *buntdb.Tx) error {
// get the unfolded account name; for an active account, this is
// stored under accountNameKey, for an unregistered account under unregisteredKey
accountName, _ = tx.Get(accountNameKey)
if accountName == "" {
accountName, _ = tx.Get(unregisteredKey)
}
if erase {
tx.Delete(unregisteredKey)
} else {
+48 -12
View File
@@ -541,10 +541,16 @@ func (channel *Channel) ClientPrefixes(client *Client, isMultiPrefix bool) strin
func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool {
channel.stateMutex.RLock()
founder := channel.registeredFounder
clientModes := channel.members[client]
targetModes := channel.members[target]
channel.stateMutex.RUnlock()
if founder != "" && founder == client.Account() {
// #950: founder can kick or whatever without actually having the +q mode
return true
}
return channelUserModeHasPrivsOver(clientModes.HighestChannelUserMode(), targetModes.HighestChannelUserMode())
}
@@ -1064,6 +1070,25 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
message := fmt.Sprintf(client.t("%[1]s changed nick to %[2]s"), nick, item.Params[0])
rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
}
case history.Topic:
if eventPlayback {
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "TOPIC", chname, item.Message.Message)
} else {
message := fmt.Sprintf(client.t("%[1]s set the channel topic to: %[2]s"), nick, item.Message.Message)
rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
}
case history.Mode:
params := make([]string, len(item.Message.Split)+1)
params[0] = chname
for i, pair := range item.Message.Split {
params[i+1] = pair.Message
}
if eventPlayback {
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "MODE", params...)
} else {
message := fmt.Sprintf(client.t("%[1]s set channel modes: %[2]s"), nick, strings.Join(params[1:], " "))
rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
}
}
}
}
@@ -1113,22 +1138,30 @@ func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffe
}
channel.stateMutex.Lock()
chname := channel.name
channel.topic = topic
channel.topicSetBy = client.nickMaskString
channel.topicSetTime = time.Now().UTC()
channel.stateMutex.Unlock()
prefix := client.NickMaskString()
details := client.Details()
message := utils.MakeMessage(topic)
rb.AddFromClient(message.Time, message.Msgid, details.nickMask, details.accountName, nil, "TOPIC", chname, topic)
for _, member := range channel.Members() {
for _, session := range member.Sessions() {
if session == rb.session {
rb.Add(nil, prefix, "TOPIC", channel.name, topic)
} else {
session.Send(nil, prefix, "TOPIC", channel.name, topic)
if session != rb.session {
session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, nil, "TOPIC", chname, topic)
}
}
}
channel.AddHistoryItem(history.Item{
Type: history.Topic,
Nick: details.nickMask,
AccountName: details.accountName,
Message: message,
})
channel.MarkDirty(IncludeTopic)
}
@@ -1251,13 +1284,16 @@ func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mod
}
}
channel.AddHistoryItem(history.Item{
Type: histType,
Message: message,
Nick: nickmask,
AccountName: account,
Tags: clientOnlyTags,
})
// #959: don't save STATUSMSG
if minPrefixMode == modes.Mode(0) {
channel.AddHistoryItem(history.Item{
Type: histType,
Message: message,
Nick: nickmask,
AccountName: account,
Tags: clientOnlyTags,
})
}
}
func (channel *Channel) applyModeToMember(client *Client, change modes.ModeChange, rb *ResponseBuffer) (applied bool, result modes.ModeChange) {
+3 -3
View File
@@ -244,7 +244,7 @@ func csAmodeHandler(server *Server, client *Client, command string, params []str
if member.Account() == change.Arg {
applied, change := channel.applyModeToMember(client, change, rb)
if applied {
announceCmodeChanges(channel, modes.ModeChanges{change}, chanservMask, rb)
announceCmodeChanges(channel, modes.ModeChanges{change}, chanservMask, "*", rb)
}
}
}
@@ -291,7 +291,7 @@ func csOpHandler(server *Server, client *Client, command string, params []string
},
rb)
if applied {
announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, rb)
announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, "*", rb)
}
csNotice(rb, fmt.Sprintf(client.t("Successfully op'd in channel %s"), channelName))
@@ -343,7 +343,7 @@ func csRegisterHandler(server *Server, client *Client, command string, params []
},
rb)
if applied {
announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, rb)
announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, "*", rb)
}
}
+8
View File
@@ -318,6 +318,10 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
session.idletimer.Initialize(session)
session.resetFakelag()
for _, defaultMode := range config.Accounts.defaultUserModes {
client.SetMode(defaultMode, true)
}
if conn.Config.TLSConfig != nil {
client.SetMode(modes.TLS, true)
// error is not useful to us here anyways so we can ignore it
@@ -371,6 +375,10 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
alwaysOn: true,
}
for _, defaultMode := range config.Accounts.defaultUserModes {
client.SetMode(defaultMode, true)
}
client.SetMode(modes.TLS, true)
client.writerSemaphore.Initialize(1)
client.history.Initialize(0, 0)
+10 -1
View File
@@ -205,9 +205,18 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
// the client may just be changing case
if currentClient != nil && currentClient != client && session != nil {
// these conditions forbid reattaching to an existing session:
if registered || !bouncerAllowed || account == "" || account != currentClient.Account() || client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
if registered || !bouncerAllowed || account == "" || account != currentClient.Account() {
return "", errNicknameInUse
}
// check TLS modes
if client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
if useAccountName {
// #955: this is fatal because they can't fix it by trying a different nick
return "", errInsecureReattach
} else {
return "", errNicknameInUse
}
}
reattachSuccessful, numSessions, lastSeen := currentClient.AddSession(session)
if !reattachSuccessful {
return "", errNicknameInUse
+5
View File
@@ -263,6 +263,8 @@ type AccountConfig struct {
Exempted []string
exemptedNets []net.IPNet
} `yaml:"require-sasl"`
DefaultUserModes *string `yaml:"default-user-modes"`
defaultUserModes modes.Modes
LDAP ldap.ServerConfig
LoginThrottling ThrottleConfig `yaml:"login-throttling"`
SkipServerPassword bool `yaml:"skip-server-password"`
@@ -552,6 +554,7 @@ type Config struct {
OperatorOnly bool `yaml:"operator-only"`
MaxChannelsPerAccount int `yaml:"max-channels-per-account"`
}
ListDelay time.Duration `yaml:"list-delay"`
}
OperClasses map[string]*OperClassConfig `yaml:"oper-classes"`
@@ -984,6 +987,8 @@ func LoadConfig(filename string) (config *Config, err error) {
}
}
config.Accounts.defaultUserModes = ParseDefaultUserModes(config.Accounts.DefaultUserModes)
config.Accounts.RequireSasl.exemptedNets, err = utils.ParseNetList(config.Accounts.RequireSasl.Exempted)
if err != nil {
return nil, fmt.Errorf("Could not parse require-sasl exempted nets: %v", err.Error())
+1
View File
@@ -42,6 +42,7 @@ var (
errNickMissing = errors.New("nick missing")
errNicknameInvalid = errors.New("invalid nickname")
errNicknameInUse = errors.New("nickname in use")
errInsecureReattach = errors.New("insecure reattach")
errNicknameReserved = errors.New("nickname is reserved")
errNickAccountMismatch = errors.New(`Your nickname must match your account name; try logging out and logging back in with SASL`)
errNoExistingBan = errors.New("Ban does not exist")
+40 -11
View File
@@ -1408,6 +1408,14 @@ func languageHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *
// LIST [<channel>{,<channel>}] [<elistcond>{,<elistcond>}]
func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
config := server.Config()
if time.Since(client.ctime) < config.Channels.ListDelay && client.Account() == "" && !client.HasMode(modes.Operator) {
remaining := time.Until(client.ctime.Add(config.Channels.ListDelay))
csNotice(rb, fmt.Sprintf(client.t("This server requires that you wait %v after connecting before you can use /LIST. You have %v left."), config.Channels.ListDelay, remaining))
rb.Add(nil, server.name, RPL_LISTEND, client.Nick(), client.t("End of LIST"))
return false
}
// get channels
var channels []string
for _, param := range msg.Params {
@@ -1520,24 +1528,35 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
}
// process mode changes, include list operations (an empty set of changes does a list)
applied := channel.ApplyChannelModeChanges(client, msg.Command == "SAMODE", changes, rb)
announceCmodeChanges(channel, applied, client.NickMaskString(), rb)
details := client.Details()
announceCmodeChanges(channel, applied, details.nickMask, details.accountName, rb)
return false
}
func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source string, rb *ResponseBuffer) {
func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source, accountName string, rb *ResponseBuffer) {
// send out changes
if len(applied) > 0 {
//TODO(dan): we should change the name of String and make it return a slice here
args := append([]string{channel.name}, applied.Strings()...)
rb.Add(nil, source, "MODE", args...)
message := utils.MakeMessage("")
changeStrings := applied.Strings()
for _, changeString := range changeStrings {
message.Split = append(message.Split, utils.MessagePair{Message: changeString})
}
args := append([]string{channel.name}, changeStrings...)
rb.AddFromClient(message.Time, message.Msgid, source, accountName, nil, "MODE", args...)
for _, member := range channel.Members() {
for _, session := range member.Sessions() {
if session != rb.session {
session.Send(nil, source, "MODE", args...)
session.sendFromClientInternal(false, message.Time, message.Msgid, source, accountName, nil, "MODE", args...)
}
}
}
channel.AddHistoryItem(history.Item{
Type: history.Mode,
Nick: source,
AccountName: accountName,
Message: message,
})
}
}
@@ -2054,7 +2073,7 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
}
// must pass at least one check, and all enabled checks
var checkPassed, checkFailed bool
var checkPassed, checkFailed, passwordFailed bool
oper := server.GetOperator(msg.Params[0])
if oper != nil {
if oper.Fingerprint != "" {
@@ -2065,8 +2084,11 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
}
}
if !checkFailed && oper.Pass != nil {
if len(msg.Params) == 1 || bcrypt.CompareHashAndPassword(oper.Pass, []byte(msg.Params[1])) != nil {
if len(msg.Params) == 1 {
checkFailed = true
} else if bcrypt.CompareHashAndPassword(oper.Pass, []byte(msg.Params[1])) != nil {
checkFailed = true
passwordFailed = true
} else {
checkPassed = true
}
@@ -2075,11 +2097,18 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
if !checkPassed || checkFailed {
rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.Nick(), client.t("Password incorrect"))
client.Quit(client.t("Password incorrect"), rb.session)
return true
// #951: only disconnect them if we actually tried to check a password for them
if passwordFailed {
client.Quit(client.t("Password incorrect"), rb.session)
return true
} else {
return false
}
}
applyOper(client, oper, rb)
if oper != nil {
applyOper(client, oper, rb)
}
return false
}
+1
View File
@@ -22,6 +22,7 @@ const (
Mode
Tagmsg
Nick
Topic
)
const (
+26 -8
View File
@@ -20,6 +20,10 @@ var (
DefaultChannelModes = modes.Modes{
modes.NoOutside, modes.OpOnlyTopic,
}
// DefaultUserModes are set on all users when they login.
// this can be overridden in the `accounts` config, with the `default-user-modes` key
DefaultUserModes = modes.Modes{}
)
// ApplyUserModeChanges applies the given changes, and returns the applied changes.
@@ -102,21 +106,35 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool,
return applied
}
// parseDefaultModes uses the provided mode change parser to parse the rawModes.
func parseDefaultModes(rawModes string, parser func(params ...string) (modes.ModeChanges, map[rune]bool)) modes.Modes {
modeChangeStrings := strings.Fields(rawModes)
modeChanges, _ := parser(modeChangeStrings...)
defaultModes := make(modes.Modes, 0)
for _, modeChange := range modeChanges {
if modeChange.Op == modes.Add {
defaultModes = append(defaultModes, modeChange.Mode)
}
}
return defaultModes
}
// ParseDefaultChannelModes parses the `default-modes` line of the config
func ParseDefaultChannelModes(rawModes *string) modes.Modes {
if rawModes == nil {
// not present in config, fall back to compile-time default
return DefaultChannelModes
}
modeChangeStrings := strings.Fields(*rawModes)
modeChanges, _ := modes.ParseChannelModeChanges(modeChangeStrings...)
defaultChannelModes := make(modes.Modes, 0)
for _, modeChange := range modeChanges {
if modeChange.Op == modes.Add {
defaultChannelModes = append(defaultChannelModes, modeChange.Mode)
}
return parseDefaultModes(*rawModes, modes.ParseChannelModeChanges)
}
// ParseDefaultUserModes parses the `default-user-modes` line of the config
func ParseDefaultUserModes(rawModes *string) modes.Modes {
if rawModes == nil {
// not present in config, fall back to compile-time default
return DefaultUserModes
}
return defaultChannelModes
return parseDefaultModes(*rawModes, modes.ParseUserModeChanges)
}
// ApplyChannelModeChanges applies a given set of mode changes.
+25
View File
@@ -35,6 +35,31 @@ func TestParseDefaultChannelModes(t *testing.T) {
}
}
func TestParseDefaultUserModes(t *testing.T) {
iR := "+iR"
i := "+i"
empty := ""
rminusi := "+R -i"
var parseTests = []struct {
raw *string
expected modes.Modes
}{
{&iR, modes.Modes{modes.Invisible, modes.RegisteredOnly}},
{&i, modes.Modes{modes.Invisible}},
{&empty, modes.Modes{}},
{&rminusi, modes.Modes{modes.RegisteredOnly}},
{nil, modes.Modes{}},
}
for _, testcase := range parseTests {
result := ParseDefaultUserModes(testcase.raw)
if !reflect.DeepEqual(result, testcase.expected) {
t.Errorf("expected modes %s, got %s", testcase.expected, result)
}
}
}
func TestUmodeGreaterThan(t *testing.T) {
if !umodeGreaterThan(modes.Halfop, modes.Voice) {
t.Errorf("expected Halfop > Voice")
+5 -6
View File
@@ -25,12 +25,11 @@ var (
restrictedSkeletons = make(map[string]bool)
)
// returns whether the change succeeded or failed
func performNickChange(server *Server, client *Client, target *Client, session *Session, nickname string, rb *ResponseBuffer) bool {
func performNickChange(server *Server, client *Client, target *Client, session *Session, nickname string, rb *ResponseBuffer) error {
currentNick := client.Nick()
details := target.Details()
if details.nick == nickname {
return true
return nil
}
hadNick := details.nick != "*"
origNickMask := details.nickMask
@@ -52,7 +51,7 @@ func performNickChange(server *Server, client *Client, target *Client, session *
rb.Add(nil, server.name, ERR_UNKNOWNERROR, currentNick, "NICK", fmt.Sprintf(client.t("Could not set or change nickname: %s"), err.Error()))
}
if err != nil {
return false
return err
}
message := utils.MakeMessage("")
@@ -88,7 +87,7 @@ func performNickChange(server *Server, client *Client, target *Client, session *
client.server.monitorManager.AlertAbout(target, true)
target.nickTimer.Touch(rb)
} // else: these will be deferred to the end of registration (see #572)
return true
return nil
}
func (server *Server) RandomlyRename(client *Client) {
@@ -124,7 +123,7 @@ func fixupNickEqualsAccount(client *Client, rb *ResponseBuffer, config *Config)
if !client.registered {
return true
}
if !performNickChange(client.server, client, client, rb.session, client.AccountName(), rb) {
if performNickChange(client.server, client, client, rb.session, client.AccountName(), rb) != nil {
client.server.accounts.Logout(client)
nsNotice(rb, client.t("A client is already using that account; try logging out and logging back in with SASL"))
return false
+2
View File
@@ -841,6 +841,8 @@ func nsUnregisterHandler(server *Server, client *Client, command string, params
if erase {
// account may not be in a loadable state, e.g., if it was unregistered
accountName = username
// make the confirmation code nondeterministic for ERASE
registeredAt = server.ctime
} else {
account, err := server.accounts.LoadAccount(username)
if err == errAccountDoesNotExist {
+7 -4
View File
@@ -438,11 +438,14 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) {
}
rb := NewResponseBuffer(session)
nickAssigned := performNickChange(server, c, c, session, c.preregNick, rb)
nickError := performNickChange(server, c, c, session, c.preregNick, rb)
rb.Send(true)
if !nickAssigned {
if nickError == errInsecureReattach {
c.Quit(c.t("You can't mix secure and insecure connections to this account"), nil)
return true
} else if nickError != nil {
c.preregNick = ""
return
return false
}
if session.client != c {
@@ -450,7 +453,7 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) {
// we'll play the reg burst later, on the new goroutine associated with
// (thisSession, otherClient). This is to avoid having to transfer state
// like nickname, hostname, etc. to show the correct values in the reg burst.
return
return false
}
// check KLINEs