mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-29 07:29:31 +01:00
Move from ascii(ish) unicode encoding to prelim rfc7700 using functions instead
This commit is contained in:
parent
2bfcc553ce
commit
5e72409695
@ -80,9 +80,9 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage)
|
||||
// sasl abort
|
||||
if len(msg.Params) == 1 && msg.Params[0] == "*" {
|
||||
if client.saslInProgress {
|
||||
client.Send(nil, server.nameString, ERR_SASLABORTED, client.nickString, "SASL authentication aborted")
|
||||
client.Send(nil, server.name, ERR_SASLABORTED, client.nick, "SASL authentication aborted")
|
||||
} else {
|
||||
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed")
|
||||
client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
|
||||
}
|
||||
client.saslInProgress = false
|
||||
client.saslMechanism = ""
|
||||
@ -98,9 +98,9 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage)
|
||||
if mechanismIsEnabled {
|
||||
client.saslInProgress = true
|
||||
client.saslMechanism = mechanism
|
||||
client.Send(nil, server.nameString, "AUTHENTICATE", "+")
|
||||
client.Send(nil, server.name, "AUTHENTICATE", "+")
|
||||
} else {
|
||||
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed")
|
||||
client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
|
||||
}
|
||||
|
||||
return false
|
||||
@ -110,7 +110,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage)
|
||||
rawData := msg.Params[0]
|
||||
|
||||
if len(rawData) > 400 {
|
||||
client.Send(nil, server.nameString, ERR_SASLTOOLONG, client.nickString, "SASL message too long")
|
||||
client.Send(nil, server.name, ERR_SASLTOOLONG, client.nick, "SASL message too long")
|
||||
client.saslInProgress = false
|
||||
client.saslMechanism = ""
|
||||
client.saslValue = ""
|
||||
@ -119,7 +119,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage)
|
||||
client.saslValue += rawData
|
||||
// allow 4 'continuation' lines before rejecting for length
|
||||
if len(client.saslValue) > 400*4 {
|
||||
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed: Passphrase too long")
|
||||
client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Passphrase too long")
|
||||
client.saslInProgress = false
|
||||
client.saslMechanism = ""
|
||||
client.saslValue = ""
|
||||
@ -136,7 +136,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage)
|
||||
if client.saslValue != "+" {
|
||||
data, err = base64.StdEncoding.DecodeString(client.saslValue)
|
||||
if err != nil {
|
||||
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed: Invalid b64 encoding")
|
||||
client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Invalid b64 encoding")
|
||||
client.saslInProgress = false
|
||||
client.saslMechanism = ""
|
||||
client.saslValue = ""
|
||||
@ -149,7 +149,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage)
|
||||
|
||||
// like 100% not required, but it's good to be safe I guess
|
||||
if !handlerExists {
|
||||
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed")
|
||||
client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
|
||||
client.saslInProgress = false
|
||||
client.saslMechanism = ""
|
||||
client.saslValue = ""
|
||||
@ -169,7 +169,7 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
|
||||
splitValue := bytes.Split(value, []byte{'\000'})
|
||||
|
||||
if len(splitValue) != 3 {
|
||||
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed: Invalid auth blob")
|
||||
client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Invalid auth blob")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -177,17 +177,20 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
|
||||
authzid := string(splitValue[1])
|
||||
|
||||
if accountKey != authzid {
|
||||
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed: authcid and authzid should be the same")
|
||||
client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: authcid and authzid should be the same")
|
||||
return false
|
||||
}
|
||||
|
||||
// casefolding, rough for now bit will improve later.
|
||||
// keep it the same as in the REG CREATE stage
|
||||
accountKey = NewName(accountKey).String()
|
||||
accountKey, err := CasefoldName(accountKey)
|
||||
if err != nil {
|
||||
client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Bad account name")
|
||||
return false
|
||||
}
|
||||
|
||||
// load and check acct data all in one update to prevent races.
|
||||
// as noted elsewhere, change to proper locking for Account type later probably
|
||||
err := server.store.Update(func(tx *buntdb.Tx) error {
|
||||
err = server.store.Update(func(tx *buntdb.Tx) error {
|
||||
creds, err := loadAccountCredentials(tx, accountKey)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -213,19 +216,19 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed")
|
||||
client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
|
||||
return false
|
||||
}
|
||||
|
||||
client.Send(nil, server.nameString, RPL_LOGGEDIN, client.nickString, client.nickMaskString, client.account.Name, fmt.Sprintf("You are now logged in as %s", client.account.Name))
|
||||
client.Send(nil, server.nameString, RPL_SASLSUCCESS, client.nickString, "SASL authentication successful")
|
||||
client.Send(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, client.account.Name, fmt.Sprintf("You are now logged in as %s", client.account.Name))
|
||||
client.Send(nil, server.name, RPL_SASLSUCCESS, client.nick, "SASL authentication successful")
|
||||
return false
|
||||
}
|
||||
|
||||
// authExternalHandler parses the SASL EXTERNAL mechanism.
|
||||
func authExternalHandler(server *Server, client *Client, mechanism string, value []byte) bool {
|
||||
if client.certfp == "" {
|
||||
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed, you are not connecting with a certificate")
|
||||
client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed, you are not connecting with a caertificate")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -261,11 +264,11 @@ func authExternalHandler(server *Server, client *Client, mechanism string, value
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed")
|
||||
client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
|
||||
return false
|
||||
}
|
||||
|
||||
client.Send(nil, server.nameString, RPL_LOGGEDIN, client.nickString, client.nickMaskString, client.account.Name, fmt.Sprintf("You are now logged in as %s", client.account.Name))
|
||||
client.Send(nil, server.nameString, RPL_SASLSUCCESS, client.nickString, "SASL authentication successful")
|
||||
client.Send(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, client.account.Name, fmt.Sprintf("You are now logged in as %s", client.account.Name))
|
||||
client.Send(nil, server.name, RPL_SASLSUCCESS, client.nick, "SASL authentication successful")
|
||||
return false
|
||||
}
|
||||
|
@ -107,23 +107,23 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
// client.server needs to be here to workaround a parsing bug in weechat 1.4
|
||||
// and let it connect to the server (otherwise it doesn't respond to the CAP
|
||||
// message with anything and just hangs on connection)
|
||||
client.Send(nil, server.nameString, "CAP", client.nickString, subCommand, SupportedCapabilities.String())
|
||||
client.Send(nil, server.name, "CAP", client.nick, subCommand, SupportedCapabilities.String())
|
||||
|
||||
case "LIST":
|
||||
client.Send(nil, server.nameString, "CAP", client.nickString, subCommand, client.capabilities.String())
|
||||
client.Send(nil, server.name, "CAP", client.nick, subCommand, client.capabilities.String())
|
||||
|
||||
case "REQ":
|
||||
// make sure all capabilities actually exist
|
||||
for capability := range capabilities {
|
||||
if !SupportedCapabilities[capability] {
|
||||
client.Send(nil, server.nameString, "CAP", client.nickString, "NAK", capString)
|
||||
client.Send(nil, server.name, "CAP", client.nick, "NAK", capString)
|
||||
return false
|
||||
}
|
||||
}
|
||||
for capability := range capabilities {
|
||||
client.capabilities[capability] = true
|
||||
}
|
||||
client.Send(nil, server.nameString, "CAP", client.nickString, "ACK", capString)
|
||||
client.Send(nil, server.name, "CAP", client.nick, "ACK", capString)
|
||||
|
||||
case "END":
|
||||
if !client.registered {
|
||||
@ -132,7 +132,7 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
}
|
||||
|
||||
default:
|
||||
client.Send(nil, server.nameString, ERR_INVALIDCAPCMD, client.nickString, subCommand, "Invalid CAP subcommand")
|
||||
client.Send(nil, server.name, ERR_INVALIDCAPCMD, client.nick, subCommand, "Invalid CAP subcommand")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
143
irc/channel.go
143
irc/channel.go
@ -6,29 +6,36 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
flags ChannelModeSet
|
||||
lists map[ChannelMode]*UserMaskSet
|
||||
key string
|
||||
members MemberSet
|
||||
name Name
|
||||
nameString string
|
||||
server *Server
|
||||
createdTime time.Time
|
||||
topic string
|
||||
topicSetBy string
|
||||
topicSetTime time.Time
|
||||
userLimit uint64
|
||||
flags ChannelModeSet
|
||||
lists map[ChannelMode]*UserMaskSet
|
||||
key string
|
||||
members MemberSet
|
||||
name string
|
||||
nameCasefolded string
|
||||
server *Server
|
||||
createdTime time.Time
|
||||
topic string
|
||||
topicSetBy string
|
||||
topicSetTime time.Time
|
||||
userLimit uint64
|
||||
}
|
||||
|
||||
// NewChannel creates a new channel from a `Server` and a `name`
|
||||
// string, which must be unique on the server.
|
||||
func NewChannel(s *Server, name Name, addDefaultModes bool) *Channel {
|
||||
func NewChannel(s *Server, name string, addDefaultModes bool) *Channel {
|
||||
casefoldedName, err := CasefoldChannel(name)
|
||||
if err != nil {
|
||||
log.Println(fmt.Sprintf("ERROR: Channel name is bad: [%s]", name), err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
channel := &Channel{
|
||||
flags: make(ChannelModeSet),
|
||||
lists: map[ChannelMode]*UserMaskSet{
|
||||
@ -36,10 +43,10 @@ func NewChannel(s *Server, name Name, addDefaultModes bool) *Channel {
|
||||
ExceptMask: NewUserMaskSet(),
|
||||
InviteMask: NewUserMaskSet(),
|
||||
},
|
||||
members: make(MemberSet),
|
||||
name: name,
|
||||
nameString: name.String(),
|
||||
server: s,
|
||||
members: make(MemberSet),
|
||||
name: name,
|
||||
nameCasefolded: casefoldedName,
|
||||
server: s,
|
||||
}
|
||||
|
||||
if addDefaultModes {
|
||||
@ -60,7 +67,7 @@ func (channel *Channel) IsEmpty() bool {
|
||||
func (channel *Channel) Names(client *Client) {
|
||||
currentNicks := channel.Nicks(client)
|
||||
// assemble and send replies
|
||||
maxNamLen := 480 - len(client.server.nameString) - len(client.nickString)
|
||||
maxNamLen := 480 - len(client.server.name) - len(client.nick)
|
||||
var buffer string
|
||||
for _, nick := range currentNicks {
|
||||
if buffer == "" {
|
||||
@ -69,7 +76,7 @@ func (channel *Channel) Names(client *Client) {
|
||||
}
|
||||
|
||||
if len(buffer)+1+len(nick) > maxNamLen {
|
||||
client.Send(nil, client.server.nameString, RPL_NAMREPLY, client.nickString, "=", channel.nameString, buffer)
|
||||
client.Send(nil, client.server.name, RPL_NAMREPLY, client.nick, "=", channel.name, buffer)
|
||||
buffer = nick
|
||||
continue
|
||||
}
|
||||
@ -78,8 +85,8 @@ func (channel *Channel) Names(client *Client) {
|
||||
buffer += nick
|
||||
}
|
||||
|
||||
client.Send(nil, client.server.nameString, RPL_NAMREPLY, client.nickString, "=", channel.nameString, buffer)
|
||||
client.Send(nil, client.server.nameString, RPL_ENDOFNAMES, client.nickString, channel.nameString, "End of NAMES list")
|
||||
client.Send(nil, client.server.name, RPL_NAMREPLY, client.nick, "=", channel.name, buffer)
|
||||
client.Send(nil, client.server.name, RPL_ENDOFNAMES, client.nick, channel.name, "End of NAMES list")
|
||||
}
|
||||
|
||||
func (channel *Channel) ClientIsOperator(client *Client) bool {
|
||||
@ -117,25 +124,21 @@ func (channel *Channel) Nicks(target *Client) []string {
|
||||
if isUserhostInNames {
|
||||
nicks[i] += client.nickMaskString
|
||||
} else {
|
||||
nicks[i] += client.nickString
|
||||
nicks[i] += client.nick
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return nicks
|
||||
}
|
||||
|
||||
func (channel *Channel) Id() Name {
|
||||
func (channel *Channel) Id() string {
|
||||
return channel.name
|
||||
}
|
||||
|
||||
func (channel *Channel) Nick() Name {
|
||||
func (channel *Channel) Nick() string {
|
||||
return channel.name
|
||||
}
|
||||
|
||||
func (channel *Channel) String() string {
|
||||
return channel.Id().String()
|
||||
}
|
||||
|
||||
// <mode> <mode params>
|
||||
func (channel *Channel) ModeString(client *Client) (str string) {
|
||||
isMember := client.flags[Operator] || channel.members.Has(client)
|
||||
@ -185,33 +188,33 @@ func (channel *Channel) Join(client *Client, key string) {
|
||||
}
|
||||
|
||||
if channel.IsFull() {
|
||||
client.Send(nil, client.server.nameString, ERR_CHANNELISFULL, channel.nameString, "Cannot join channel (+l)")
|
||||
client.Send(nil, client.server.name, ERR_CHANNELISFULL, channel.name, "Cannot join channel (+l)")
|
||||
return
|
||||
}
|
||||
|
||||
if !channel.CheckKey(key) {
|
||||
client.Send(nil, client.server.nameString, ERR_BADCHANNELKEY, channel.nameString, "Cannot join channel (+k)")
|
||||
client.Send(nil, client.server.name, ERR_BADCHANNELKEY, channel.name, "Cannot join channel (+k)")
|
||||
return
|
||||
}
|
||||
|
||||
isInvited := channel.lists[InviteMask].Match(client.UserHost())
|
||||
if channel.flags[InviteOnly] && !isInvited {
|
||||
client.Send(nil, client.server.nameString, ERR_INVITEONLYCHAN, channel.nameString, "Cannot join channel (+i)")
|
||||
client.Send(nil, client.server.name, ERR_INVITEONLYCHAN, channel.name, "Cannot join channel (+i)")
|
||||
return
|
||||
}
|
||||
|
||||
if channel.lists[BanMask].Match(client.UserHost()) &&
|
||||
!isInvited &&
|
||||
!channel.lists[ExceptMask].Match(client.UserHost()) {
|
||||
client.Send(nil, client.server.nameString, ERR_BANNEDFROMCHAN, channel.nameString, "Cannot join channel (+b)")
|
||||
client.Send(nil, client.server.name, ERR_BANNEDFROMCHAN, channel.name, "Cannot join channel (+b)")
|
||||
return
|
||||
}
|
||||
|
||||
for member := range channel.members {
|
||||
if member.capabilities[ExtendedJoin] {
|
||||
member.Send(nil, client.nickMaskString, "JOIN", channel.nameString, client.account.Name, client.realname)
|
||||
member.Send(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname)
|
||||
} else {
|
||||
member.Send(nil, client.nickMaskString, "JOIN", channel.nameString)
|
||||
member.Send(nil, client.nickMaskString, "JOIN", channel.name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,9 +227,9 @@ func (channel *Channel) Join(client *Client, key string) {
|
||||
}
|
||||
|
||||
if client.capabilities[ExtendedJoin] {
|
||||
client.Send(nil, client.nickMaskString, "JOIN", channel.nameString, client.account.Name, client.realname)
|
||||
client.Send(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname)
|
||||
} else {
|
||||
client.Send(nil, client.nickMaskString, "JOIN", channel.nameString)
|
||||
client.Send(nil, client.nickMaskString, "JOIN", channel.name)
|
||||
}
|
||||
channel.GetTopic(client)
|
||||
channel.Names(client)
|
||||
@ -234,39 +237,39 @@ func (channel *Channel) Join(client *Client, key string) {
|
||||
|
||||
func (channel *Channel) Part(client *Client, message string) {
|
||||
if !channel.members.Has(client) {
|
||||
client.Send(nil, client.server.nameString, ERR_NOTONCHANNEL, channel.nameString, "You're not on that channel")
|
||||
client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel")
|
||||
return
|
||||
}
|
||||
|
||||
for member := range channel.members {
|
||||
member.Send(nil, client.nickMaskString, "PART", channel.nameString, message)
|
||||
member.Send(nil, client.nickMaskString, "PART", channel.name, message)
|
||||
}
|
||||
channel.Quit(client)
|
||||
}
|
||||
|
||||
func (channel *Channel) GetTopic(client *Client) {
|
||||
if !channel.members.Has(client) {
|
||||
client.Send(nil, client.server.nameString, ERR_NOTONCHANNEL, client.nickString, channel.nameString, "You're not on that channel")
|
||||
client.Send(nil, client.server.name, ERR_NOTONCHANNEL, client.nick, channel.name, "You're not on that channel")
|
||||
return
|
||||
}
|
||||
|
||||
if channel.topic == "" {
|
||||
client.Send(nil, client.server.nameString, RPL_NOTOPIC, client.nickString, channel.nameString, "No topic is set")
|
||||
client.Send(nil, client.server.name, RPL_NOTOPIC, client.nick, channel.name, "No topic is set")
|
||||
return
|
||||
}
|
||||
|
||||
client.Send(nil, client.server.nameString, RPL_TOPIC, client.nickString, channel.nameString, channel.topic)
|
||||
client.Send(nil, client.server.nameString, RPL_TOPICTIME, client.nickString, channel.nameString, channel.topicSetBy, strconv.FormatInt(channel.topicSetTime.Unix(), 10))
|
||||
client.Send(nil, client.server.name, RPL_TOPIC, client.nick, channel.name, channel.topic)
|
||||
client.Send(nil, client.server.name, RPL_TOPICTIME, client.nick, channel.name, channel.topicSetBy, strconv.FormatInt(channel.topicSetTime.Unix(), 10))
|
||||
}
|
||||
|
||||
func (channel *Channel) SetTopic(client *Client, topic string) {
|
||||
if !(client.flags[Operator] || channel.members.Has(client)) {
|
||||
client.Send(nil, client.server.nameString, ERR_NOTONCHANNEL, channel.nameString, "You're not on that channel")
|
||||
client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel")
|
||||
return
|
||||
}
|
||||
|
||||
if channel.flags[OpOnlyTopic] && !channel.ClientIsOperator(client) {
|
||||
client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator")
|
||||
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator")
|
||||
return
|
||||
}
|
||||
|
||||
@ -275,11 +278,11 @@ func (channel *Channel) SetTopic(client *Client, topic string) {
|
||||
}
|
||||
|
||||
channel.topic = topic
|
||||
channel.topicSetBy = client.nickString
|
||||
channel.topicSetBy = client.nick
|
||||
channel.topicSetTime = time.Now()
|
||||
|
||||
for member := range channel.members {
|
||||
member.Send(nil, client.nickMaskString, "TOPIC", channel.nameString, channel.topic)
|
||||
member.Send(nil, client.nickMaskString, "TOPIC", channel.name, channel.topic)
|
||||
}
|
||||
|
||||
channel.Persist()
|
||||
@ -301,21 +304,21 @@ func (channel *Channel) CanSpeak(client *Client) bool {
|
||||
|
||||
func (channel *Channel) PrivMsg(client *Client, message string) {
|
||||
if !channel.CanSpeak(client) {
|
||||
client.Send(nil, client.server.nameString, ERR_CANNOTSENDTOCHAN, channel.nameString, "Cannot send to channel")
|
||||
client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel")
|
||||
return
|
||||
}
|
||||
for member := range channel.members {
|
||||
if member == client {
|
||||
continue
|
||||
}
|
||||
member.SendFromClient(client, nil, client.nickMaskString, "PRIVMSG", channel.nameString, message)
|
||||
member.SendFromClient(client, nil, client.nickMaskString, "PRIVMSG", channel.name, message)
|
||||
}
|
||||
}
|
||||
|
||||
func (channel *Channel) applyModeFlag(client *Client, mode ChannelMode,
|
||||
op ModeOp) bool {
|
||||
if !channel.ClientIsOperator(client) {
|
||||
client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator")
|
||||
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -341,20 +344,19 @@ func (channel *Channel) applyModeMember(client *Client, mode ChannelMode,
|
||||
op ModeOp, nick string) *ChannelModeChange {
|
||||
if nick == "" {
|
||||
//TODO(dan): shouldn't this be handled before it reaches this function?
|
||||
client.Send(nil, client.server.nameString, ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters")
|
||||
client.Send(nil, client.server.name, ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters")
|
||||
return nil
|
||||
}
|
||||
|
||||
target := channel.server.clients.Get(Name(nick))
|
||||
if target == nil {
|
||||
//TODO(dan): investigate using NOSUCHNICK and NOSUCHCHANNEL specifically as that other IRCd (insp?) does,
|
||||
// since I think that would make sense
|
||||
client.Send(nil, client.server.nameString, ERR_NOSUCHNICK, nick, "No such nick")
|
||||
casefoldedName, err := CasefoldName(nick)
|
||||
target := channel.server.clients.Get(casefoldedName)
|
||||
if err != nil || target == nil {
|
||||
client.Send(nil, client.server.name, ERR_NOSUCHNICK, nick, "No such nick")
|
||||
return nil
|
||||
}
|
||||
|
||||
if !channel.members.Has(target) {
|
||||
client.Send(nil, client.server.nameString, ERR_USERNOTINCHANNEL, client.nickString, channel.nameString, "They aren't on that channel")
|
||||
client.Send(nil, client.server.name, ERR_USERNOTINCHANNEL, client.nick, channel.name, "They aren't on that channel")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -394,8 +396,7 @@ func (channel *Channel) ShowMaskList(client *Client, mode ChannelMode) {
|
||||
client.RplEndOfMaskList(mode, channel)*/
|
||||
}
|
||||
|
||||
func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeOp,
|
||||
mask Name) bool {
|
||||
func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeOp, mask string) bool {
|
||||
list := channel.lists[mode]
|
||||
if list == nil {
|
||||
// This should never happen, but better safe than panicky.
|
||||
@ -408,7 +409,7 @@ func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeO
|
||||
}
|
||||
|
||||
if !channel.ClientIsOperator(client) {
|
||||
client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator")
|
||||
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -453,14 +454,14 @@ func (channel *Channel) Persist() (err error) {
|
||||
|
||||
func (channel *Channel) Notice(client *Client, message string) {
|
||||
if !channel.CanSpeak(client) {
|
||||
client.Send(nil, client.server.nameString, ERR_CANNOTSENDTOCHAN, channel.nameString, "Cannot send to channel")
|
||||
client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel")
|
||||
return
|
||||
}
|
||||
for member := range channel.members {
|
||||
if member == client {
|
||||
continue
|
||||
}
|
||||
member.SendFromClient(client, nil, client.nickMaskString, "NOTICE", channel.nameString, message)
|
||||
member.SendFromClient(client, nil, client.nickMaskString, "NOTICE", channel.name, message)
|
||||
}
|
||||
}
|
||||
|
||||
@ -475,15 +476,15 @@ func (channel *Channel) Quit(client *Client) {
|
||||
|
||||
func (channel *Channel) Kick(client *Client, target *Client, comment string) {
|
||||
if !(client.flags[Operator] || channel.members.Has(client)) {
|
||||
client.Send(nil, client.server.nameString, ERR_NOTONCHANNEL, channel.nameString, "You're not on that channel")
|
||||
client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel")
|
||||
return
|
||||
}
|
||||
if !channel.ClientIsOperator(client) {
|
||||
client.Send(nil, client.server.nameString, ERR_CANNOTSENDTOCHAN, channel.nameString, "Cannot send to channel")
|
||||
client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel")
|
||||
return
|
||||
}
|
||||
if !channel.members.Has(target) {
|
||||
client.Send(nil, client.server.nameString, ERR_USERNOTINCHANNEL, client.nickString, channel.nameString, "They aren't on that channel")
|
||||
client.Send(nil, client.server.name, ERR_USERNOTINCHANNEL, client.nick, channel.name, "They aren't on that channel")
|
||||
return
|
||||
}
|
||||
|
||||
@ -492,19 +493,19 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string) {
|
||||
}
|
||||
|
||||
for member := range channel.members {
|
||||
member.Send(nil, client.nickMaskString, "KICK", channel.nameString, target.nickString, comment)
|
||||
member.Send(nil, client.nickMaskString, "KICK", channel.name, target.nick, comment)
|
||||
}
|
||||
channel.Quit(target)
|
||||
}
|
||||
|
||||
func (channel *Channel) Invite(invitee *Client, inviter *Client) {
|
||||
if channel.flags[InviteOnly] && !channel.ClientIsOperator(inviter) {
|
||||
inviter.Send(nil, inviter.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator")
|
||||
inviter.Send(nil, inviter.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator")
|
||||
return
|
||||
}
|
||||
|
||||
if !channel.members.Has(inviter) {
|
||||
inviter.Send(nil, inviter.server.nameString, ERR_NOTONCHANNEL, channel.nameString, "You're not on that channel")
|
||||
inviter.Send(nil, inviter.server.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel")
|
||||
return
|
||||
}
|
||||
|
||||
@ -513,10 +514,10 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client) {
|
||||
channel.Persist()
|
||||
}
|
||||
|
||||
//TODO(dan): should inviter.server.nameString here be inviter.nickMaskString ?
|
||||
inviter.Send(nil, inviter.server.nameString, RPL_INVITING, invitee.nickString, channel.nameString)
|
||||
invitee.Send(nil, inviter.nickMaskString, "INVITE", invitee.nickString, channel.nameString)
|
||||
//TODO(dan): should inviter.server.name here be inviter.nickMaskString ?
|
||||
inviter.Send(nil, inviter.server.name, RPL_INVITING, invitee.nick, channel.name)
|
||||
invitee.Send(nil, inviter.nickMaskString, "INVITE", invitee.nick, channel.name)
|
||||
if invitee.flags[Away] {
|
||||
inviter.Send(nil, inviter.server.nameString, RPL_AWAY, invitee.nickString, invitee.awayMessage)
|
||||
inviter.Send(nil, inviter.server.name, RPL_AWAY, invitee.nick, invitee.awayMessage)
|
||||
}
|
||||
}
|
||||
|
125
irc/client.go
125
irc/client.go
@ -27,34 +27,35 @@ var (
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
account *ClientAccount
|
||||
atime time.Time
|
||||
authorized bool
|
||||
awayMessage string
|
||||
capabilities CapabilitySet
|
||||
capState CapState
|
||||
certfp string
|
||||
channels ChannelSet
|
||||
ctime time.Time
|
||||
flags map[UserMode]bool
|
||||
isDestroyed bool
|
||||
isQuitting bool
|
||||
hasQuit bool
|
||||
hops uint
|
||||
hostname Name
|
||||
idleTimer *time.Timer
|
||||
nick Name
|
||||
nickString string // cache for nick string since it's used with most numerics
|
||||
nickMaskString string // cache for nickmask string since it's used with lots of replies
|
||||
quitTimer *time.Timer
|
||||
realname string
|
||||
registered bool
|
||||
saslInProgress bool
|
||||
saslMechanism string
|
||||
saslValue string
|
||||
server *Server
|
||||
socket *Socket
|
||||
username Name
|
||||
account *ClientAccount
|
||||
atime time.Time
|
||||
authorized bool
|
||||
awayMessage string
|
||||
capabilities CapabilitySet
|
||||
capState CapState
|
||||
certfp string
|
||||
channels ChannelSet
|
||||
ctime time.Time
|
||||
flags map[UserMode]bool
|
||||
isDestroyed bool
|
||||
isQuitting bool
|
||||
hasQuit bool
|
||||
hops uint
|
||||
hostname string
|
||||
idleTimer *time.Timer
|
||||
nick string
|
||||
nickCasefolded string
|
||||
nickMaskString string // cache for nickmask string since it's used with lots of replies
|
||||
nickMaskCasefolded string
|
||||
quitTimer *time.Timer
|
||||
realname string
|
||||
registered bool
|
||||
saslInProgress bool
|
||||
saslMechanism string
|
||||
saslValue string
|
||||
server *Server
|
||||
socket *Socket
|
||||
username string
|
||||
}
|
||||
|
||||
func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
||||
@ -71,7 +72,8 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
||||
server: server,
|
||||
socket: &socket,
|
||||
account: &NoAccount,
|
||||
nickString: "*", // * is used until actual nick is given
|
||||
nick: "*", // * is used until actual nick is given
|
||||
nickCasefolded: "*",
|
||||
nickMaskString: "*", // * is used until actual nick is given
|
||||
}
|
||||
if isTLS {
|
||||
@ -96,10 +98,10 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
||||
resp, err := ident.Query(clientHost, serverPort, clientPort, IdentTimeoutSeconds)
|
||||
if err == nil {
|
||||
username := resp.Identifier
|
||||
//TODO(dan): replace this with IsUsername/IsIRCName?
|
||||
if Name(username).IsNickname() {
|
||||
_, err := CasefoldName(username) // ensure it's a valid username
|
||||
if err == nil {
|
||||
client.Notice("*** Found your username")
|
||||
client.username = Name(username)
|
||||
client.username = username
|
||||
// we don't need to updateNickMask here since nickMask is not used for anything yet
|
||||
} else {
|
||||
client.Notice("*** Got a malformed username, ignoring")
|
||||
@ -144,7 +146,7 @@ func (client *Client) run() {
|
||||
|
||||
cmd, exists := Commands[msg.Command]
|
||||
if !exists {
|
||||
client.Send(nil, client.server.nameString, ERR_UNKNOWNCOMMAND, client.nickString, msg.Command, "Unknown command")
|
||||
client.Send(nil, client.server.name, ERR_UNKNOWNCOMMAND, client.nick, msg.Command, "Unknown command")
|
||||
continue
|
||||
}
|
||||
|
||||
@ -196,7 +198,7 @@ func (client *Client) Touch() {
|
||||
}
|
||||
|
||||
func (client *Client) Idle() {
|
||||
client.Send(nil, "", "PING", client.nickString)
|
||||
client.Send(nil, "", "PING", client.nick)
|
||||
|
||||
if client.quitTimer == nil {
|
||||
client.quitTimer = time.AfterFunc(QUIT_TIMEOUT, client.connectionTimeout)
|
||||
@ -226,11 +228,11 @@ func (client *Client) IdleSeconds() uint64 {
|
||||
}
|
||||
|
||||
func (client *Client) HasNick() bool {
|
||||
return client.nick != ""
|
||||
return client.nick != "" && client.nick != "*"
|
||||
}
|
||||
|
||||
func (client *Client) HasUsername() bool {
|
||||
return client.username != ""
|
||||
return client.username != "" && client.username != "*"
|
||||
}
|
||||
|
||||
// <mode>
|
||||
@ -244,29 +246,14 @@ func (c *Client) ModeString() (str string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) UserHost() Name {
|
||||
username := "*"
|
||||
if c.HasUsername() {
|
||||
username = c.username.String()
|
||||
}
|
||||
return Name(fmt.Sprintf("%s!%s@%s", c.Nick(), username, c.hostname))
|
||||
func (c *Client) UserHost() string {
|
||||
return fmt.Sprintf("%s!%s@%s", c.nick, c.username, c.hostname)
|
||||
}
|
||||
|
||||
func (c *Client) Nick() Name {
|
||||
if c.HasNick() {
|
||||
return c.nick
|
||||
}
|
||||
return Name("*")
|
||||
}
|
||||
|
||||
func (c *Client) Id() Name {
|
||||
func (c *Client) Id() string {
|
||||
return c.UserHost()
|
||||
}
|
||||
|
||||
func (c *Client) String() string {
|
||||
return c.Id().String()
|
||||
}
|
||||
|
||||
// Friends refers to clients that share a channel with this client.
|
||||
func (client *Client) Friends() ClientSet {
|
||||
friends := make(ClientSet)
|
||||
@ -280,30 +267,42 @@ func (client *Client) Friends() ClientSet {
|
||||
}
|
||||
|
||||
func (client *Client) updateNickMask() {
|
||||
client.nickString = client.nick.String()
|
||||
client.nickMaskString = fmt.Sprintf("%s!%s@%s", client.nickString, client.username, client.hostname)
|
||||
casefoldedName, err := CasefoldName(client.nick)
|
||||
if err != nil {
|
||||
log.Println(fmt.Sprintf("ERROR: Nick [%s] couldn't be casefolded... this should never happen.", client.nick))
|
||||
}
|
||||
client.nickCasefolded = casefoldedName
|
||||
|
||||
client.nickMaskString = fmt.Sprintf("%s!%s@%s", client.nick, client.username, client.hostname)
|
||||
|
||||
nickMaskCasefolded, err := Casefold(client.nickMaskString)
|
||||
if err != nil {
|
||||
log.Println(fmt.Sprintf("ERROR: Nickmask [%s] couldn't be casefolded... this should never happen.", client.nickMaskString))
|
||||
}
|
||||
client.nickMaskCasefolded = nickMaskCasefolded
|
||||
}
|
||||
|
||||
func (client *Client) SetNickname(nickname Name) {
|
||||
func (client *Client) SetNickname(nickname string) {
|
||||
if client.HasNick() {
|
||||
Log.error.Printf("%s nickname already set!", client)
|
||||
Log.error.Printf("%s nickname already set!", client.nickMaskString)
|
||||
return
|
||||
}
|
||||
fmt.Println("Setting nick to:", nickname, "from", client.nick)
|
||||
client.nick = nickname
|
||||
client.updateNickMask()
|
||||
client.server.clients.Add(client)
|
||||
}
|
||||
|
||||
func (client *Client) ChangeNickname(nickname Name) {
|
||||
func (client *Client) ChangeNickname(nickname string) {
|
||||
origNickMask := client.nickMaskString
|
||||
client.server.clients.Remove(client)
|
||||
client.server.whoWas.Append(client)
|
||||
client.nick = nickname
|
||||
client.updateNickMask()
|
||||
client.server.clients.Add(client)
|
||||
client.Send(nil, origNickMask, "NICK", nickname.String())
|
||||
client.Send(nil, origNickMask, "NICK", nickname)
|
||||
for friend := range client.Friends() {
|
||||
friend.Send(nil, origNickMask, "NICK", nickname.String())
|
||||
friend.Send(nil, origNickMask, "NICK", nickname)
|
||||
}
|
||||
}
|
||||
|
||||
@ -381,7 +380,7 @@ func (client *Client) Send(tags *map[string]ircmsg.TagValue, prefix string, comm
|
||||
line, err := message.Line()
|
||||
if err != nil {
|
||||
// try not to fail quietly - especially useful when running tests, as a note to dig deeper
|
||||
message = ircmsg.MakeMessage(nil, client.server.nameString, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
|
||||
message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
|
||||
line, _ := message.Line()
|
||||
client.socket.Write(line)
|
||||
return err
|
||||
@ -392,5 +391,5 @@ func (client *Client) Send(tags *map[string]ircmsg.TagValue, prefix string, comm
|
||||
|
||||
// Notice sends the client a notice from the server.
|
||||
func (client *Client) Notice(text string) {
|
||||
client.Send(nil, client.server.nameString, "NOTICE", client.nickString, text)
|
||||
client.Send(nil, client.server.name, "NOTICE", client.nick, text)
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
// Copyright (c) 2012-2014 Jeremy Latt
|
||||
// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
|
||||
// released under the MIT license
|
||||
|
||||
package irc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@ -17,31 +20,35 @@ var (
|
||||
ErrNicknameMismatch = errors.New("nickname mismatch")
|
||||
)
|
||||
|
||||
func ExpandUserHost(userhost Name) (expanded Name) {
|
||||
func ExpandUserHost(userhost string) (expanded string) {
|
||||
expanded = userhost
|
||||
// fill in missing wildcards for nicks
|
||||
//TODO(dan): this would fail with dan@lol, do we want to accommodate that?
|
||||
if !strings.Contains(expanded.String(), "!") {
|
||||
if !strings.Contains(expanded, "!") {
|
||||
expanded += "!*"
|
||||
}
|
||||
if !strings.Contains(expanded.String(), "@") {
|
||||
if !strings.Contains(expanded, "@") {
|
||||
expanded += "@*"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ClientLookupSet struct {
|
||||
byNick map[Name]*Client
|
||||
byNick map[string]*Client
|
||||
}
|
||||
|
||||
func NewClientLookupSet() *ClientLookupSet {
|
||||
return &ClientLookupSet{
|
||||
byNick: make(map[Name]*Client),
|
||||
byNick: make(map[string]*Client),
|
||||
}
|
||||
}
|
||||
|
||||
func (clients *ClientLookupSet) Get(nick Name) *Client {
|
||||
return clients.byNick[nick.ToLower()]
|
||||
func (clients *ClientLookupSet) Get(nick string) *Client {
|
||||
casefoldedName, err := CasefoldName(nick)
|
||||
if err == nil {
|
||||
return clients.byNick[casefoldedName]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (clients *ClientLookupSet) Add(client *Client) error {
|
||||
@ -51,7 +58,7 @@ func (clients *ClientLookupSet) Add(client *Client) error {
|
||||
if clients.Get(client.nick) != nil {
|
||||
return ErrNicknameInUse
|
||||
}
|
||||
clients.byNick[client.Nick().ToLower()] = client
|
||||
clients.byNick[client.nickCasefolded] = client
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -62,20 +69,21 @@ func (clients *ClientLookupSet) Remove(client *Client) error {
|
||||
if clients.Get(client.nick) != client {
|
||||
return ErrNicknameMismatch
|
||||
}
|
||||
delete(clients.byNick, client.nick.ToLower())
|
||||
delete(clients.byNick, client.nickCasefolded)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (clients *ClientLookupSet) FindAll(userhost Name) (set ClientSet) {
|
||||
func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) {
|
||||
set = make(ClientSet)
|
||||
|
||||
userhost = ExpandUserHost(userhost)
|
||||
matcher := ircmatch.MakeMatch(userhost.String())
|
||||
userhost, err := Casefold(ExpandUserHost(userhost))
|
||||
if err != nil {
|
||||
return set
|
||||
}
|
||||
matcher := ircmatch.MakeMatch(userhost)
|
||||
|
||||
var casemappedNickMask string
|
||||
for _, client := range clients.byNick {
|
||||
casemappedNickMask = NewName(client.nickMaskString).String()
|
||||
if matcher.Match(casemappedNickMask) {
|
||||
if matcher.Match(client.nickMaskCasefolded) {
|
||||
set.Add(client)
|
||||
}
|
||||
}
|
||||
@ -83,14 +91,15 @@ func (clients *ClientLookupSet) FindAll(userhost Name) (set ClientSet) {
|
||||
return set
|
||||
}
|
||||
|
||||
func (clients *ClientLookupSet) Find(userhost Name) *Client {
|
||||
userhost = ExpandUserHost(userhost)
|
||||
matcher := ircmatch.MakeMatch(userhost.String())
|
||||
func (clients *ClientLookupSet) Find(userhost string) *Client {
|
||||
userhost, err := Casefold(ExpandUserHost(userhost))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
matcher := ircmatch.MakeMatch(userhost)
|
||||
|
||||
var casemappedNickMask string
|
||||
for _, client := range clients.byNick {
|
||||
casemappedNickMask = NewName(client.nickMaskString).String()
|
||||
if matcher.Match(casemappedNickMask) {
|
||||
if matcher.Match(client.nickMaskCasefolded) {
|
||||
return client
|
||||
}
|
||||
}
|
||||
@ -105,26 +114,31 @@ func (clients *ClientLookupSet) Find(userhost Name) *Client {
|
||||
//TODO(dan): move this over to generally using glob syntax instead?
|
||||
// kinda more expected in normal ban/etc masks, though regex is useful (probably as an extban?)
|
||||
type UserMaskSet struct {
|
||||
masks map[Name]bool
|
||||
masks map[string]bool
|
||||
regexp *regexp.Regexp
|
||||
}
|
||||
|
||||
func NewUserMaskSet() *UserMaskSet {
|
||||
return &UserMaskSet{
|
||||
masks: make(map[Name]bool),
|
||||
masks: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (set *UserMaskSet) Add(mask Name) bool {
|
||||
if set.masks[mask] {
|
||||
func (set *UserMaskSet) Add(mask string) bool {
|
||||
casefoldedMask, err := Casefold(mask)
|
||||
if err != nil {
|
||||
log.Println(fmt.Sprintf("ERROR: Could not add mask to usermaskset: [%s]", mask))
|
||||
return false
|
||||
}
|
||||
set.masks[mask] = true
|
||||
if set.masks[casefoldedMask] {
|
||||
return false
|
||||
}
|
||||
set.masks[casefoldedMask] = true
|
||||
set.setRegexp()
|
||||
return true
|
||||
}
|
||||
|
||||
func (set *UserMaskSet) AddAll(masks []Name) (added bool) {
|
||||
func (set *UserMaskSet) AddAll(masks []string) (added bool) {
|
||||
for _, mask := range masks {
|
||||
if !added && !set.masks[mask] {
|
||||
added = true
|
||||
@ -135,7 +149,7 @@ func (set *UserMaskSet) AddAll(masks []Name) (added bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (set *UserMaskSet) Remove(mask Name) bool {
|
||||
func (set *UserMaskSet) Remove(mask string) bool {
|
||||
if !set.masks[mask] {
|
||||
return false
|
||||
}
|
||||
@ -144,18 +158,18 @@ func (set *UserMaskSet) Remove(mask Name) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (set *UserMaskSet) Match(userhost Name) bool {
|
||||
func (set *UserMaskSet) Match(userhost string) bool {
|
||||
if set.regexp == nil {
|
||||
return false
|
||||
}
|
||||
return set.regexp.MatchString(userhost.String())
|
||||
return set.regexp.MatchString(userhost)
|
||||
}
|
||||
|
||||
func (set *UserMaskSet) String() string {
|
||||
masks := make([]string, len(set.masks))
|
||||
index := 0
|
||||
for mask := range set.masks {
|
||||
masks[index] = mask.String()
|
||||
masks[index] = mask
|
||||
index += 1
|
||||
}
|
||||
return strings.Join(masks, " ")
|
||||
@ -176,7 +190,7 @@ func (set *UserMaskSet) setRegexp() {
|
||||
maskExprs := make([]string, len(set.masks))
|
||||
index := 0
|
||||
for mask := range set.masks {
|
||||
manyParts := strings.Split(mask.String(), "*")
|
||||
manyParts := strings.Split(mask, "*")
|
||||
manyExprs := make([]string, len(manyParts))
|
||||
for mindex, manyPart := range manyParts {
|
||||
oneParts := strings.Split(manyPart, "?")
|
||||
|
@ -24,11 +24,11 @@ func (cmd *Command) Run(server *Server, client *Client, msg ircmsg.IrcMessage) b
|
||||
return false
|
||||
}
|
||||
if cmd.oper && !client.flags[Operator] {
|
||||
client.Send(nil, server.nameString, ERR_NOPRIVILEGES, client.nickString, "Permission Denied - You're not an IRC operator")
|
||||
client.Send(nil, server.name, ERR_NOPRIVILEGES, client.nick, "Permission Denied - You're not an IRC operator")
|
||||
return false
|
||||
}
|
||||
if len(msg.Params) < cmd.minParams {
|
||||
client.Send(nil, server.nameString, ERR_NEEDMOREPARAMS, client.nickString, msg.Command, "Not enough parameters")
|
||||
client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, "Not enough parameters")
|
||||
return false
|
||||
}
|
||||
if !cmd.leaveClientActive {
|
||||
|
@ -103,22 +103,32 @@ type Config struct {
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *Config) Operators() map[Name][]byte {
|
||||
operators := make(map[Name][]byte)
|
||||
func (conf *Config) Operators() map[string][]byte {
|
||||
operators := make(map[string][]byte)
|
||||
for name, opConf := range conf.Operator {
|
||||
operators[NewName(name)] = opConf.PasswordBytes()
|
||||
name, err := CasefoldName(name)
|
||||
if err == nil {
|
||||
operators[name] = opConf.PasswordBytes()
|
||||
} else {
|
||||
log.Println("Could not casefold oper name:", err.Error())
|
||||
}
|
||||
}
|
||||
return operators
|
||||
}
|
||||
|
||||
func (conf *Config) TLSListeners() map[Name]*tls.Config {
|
||||
tlsListeners := make(map[Name]*tls.Config)
|
||||
func (conf *Config) TLSListeners() map[string]*tls.Config {
|
||||
tlsListeners := make(map[string]*tls.Config)
|
||||
for s, tlsListenersConf := range conf.Server.TLSListeners {
|
||||
config, err := tlsListenersConf.Config()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
tlsListeners[NewName(s)] = config
|
||||
name, err := CasefoldName(s)
|
||||
if err == nil {
|
||||
tlsListeners[name] = config
|
||||
} else {
|
||||
log.Println("Could not casefold TLS listener:", err.Error())
|
||||
}
|
||||
}
|
||||
return tlsListeners
|
||||
}
|
||||
|
@ -70,6 +70,6 @@ func (il *ISupportList) RegenerateCachedReply() {
|
||||
func (client *Client) RplISupport() {
|
||||
for _, tokenline := range client.server.isupport.CachedReply {
|
||||
// ugly trickery ahead
|
||||
client.Send(nil, client.server.nameString, RPL_ISUPPORT, append([]string{client.nickString}, tokenline...)...)
|
||||
client.Send(nil, client.server.name, RPL_ISUPPORT, append([]string{client.nick}, tokenline...)...)
|
||||
}
|
||||
}
|
||||
|
58
irc/modes.go
58
irc/modes.go
@ -120,7 +120,7 @@ func (changes ChannelModeChanges) String() string {
|
||||
}
|
||||
|
||||
type ChannelModeCommand struct {
|
||||
channel Name
|
||||
channel string
|
||||
changes ChannelModeChanges
|
||||
}
|
||||
|
||||
@ -209,8 +209,9 @@ var (
|
||||
|
||||
// MODE <target> [<modestring> [<mode arguments>...]]
|
||||
func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
name := NewName(msg.Params[0])
|
||||
if name.IsChannel() {
|
||||
_, errChan := CasefoldChannel(msg.Params[0])
|
||||
|
||||
if errChan != nil {
|
||||
return cmodeHandler(server, client, msg)
|
||||
} else {
|
||||
return umodeHandler(server, client, msg)
|
||||
@ -219,12 +220,12 @@ func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
|
||||
// MODE <target> [<modestring> [<mode arguments>...]]
|
||||
func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
nickname := NewName(msg.Params[0])
|
||||
nickname, err := CasefoldName(msg.Params[0])
|
||||
|
||||
target := server.clients.Get(nickname)
|
||||
|
||||
if target == nil {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHNICK, client.nickString, msg.Params[0], "No such nick")
|
||||
if err != nil || target == nil {
|
||||
client.Send(nil, server.name, ERR_NOSUCHNICK, client.nick, msg.Params[0], "No such nick")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -232,9 +233,9 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
// point SAMODE at this handler too, if they are operator and SAMODE was called then fine
|
||||
if client != target && !client.flags[Operator] {
|
||||
if len(msg.Params) > 1 {
|
||||
client.Send(nil, server.nameString, ERR_USERSDONTMATCH, client.nickString, "Can't change modes for other users")
|
||||
client.Send(nil, server.name, ERR_USERSDONTMATCH, client.nick, "Can't change modes for other users")
|
||||
} else {
|
||||
client.Send(nil, server.nameString, ERR_USERSDONTMATCH, client.nickString, "Can't view modes for other users")
|
||||
client.Send(nil, server.name, ERR_USERSDONTMATCH, client.nick, "Can't view modes for other users")
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -249,7 +250,7 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
if (op == Add) || (op == Remove) {
|
||||
modeArg = modeArg[1:]
|
||||
} else {
|
||||
client.Send(nil, server.nameString, ERR_UNKNOWNMODE, client.nickString, string(modeArg[0]), "is an unknown mode character to me")
|
||||
client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(modeArg[0]), "is an unknown mode character to me")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -298,20 +299,20 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
}
|
||||
|
||||
if len(applied) > 0 {
|
||||
client.Send(nil, client.nickMaskString, "MODE", target.nickString, applied.String())
|
||||
client.Send(nil, client.nickMaskString, "MODE", target.nick, applied.String())
|
||||
} else if client == target {
|
||||
client.Send(nil, target.nickMaskString, RPL_UMODEIS, target.nickString, target.ModeString())
|
||||
client.Send(nil, target.nickMaskString, RPL_UMODEIS, target.nick, target.ModeString())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MODE <target> [<modestring> [<mode arguments>...]]
|
||||
func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
channelName := NewName(msg.Params[0])
|
||||
channelName, err := CasefoldChannel(msg.Params[0])
|
||||
channel := server.channels.Get(channelName)
|
||||
|
||||
if channel == nil {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, msg.Params[0], "No such channel")
|
||||
if err != nil || channel == nil {
|
||||
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, msg.Params[0], "No such channel")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -327,7 +328,7 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
if (op == Add) || (op == Remove) {
|
||||
modeArg = modeArg[1:]
|
||||
} else {
|
||||
client.Send(nil, server.nameString, ERR_UNKNOWNMODE, client.nickString, string(modeArg[0]), "is an unknown mode character to me")
|
||||
client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(modeArg[0]), "is an unknown mode character to me")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -380,7 +381,7 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
list := channel.lists[change.mode]
|
||||
if list == nil {
|
||||
// This should never happen, but better safe than panicky.
|
||||
client.Send(nil, server.nameString, ERR_UNKNOWNERROR, client.nickString, "MODE", "Could not complete MODE command")
|
||||
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "MODE", "Could not complete MODE command")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -389,13 +390,19 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
continue
|
||||
}
|
||||
|
||||
// confirm mask looks valid
|
||||
mask, err = Casefold(mask)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch change.op {
|
||||
case Add:
|
||||
list.Add(Name(mask))
|
||||
list.Add(mask)
|
||||
applied = append(applied, change)
|
||||
|
||||
case Remove:
|
||||
list.Remove(Name(mask))
|
||||
list.Remove(mask)
|
||||
applied = append(applied, change)
|
||||
}
|
||||
|
||||
@ -460,13 +467,16 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
}
|
||||
}
|
||||
|
||||
name := NewName(change.arg)
|
||||
casefoldedName, err := CasefoldName(change.arg)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if !hasPrivs {
|
||||
if change.op == Remove && name.ToLower() == client.nick.ToLower() {
|
||||
if change.op == Remove && casefoldedName == client.nickCasefolded {
|
||||
// success!
|
||||
} else {
|
||||
client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator")
|
||||
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator")
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -481,13 +491,13 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
|
||||
if len(applied) > 0 {
|
||||
//TODO(dan): we should change the name of String and make it return a slice here
|
||||
args := append([]string{channel.nameString}, strings.Split(applied.String(), " ")...)
|
||||
args := append([]string{channel.name}, strings.Split(applied.String(), " ")...)
|
||||
client.Send(nil, client.nickMaskString, "MODE", args...)
|
||||
} else {
|
||||
//TODO(dan): we should just make ModeString return a slice here
|
||||
args := append([]string{client.nickString, channel.nameString}, strings.Split(channel.ModeString(client), " ")...)
|
||||
args := append([]string{client.nick, channel.name}, strings.Split(channel.ModeString(client), " ")...)
|
||||
client.Send(nil, client.nickMaskString, RPL_CHANNELMODEIS, args...)
|
||||
client.Send(nil, client.nickMaskString, RPL_CHANNELCREATED, client.nickString, channel.nameString, strconv.FormatInt(channel.createdTime.Unix(), 10))
|
||||
client.Send(nil, client.nickMaskString, RPL_CHANNELCREATED, client.nick, channel.name, strconv.FormatInt(channel.createdTime.Unix(), 10))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
20
irc/net.go
20
irc/net.go
@ -9,27 +9,27 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func IPString(addr net.Addr) Name {
|
||||
func IPString(addr net.Addr) string {
|
||||
addrStr := addr.String()
|
||||
ipaddr, _, err := net.SplitHostPort(addrStr)
|
||||
if err != nil {
|
||||
return Name(addrStr)
|
||||
return addrStr
|
||||
}
|
||||
return Name(ipaddr)
|
||||
return ipaddr
|
||||
}
|
||||
|
||||
func AddrLookupHostname(addr net.Addr) Name {
|
||||
func AddrLookupHostname(addr net.Addr) string {
|
||||
return LookupHostname(IPString(addr))
|
||||
}
|
||||
|
||||
func LookupHostname(addr Name) Name {
|
||||
names, err := net.LookupAddr(addr.String())
|
||||
if err != nil {
|
||||
return Name(addr)
|
||||
func LookupHostname(addr string) string {
|
||||
names, err := net.LookupAddr(addr)
|
||||
if err != nil || !IsHostname(names[0]) {
|
||||
// return original address
|
||||
return addr
|
||||
}
|
||||
|
||||
hostname := strings.TrimSuffix(names[0], ".")
|
||||
return Name(hostname)
|
||||
return names[0]
|
||||
}
|
||||
|
||||
var allowedHostnameChars = "abcdefghijklmnopqrstuvwxyz1234567890-."
|
||||
|
@ -4,7 +4,11 @@
|
||||
|
||||
package irc
|
||||
|
||||
import "github.com/DanielOaks/girc-go/ircmsg"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/DanielOaks/girc-go/ircmsg"
|
||||
)
|
||||
|
||||
// NICK <nickname>
|
||||
func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
@ -13,15 +17,15 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
nickname := NewName(msg.Params[0])
|
||||
nickname, err := CasefoldName(msg.Params[0])
|
||||
|
||||
if len(nickname) < 1 {
|
||||
client.Send(nil, server.nameString, ERR_NONICKNAMEGIVEN, client.nickString, "No nickname given")
|
||||
if len(strings.TrimSpace(msg.Params[0])) < 1 {
|
||||
client.Send(nil, server.name, ERR_NONICKNAMEGIVEN, client.nick, "No nickname given")
|
||||
return false
|
||||
}
|
||||
|
||||
if !nickname.IsNickname() {
|
||||
client.Send(nil, server.nameString, ERR_ERRONEUSNICKNAME, client.nickString, msg.Params[0], "Erroneous nickname")
|
||||
if err != nil || len(strings.TrimSpace(msg.Params[0])) > server.limits.NickLen {
|
||||
client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, msg.Params[0], "Erroneous nickname")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -32,7 +36,7 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
//TODO(dan): There's probably some races here, we should be changing this in the primary server thread
|
||||
target := server.clients.Get(nickname)
|
||||
if target != nil && target != client {
|
||||
client.Send(nil, server.nameString, ERR_NICKNAMEINUSE, client.nickString, msg.Params[0], "Nickname is already in use")
|
||||
client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, msg.Params[0], "Nickname is already in use")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -52,35 +56,35 @@ func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
oldnick := NewName(msg.Params[0])
|
||||
nickname := NewName(msg.Params[1])
|
||||
oldnick, oerr := CasefoldName(msg.Params[0])
|
||||
casefoldedNickname, err := CasefoldName(msg.Params[1])
|
||||
|
||||
if len(nickname) < 1 {
|
||||
client.Send(nil, server.nameString, ERR_NONICKNAMEGIVEN, client.nickString, "No nickname given")
|
||||
if len(casefoldedNickname) < 1 {
|
||||
client.Send(nil, server.name, ERR_NONICKNAMEGIVEN, client.nick, "No nickname given")
|
||||
return false
|
||||
}
|
||||
|
||||
if !nickname.IsNickname() {
|
||||
client.Send(nil, server.nameString, ERR_ERRONEUSNICKNAME, client.nickString, msg.Params[0], "Erroneous nickname")
|
||||
if oerr != nil || err != nil || len(strings.TrimSpace(msg.Params[1])) > server.limits.NickLen {
|
||||
client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, msg.Params[0], "Erroneous nickname")
|
||||
return false
|
||||
}
|
||||
|
||||
if client.nick == nickname {
|
||||
if client.nick == msg.Params[1] {
|
||||
return false
|
||||
}
|
||||
|
||||
target := server.clients.Get(oldnick)
|
||||
if target == nil {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHNICK, msg.Params[0], "No such nick")
|
||||
client.Send(nil, server.name, ERR_NOSUCHNICK, msg.Params[0], "No such nick")
|
||||
return false
|
||||
}
|
||||
|
||||
//TODO(dan): There's probably some races here, we should be changing this in the primary server thread
|
||||
if server.clients.Get(nickname) != nil {
|
||||
client.Send(nil, server.nameString, ERR_NICKNAMEINUSE, client.nickString, msg.Params[0], "Nickname is already in use")
|
||||
if server.clients.Get(casefoldedNickname) != nil || server.clients.Get(casefoldedNickname) != target {
|
||||
client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, msg.Params[0], "Nickname is already in use")
|
||||
return false
|
||||
}
|
||||
|
||||
target.ChangeNickname(nickname)
|
||||
target.ChangeNickname(msg.Params[1])
|
||||
return false
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ func regHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
} else if subcommand == "verify" {
|
||||
client.Notice("Parsing VERIFY")
|
||||
} else {
|
||||
client.Send(nil, server.nameString, ERR_UNKNOWNERROR, client.nickString, "REG", msg.Params[0], "Unknown subcommand")
|
||||
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "REG", msg.Params[0], "Unknown subcommand")
|
||||
}
|
||||
|
||||
return false
|
||||
@ -94,30 +94,30 @@ func removeFailedRegCreateData(store buntdb.DB, account string) {
|
||||
// regCreateHandler parses the REG CREATE command.
|
||||
func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
// get and sanitise account name
|
||||
account := NewName(msg.Params[1])
|
||||
//TODO(dan): probably don't need explicit check for "*" here... until we actually casemap properly as per rfc7700
|
||||
if !account.IsNickname() || msg.Params[1] == "*" {
|
||||
client.Send(nil, server.nameString, ERR_REG_UNSPECIFIED_ERROR, client.nickString, msg.Params[1], "Account name is not valid")
|
||||
account := strings.TrimSpace(msg.Params[1])
|
||||
casefoldedAccount, err := CasefoldName(account)
|
||||
// probably don't need explicit check for "*" here... but let's do it anyway just to make sure
|
||||
if err != nil || msg.Params[1] == "*" {
|
||||
client.Send(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, client.nick, account, "Account name is not valid")
|
||||
return false
|
||||
}
|
||||
accountString := account.String()
|
||||
|
||||
// check whether account exists
|
||||
// do it all in one write tx to prevent races
|
||||
err := server.store.Update(func(tx *buntdb.Tx) error {
|
||||
accountKey := fmt.Sprintf(keyAccountExists, accountString)
|
||||
err = server.store.Update(func(tx *buntdb.Tx) error {
|
||||
accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
|
||||
|
||||
_, err := tx.Get(accountKey)
|
||||
if err != buntdb.ErrNotFound {
|
||||
//TODO(dan): if account verified key doesn't exist account is not verified, calc the maximum time without verification and expire and continue if need be
|
||||
client.Send(nil, server.nameString, ERR_ACCOUNT_ALREADY_EXISTS, client.nickString, msg.Params[1], "Account already exists")
|
||||
client.Send(nil, server.name, ERR_ACCOUNT_ALREADY_EXISTS, client.nick, account, "Account already exists")
|
||||
return errAccountCreation
|
||||
}
|
||||
|
||||
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, accountString)
|
||||
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
|
||||
|
||||
tx.Set(accountKey, "1", nil)
|
||||
tx.Set(fmt.Sprintf(keyAccountName, accountString), strings.TrimSpace(msg.Params[1]), nil)
|
||||
tx.Set(fmt.Sprintf(keyAccountName, casefoldedAccount), account, nil)
|
||||
tx.Set(registeredTimeKey, strconv.FormatInt(time.Now().Unix(), 10), nil)
|
||||
return nil
|
||||
})
|
||||
@ -125,7 +125,7 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
|
||||
// account could not be created and relevant numerics have been dispatched, abort
|
||||
if err != nil {
|
||||
if err != errAccountCreation {
|
||||
client.Send(nil, server.nameString, ERR_UNKNOWNERROR, client.nickString, "REG", "CREATE", "Could not register")
|
||||
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "REG", "CREATE", "Could not register")
|
||||
log.Println("Could not save registration initial data:", err.Error())
|
||||
}
|
||||
return false
|
||||
@ -155,8 +155,8 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
|
||||
}
|
||||
|
||||
if !callbackValid {
|
||||
client.Send(nil, server.nameString, ERR_REG_INVALID_CALLBACK, client.nickString, msg.Params[1], callbackNamespace, "Callback namespace is not supported")
|
||||
removeFailedRegCreateData(server.store, accountString)
|
||||
client.Send(nil, server.name, ERR_REG_INVALID_CALLBACK, client.nick, account, callbackNamespace, "Callback namespace is not supported")
|
||||
removeFailedRegCreateData(server.store, casefoldedAccount)
|
||||
return false
|
||||
}
|
||||
|
||||
@ -170,8 +170,8 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
|
||||
credentialType = "passphrase" // default from the spec
|
||||
credentialValue = msg.Params[3]
|
||||
} else {
|
||||
client.Send(nil, server.nameString, ERR_NEEDMOREPARAMS, client.nickString, msg.Command, "Not enough parameters")
|
||||
removeFailedRegCreateData(server.store, accountString)
|
||||
client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, "Not enough parameters")
|
||||
removeFailedRegCreateData(server.store, casefoldedAccount)
|
||||
return false
|
||||
}
|
||||
|
||||
@ -183,14 +183,14 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
|
||||
}
|
||||
}
|
||||
if credentialType == "certfp" && client.certfp == "" {
|
||||
client.Send(nil, server.nameString, ERR_REG_INVALID_CRED_TYPE, client.nickString, credentialType, callbackNamespace, "You are not using a certificiate")
|
||||
removeFailedRegCreateData(server.store, accountString)
|
||||
client.Send(nil, server.name, ERR_REG_INVALID_CRED_TYPE, client.nick, credentialType, callbackNamespace, "You are not using a certificiate")
|
||||
removeFailedRegCreateData(server.store, casefoldedAccount)
|
||||
return false
|
||||
}
|
||||
|
||||
if !credentialValid {
|
||||
client.Send(nil, server.nameString, ERR_REG_INVALID_CRED_TYPE, client.nickString, credentialType, callbackNamespace, "Credential type is not supported")
|
||||
removeFailedRegCreateData(server.store, accountString)
|
||||
client.Send(nil, server.name, ERR_REG_INVALID_CRED_TYPE, client.nick, credentialType, callbackNamespace, "Credential type is not supported")
|
||||
removeFailedRegCreateData(server.store, casefoldedAccount)
|
||||
return false
|
||||
}
|
||||
|
||||
@ -206,7 +206,7 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
|
||||
return errCertfpAlreadyExists
|
||||
}
|
||||
|
||||
tx.Set(assembledKeyCertToAccount, account.String(), nil)
|
||||
tx.Set(assembledKeyCertToAccount, casefoldedAccount, nil)
|
||||
}
|
||||
|
||||
// make creds
|
||||
@ -241,9 +241,9 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
|
||||
if err == errCertfpAlreadyExists {
|
||||
errMsg = "An account already exists for your certificate fingerprint"
|
||||
}
|
||||
client.Send(nil, server.nameString, ERR_UNKNOWNERROR, client.nickString, "REG", "CREATE", errMsg)
|
||||
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "REG", "CREATE", errMsg)
|
||||
log.Println("Could not save registration creds:", err.Error())
|
||||
removeFailedRegCreateData(server.store, accountString)
|
||||
removeFailedRegCreateData(server.store, casefoldedAccount)
|
||||
return false
|
||||
}
|
||||
|
||||
@ -259,18 +259,18 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
|
||||
Clients: []*Client{client},
|
||||
}
|
||||
//TODO(dan): Consider creating ircd-wide account adding/removing/affecting lock for protecting access to these sorts of variables
|
||||
server.accounts[accountString] = &account
|
||||
server.accounts[casefoldedAccount] = &account
|
||||
client.account = &account
|
||||
|
||||
client.Send(nil, server.nameString, RPL_REGISTRATION_SUCCESS, client.nickString, accountString, "Account created")
|
||||
client.Send(nil, server.nameString, RPL_LOGGEDIN, client.nickString, client.nickMaskString, accountString, fmt.Sprintf("You are now logged in as %s", accountString))
|
||||
client.Send(nil, server.nameString, RPL_SASLSUCCESS, client.nickString, "Authentication successful")
|
||||
client.Send(nil, server.name, RPL_REGISTRATION_SUCCESS, client.nick, account.Name, "Account created")
|
||||
client.Send(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, account.Name, fmt.Sprintf("You are now logged in as %s", account.Name))
|
||||
client.Send(nil, server.name, RPL_SASLSUCCESS, client.nick, "Authentication successful")
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
client.Send(nil, server.nameString, ERR_UNKNOWNERROR, client.nickString, "REG", "CREATE", "Could not register")
|
||||
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "REG", "CREATE", "Could not register")
|
||||
log.Println("Could not save verification confirmation (*):", err.Error())
|
||||
removeFailedRegCreateData(server.store, accountString)
|
||||
removeFailedRegCreateData(server.store, casefoldedAccount)
|
||||
return false
|
||||
}
|
||||
|
||||
|
328
irc/server.go
328
irc/server.go
@ -15,7 +15,6 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@ -27,9 +26,11 @@ import (
|
||||
|
||||
// Limits holds the maximum limits for various things such as topic lengths
|
||||
type Limits struct {
|
||||
AwayLen int
|
||||
KickLen int
|
||||
TopicLen int
|
||||
AwayLen int
|
||||
ChannelLen int
|
||||
KickLen int
|
||||
NickLen int
|
||||
TopicLen int
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
@ -42,10 +43,10 @@ type Server struct {
|
||||
idle chan *Client
|
||||
limits Limits
|
||||
motdLines []string
|
||||
name Name
|
||||
nameString string // cache for server name string since it's used with almost every reply
|
||||
name string
|
||||
nameCasefolded string
|
||||
newConns chan clientConn
|
||||
operators map[Name][]byte
|
||||
operators map[string][]byte
|
||||
password []byte
|
||||
passwords *PasswordManager
|
||||
accountRegistration *AccountRegistration
|
||||
@ -71,6 +72,12 @@ type clientConn struct {
|
||||
}
|
||||
|
||||
func NewServer(config *Config) *Server {
|
||||
casefoldedName, err := Casefold(config.Server.Name)
|
||||
if err != nil {
|
||||
log.Println(fmt.Sprintf("Server name isn't valid: []", config.Server.Name), err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
accounts: make(map[string]*ClientAccount),
|
||||
channels: make(ChannelNameMap),
|
||||
@ -79,12 +86,14 @@ func NewServer(config *Config) *Server {
|
||||
ctime: time.Now(),
|
||||
idle: make(chan *Client),
|
||||
limits: Limits{
|
||||
AwayLen: config.Limits.AwayLen,
|
||||
KickLen: config.Limits.KickLen,
|
||||
TopicLen: config.Limits.TopicLen,
|
||||
AwayLen: config.Limits.AwayLen,
|
||||
ChannelLen: config.Limits.ChannelLen,
|
||||
KickLen: config.Limits.KickLen,
|
||||
NickLen: config.Limits.NickLen,
|
||||
TopicLen: config.Limits.TopicLen,
|
||||
},
|
||||
name: NewName(config.Server.Name),
|
||||
nameString: NewName(config.Server.Name).String(),
|
||||
name: config.Server.Name,
|
||||
nameCasefolded: casefoldedName,
|
||||
newConns: make(chan clientConn),
|
||||
operators: config.Operators(),
|
||||
signals: make(chan os.Signal, len(SERVER_SIGNALS)),
|
||||
@ -141,10 +150,6 @@ func NewServer(config *Config) *Server {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO(dan): Hot damn this is an ugly hack. Fix it properly at some point.
|
||||
ChannelNameExpr = regexp.MustCompile(fmt.Sprintf(`^[#][\pL\pN\pP\pS]{1,%d}$`, config.Limits.ChannelLen))
|
||||
NicknameExpr = regexp.MustCompile(fmt.Sprintf("^[\\pL\\pN\\pP\\pS]{1,%d}$", config.Limits.NickLen))
|
||||
|
||||
if config.Server.Password != "" {
|
||||
server.password = config.Server.PasswordBytes()
|
||||
}
|
||||
@ -169,7 +174,7 @@ func NewServer(config *Config) *Server {
|
||||
// add RPL_ISUPPORT tokens
|
||||
server.isupport = NewISupportList()
|
||||
server.isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen))
|
||||
server.isupport.Add("CASEMAPPING", "ascii")
|
||||
server.isupport.Add("CASEMAPPING", "rfc7700")
|
||||
server.isupport.Add("CHANMODES", strings.Join([]string{ChannelModes{BanMask, ExceptMask, InviteMask}.String(), "", ChannelModes{UserLimit, Key}.String(), ChannelModes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, Persistent, ReOp, Secret}.String()}, ","))
|
||||
server.isupport.Add("CHANNELLEN", strconv.Itoa(config.Limits.ChannelLen))
|
||||
server.isupport.Add("CHANTYPES", "#")
|
||||
@ -209,7 +214,7 @@ func loadChannelList(channel *Channel, list string, maskMode ChannelMode) {
|
||||
if list == "" {
|
||||
return
|
||||
}
|
||||
channel.lists[maskMode].AddAll(NewNames(strings.Split(list, " ")))
|
||||
channel.lists[maskMode].AddAll(strings.Split(list, " "))
|
||||
}
|
||||
|
||||
func (server *Server) loadChannels() {
|
||||
@ -287,8 +292,9 @@ func (server *Server) Run() {
|
||||
// listen goroutine
|
||||
//
|
||||
|
||||
func (s *Server) listen(addr string, tlsMap map[Name]*tls.Config) {
|
||||
config, listenTLS := tlsMap[NewName(addr)]
|
||||
func (s *Server) listen(addr string, tlsMap map[string]*tls.Config) {
|
||||
//TODO(dan): we could casemap this but... eh
|
||||
config, listenTLS := tlsMap[addr]
|
||||
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
@ -301,16 +307,16 @@ func (s *Server) listen(addr string, tlsMap map[Name]*tls.Config) {
|
||||
listener = tls.NewListener(listener, config)
|
||||
tlsString = "TLS"
|
||||
}
|
||||
Log.info.Printf("%s listening on %s using %s.", s, addr, tlsString)
|
||||
Log.info.Printf("%s listening on %s using %s.", s.name, addr, tlsString)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
Log.error.Printf("%s accept error: %s", s, err)
|
||||
Log.error.Printf("%s accept error: %s", s.name, err)
|
||||
continue
|
||||
}
|
||||
Log.debug.Printf("%s accept: %s", s, conn.RemoteAddr())
|
||||
Log.debug.Printf("%s accept: %s", s.name, conn.RemoteAddr())
|
||||
|
||||
newConn := clientConn{
|
||||
Conn: conn,
|
||||
@ -329,7 +335,7 @@ func (s *Server) listen(addr string, tlsMap map[Name]*tls.Config) {
|
||||
func (s *Server) wslisten(addr string, tlsMap map[string]*TLSListenConfig) {
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
Log.error.Printf("%s method not allowed", s)
|
||||
Log.error.Printf("%s method not allowed", s.name)
|
||||
return
|
||||
}
|
||||
|
||||
@ -342,7 +348,7 @@ func (s *Server) wslisten(addr string, tlsMap map[string]*TLSListenConfig) {
|
||||
|
||||
ws, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
Log.error.Printf("%s websocket upgrade error: %s", s, err)
|
||||
Log.error.Printf("%s websocket upgrade error: %s", s.name, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -360,7 +366,7 @@ func (s *Server) wslisten(addr string, tlsMap map[string]*TLSListenConfig) {
|
||||
if listenTLS {
|
||||
tlsString = "TLS"
|
||||
}
|
||||
Log.info.Printf("%s websocket listening on %s using %s.", s, addr, tlsString)
|
||||
Log.info.Printf("%s websocket listening on %s using %s.", s.name, addr, tlsString)
|
||||
|
||||
if listenTLS {
|
||||
err = http.ListenAndServeTLS(addr, config.Cert, config.Key, nil)
|
||||
@ -368,7 +374,7 @@ func (s *Server) wslisten(addr string, tlsMap map[string]*TLSListenConfig) {
|
||||
err = http.ListenAndServe(addr, nil)
|
||||
}
|
||||
if err != nil {
|
||||
Log.error.Printf("%s listenAndServe (%s) error: %s", s, tlsString, err)
|
||||
Log.error.Printf("%s listenAndServe (%s) error: %s", s.name, tlsString, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@ -387,38 +393,34 @@ func (s *Server) tryRegister(c *Client) {
|
||||
// send welcome text
|
||||
//NOTE(dan): we specifically use the NICK here instead of the nickmask
|
||||
// see http://modern.ircdocs.horse/#rplwelcome-001 for details on why we avoid using the nickmask
|
||||
c.Send(nil, s.nameString, RPL_WELCOME, c.nickString, fmt.Sprintf("Welcome to the Internet Relay Network %s", c.nickString))
|
||||
c.Send(nil, s.nameString, RPL_YOURHOST, c.nickString, fmt.Sprintf("Your host is %s, running version %s", s.nameString, VER))
|
||||
c.Send(nil, s.nameString, RPL_CREATED, c.nickString, fmt.Sprintf("This server was created %s", s.ctime.Format(time.RFC1123)))
|
||||
c.Send(nil, s.name, RPL_WELCOME, c.nick, fmt.Sprintf("Welcome to the Internet Relay Network %s", c.nick))
|
||||
c.Send(nil, s.name, RPL_YOURHOST, c.nick, fmt.Sprintf("Your host is %s, running version %s", s.name, VER))
|
||||
c.Send(nil, s.name, RPL_CREATED, c.nick, fmt.Sprintf("This server was created %s", s.ctime.Format(time.RFC1123)))
|
||||
//TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter
|
||||
c.Send(nil, s.nameString, RPL_MYINFO, c.nickString, s.nameString, VER, supportedUserModesString, supportedChannelModesString)
|
||||
c.Send(nil, s.name, RPL_MYINFO, c.nick, s.name, VER, supportedUserModesString, supportedChannelModesString)
|
||||
c.RplISupport()
|
||||
s.MOTD(c)
|
||||
c.Send(nil, c.nickMaskString, RPL_UMODEIS, c.nickString, c.ModeString())
|
||||
c.Send(nil, c.nickMaskString, RPL_UMODEIS, c.nick, c.ModeString())
|
||||
}
|
||||
|
||||
func (server *Server) MOTD(client *Client) {
|
||||
if len(server.motdLines) < 1 {
|
||||
client.Send(nil, server.nameString, ERR_NOMOTD, client.nickString, "MOTD File is missing")
|
||||
client.Send(nil, server.name, ERR_NOMOTD, client.nick, "MOTD File is missing")
|
||||
return
|
||||
}
|
||||
|
||||
client.Send(nil, server.nameString, RPL_MOTDSTART, client.nickString, fmt.Sprintf("- %s Message of the day - ", server.nameString))
|
||||
client.Send(nil, server.name, RPL_MOTDSTART, client.nick, fmt.Sprintf("- %s Message of the day - ", server.name))
|
||||
for _, line := range server.motdLines {
|
||||
client.Send(nil, server.nameString, RPL_MOTD, client.nickString, line)
|
||||
client.Send(nil, server.name, RPL_MOTD, client.nick, line)
|
||||
}
|
||||
client.Send(nil, server.nameString, RPL_ENDOFMOTD, client.nickString, "End of MOTD command")
|
||||
client.Send(nil, server.name, RPL_ENDOFMOTD, client.nick, "End of MOTD command")
|
||||
}
|
||||
|
||||
func (s *Server) Id() Name {
|
||||
func (s *Server) Id() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
func (s *Server) String() string {
|
||||
return s.name.String()
|
||||
}
|
||||
|
||||
func (s *Server) Nick() Name {
|
||||
func (s *Server) Nick() string {
|
||||
return s.Id()
|
||||
}
|
||||
|
||||
@ -429,7 +431,7 @@ func (s *Server) Nick() Name {
|
||||
// PASS <password>
|
||||
func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
if client.registered {
|
||||
client.Send(nil, server.nameString, ERR_ALREADYREGISTRED, client.nickString, "You may not reregister")
|
||||
client.Send(nil, server.name, ERR_ALREADYREGISTRED, client.nick, "You may not reregister")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -442,8 +444,8 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
// check the provided password
|
||||
password := []byte(msg.Params[0])
|
||||
if ComparePassword(server.password, password) != nil {
|
||||
client.Send(nil, server.nameString, ERR_PASSWDMISMATCH, client.nickString, "Password incorrect")
|
||||
client.Send(nil, server.nameString, "ERROR", "Password incorrect")
|
||||
client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, "Password incorrect")
|
||||
client.Send(nil, server.name, "ERROR", "Password incorrect")
|
||||
return true
|
||||
}
|
||||
|
||||
@ -454,12 +456,12 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
// PROXY TCP4/6 SOURCEIP DESTIP SOURCEPORT DESTPORT
|
||||
// http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
|
||||
func proxyHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
clientAddress := IPString(client.socket.conn.RemoteAddr()).String()
|
||||
clientHostname := client.hostname.String()
|
||||
clientAddress := IPString(client.socket.conn.RemoteAddr())
|
||||
clientHostname := client.hostname
|
||||
|
||||
for _, address := range server.proxyAllowedFrom {
|
||||
if clientHostname == address || clientAddress == address {
|
||||
client.hostname = LookupHostname(NewName(msg.Params[1]))
|
||||
client.hostname = LookupHostname(msg.Params[1])
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -471,7 +473,7 @@ func proxyHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
// USER <username> * 0 <realname>
|
||||
func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
if client.registered {
|
||||
client.Send(nil, server.nameString, ERR_ALREADYREGISTRED, client.nickString, "You may not reregister")
|
||||
client.Send(nil, server.name, ERR_ALREADYREGISTRED, client.nick, "You may not reregister")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -486,7 +488,8 @@ func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
|
||||
// confirm that username is valid
|
||||
//
|
||||
if !Name(msg.Params[0]).IsNickname() {
|
||||
_, err := CasefoldName(msg.Params[0])
|
||||
if err != nil {
|
||||
client.Send(nil, "", "ERROR", "Malformed username")
|
||||
return true
|
||||
}
|
||||
@ -499,7 +502,7 @@ func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
server.clients.Remove(client)
|
||||
|
||||
if !client.HasUsername() {
|
||||
client.username = Name("~" + msg.Params[0])
|
||||
client.username = "~" + msg.Params[0]
|
||||
client.updateNickMask()
|
||||
}
|
||||
if client.realname == "" {
|
||||
@ -528,7 +531,7 @@ func quitHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
|
||||
// PING <server1> [<server2>]
|
||||
func pingHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
client.Send(nil, server.nameString, "PONG", msg.Params...)
|
||||
client.Send(nil, server.name, "PONG", msg.Params...)
|
||||
return false
|
||||
}
|
||||
|
||||
@ -544,7 +547,7 @@ func joinHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
// handle JOIN 0
|
||||
if msg.Params[0] == "0" {
|
||||
for channel := range client.channels {
|
||||
channel.Part(client, client.nickString)
|
||||
channel.Part(client, client.nickCasefolded)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -556,16 +559,15 @@ func joinHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
keys = strings.Split(msg.Params[1], ",")
|
||||
}
|
||||
|
||||
var name Name
|
||||
for i, nameString := range channels {
|
||||
name = Name(nameString)
|
||||
if !name.IsChannel() {
|
||||
fmt.Println("ISN'T CHANNEL NAME:", nameString)
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, nameString, "No such channel")
|
||||
for i, name := range channels {
|
||||
casefoldedName, err := CasefoldChannel(name)
|
||||
if err != nil {
|
||||
log.Println("ISN'T CHANNEL NAME:", name)
|
||||
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, name, "No such channel")
|
||||
continue
|
||||
}
|
||||
|
||||
channel := server.channels.Get(name)
|
||||
channel := server.channels.Get(casefoldedName)
|
||||
if channel == nil {
|
||||
channel = NewChannel(server, name, true)
|
||||
}
|
||||
@ -589,10 +591,11 @@ func partHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
}
|
||||
|
||||
for _, chname := range channels {
|
||||
channel := server.channels.Get(Name(chname))
|
||||
casefoldedChannelName, err := CasefoldChannel(chname)
|
||||
channel := server.channels.Get(casefoldedChannelName)
|
||||
|
||||
if channel == nil {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, chname, "No such channel")
|
||||
if err != nil || channel == nil {
|
||||
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, "No such channel")
|
||||
continue
|
||||
}
|
||||
|
||||
@ -603,9 +606,10 @@ func partHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
|
||||
// TOPIC <channel> [<topic>]
|
||||
func topicHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
channel := server.channels.Get(Name(msg.Params[0]))
|
||||
if channel == nil {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, msg.Params[0], "No such channel")
|
||||
name, err := CasefoldChannel(msg.Params[0])
|
||||
channel := server.channels.Get(name)
|
||||
if err != nil || channel == nil {
|
||||
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, msg.Params[0], "No such channel")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -622,26 +626,26 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
|
||||
targets := strings.Split(msg.Params[0], ",")
|
||||
message := msg.Params[1]
|
||||
|
||||
var target Name
|
||||
for _, targetString := range targets {
|
||||
target = Name(targetString)
|
||||
if target.IsChannel() {
|
||||
target, err := CasefoldChannel(targetString)
|
||||
if err != nil {
|
||||
channel := server.channels.Get(target)
|
||||
if channel == nil {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, targetString, "No such channel")
|
||||
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, targetString, "No such channel")
|
||||
continue
|
||||
}
|
||||
channel.PrivMsg(client, message)
|
||||
} else {
|
||||
target, err = CasefoldName(targetString)
|
||||
user := server.clients.Get(target)
|
||||
if user == nil {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHNICK, targetString, "No such nick")
|
||||
if err != nil || user == nil {
|
||||
client.Send(nil, server.name, ERR_NOSUCHNICK, target, "No such nick")
|
||||
continue
|
||||
}
|
||||
user.Send(nil, client.nickMaskString, "PRIVMSG", user.nickString, message)
|
||||
user.Send(nil, client.nickMaskString, "PRIVMSG", user.nick, message)
|
||||
if user.flags[Away] {
|
||||
//TODO(dan): possibly implement cooldown of away notifications to users
|
||||
client.Send(nil, server.nameString, RPL_AWAY, user.nickString, user.awayMessage)
|
||||
client.Send(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -657,7 +661,7 @@ func (client *Client) WhoisChannelsNames(target *Client) []string {
|
||||
if !target.flags[Operator] && channel.flags[Secret] && !channel.members.Has(target) {
|
||||
continue
|
||||
}
|
||||
chstrs = append(chstrs, channel.members[client].Prefixes(isMultiPrefix)+channel.name.String())
|
||||
chstrs = append(chstrs, channel.members[client].Prefixes(isMultiPrefix)+channel.name)
|
||||
index++
|
||||
}
|
||||
return chstrs
|
||||
@ -678,9 +682,14 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
if client.flags[Operator] {
|
||||
masks := strings.Split(masksString, ",")
|
||||
for _, mask := range masks {
|
||||
matches := server.clients.FindAll(Name(mask))
|
||||
casefoldedMask, err := Casefold(mask)
|
||||
if err != nil {
|
||||
client.Send(nil, client.server.name, ERR_NOSUCHNICK, mask, "No such nick")
|
||||
continue
|
||||
}
|
||||
matches := server.clients.FindAll(casefoldedMask)
|
||||
if len(matches) == 0 {
|
||||
client.Send(nil, client.server.nameString, ERR_NOSUCHNICK, mask, "No such nick")
|
||||
client.Send(nil, client.server.name, ERR_NOSUCHNICK, mask, "No such nick")
|
||||
continue
|
||||
}
|
||||
for mclient := range matches {
|
||||
@ -690,31 +699,32 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
} else {
|
||||
// specifically treat this as a single lookup rather than splitting as we do above
|
||||
// this is by design
|
||||
mclient := server.clients.Get(Name(masksString))
|
||||
if mclient == nil {
|
||||
client.Send(nil, client.server.nameString, ERR_NOSUCHNICK, masksString, "No such nick")
|
||||
casefoldedMask, err := Casefold(masksString)
|
||||
mclient := server.clients.Get(casefoldedMask)
|
||||
if err != nil || mclient == nil {
|
||||
client.Send(nil, client.server.name, ERR_NOSUCHNICK, masksString, "No such nick")
|
||||
// fall through, ENDOFWHOIS is always sent
|
||||
} else {
|
||||
client.getWhoisOf(mclient)
|
||||
}
|
||||
}
|
||||
client.Send(nil, server.nameString, RPL_ENDOFWHOIS, client.nickString, masksString, "End of /WHOIS list")
|
||||
client.Send(nil, server.name, RPL_ENDOFWHOIS, client.nick, masksString, "End of /WHOIS list")
|
||||
return false
|
||||
}
|
||||
|
||||
func (client *Client) getWhoisOf(target *Client) {
|
||||
client.Send(nil, client.server.nameString, RPL_WHOISUSER, client.nickString, target.nickString, target.username.String(), target.hostname.String(), "*", target.realname)
|
||||
client.Send(nil, client.server.name, RPL_WHOISUSER, client.nick, target.nick, target.username, target.hostname, "*", target.realname)
|
||||
//TODO(dan): ...one channel per reply? really?
|
||||
for _, line := range client.WhoisChannelsNames(target) {
|
||||
client.Send(nil, client.server.nameString, RPL_WHOISCHANNELS, client.nickString, target.nickString, line)
|
||||
client.Send(nil, client.server.name, RPL_WHOISCHANNELS, client.nick, target.nick, line)
|
||||
}
|
||||
if target.flags[Operator] {
|
||||
client.Send(nil, client.server.nameString, RPL_WHOISOPERATOR, client.nickString, target.nickString, "is an IRC operator")
|
||||
client.Send(nil, client.server.name, RPL_WHOISOPERATOR, client.nick, target.nick, "is an IRC operator")
|
||||
}
|
||||
if target.certfp != "" && (client.flags[Operator] || client == target) {
|
||||
client.Send(nil, client.server.nameString, RPL_WHOISCERTFP, client.nickString, target.nickString, fmt.Sprintf("has client certificate fingerprint %s", target.certfp))
|
||||
client.Send(nil, client.server.name, RPL_WHOISCERTFP, client.nick, target.nick, fmt.Sprintf("has client certificate fingerprint %s", target.certfp))
|
||||
}
|
||||
client.Send(nil, client.server.nameString, RPL_WHOISIDLE, client.nickString, target.nickString, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), "seconds idle, signon time")
|
||||
client.Send(nil, client.server.name, RPL_WHOISIDLE, client.nick, target.nick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), "seconds idle, signon time")
|
||||
}
|
||||
|
||||
// <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ]
|
||||
@ -734,9 +744,9 @@ func (target *Client) RplWhoReply(channel *Channel, client *Client) {
|
||||
|
||||
if channel != nil {
|
||||
flags += channel.members[client].Prefixes(target.capabilities[MultiPrefix])
|
||||
channelName = channel.name.String()
|
||||
channelName = channel.name
|
||||
}
|
||||
target.Send(nil, target.server.nameString, RPL_WHOREPLY, target.nickString, channelName, client.username.String(), client.hostname.String(), client.server.nameString, client.nickString, flags, string(client.hops), client.realname)
|
||||
target.Send(nil, target.server.name, RPL_WHOREPLY, target.nick, channelName, client.username, client.hostname, client.server.name, client.nick, flags, string(client.hops), client.realname)
|
||||
}
|
||||
|
||||
func whoChannel(client *Client, channel *Channel, friends ClientSet) {
|
||||
@ -751,9 +761,14 @@ func whoChannel(client *Client, channel *Channel, friends ClientSet) {
|
||||
func whoHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
friends := client.Friends()
|
||||
|
||||
var mask Name
|
||||
var mask string
|
||||
if len(msg.Params) > 0 {
|
||||
mask = Name(msg.Params[0])
|
||||
casefoldedMask, err := Casefold(msg.Params[0])
|
||||
if err != nil {
|
||||
client.Send(nil, server.name, ERR_UNKNOWNERROR, "WHO", "Mask isn't valid")
|
||||
return false
|
||||
}
|
||||
mask = casefoldedMask
|
||||
}
|
||||
|
||||
//TODO(dan): is this used and would I put this param in the Modern doc?
|
||||
@ -767,7 +782,7 @@ func whoHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
for _, channel := range server.channels {
|
||||
whoChannel(client, channel, friends)
|
||||
}
|
||||
} else if mask.IsChannel() {
|
||||
} else if mask[0] == '#' {
|
||||
// TODO implement wildcard matching
|
||||
//TODO(dan): ^ only for opers
|
||||
channel := server.channels.Get(mask)
|
||||
@ -780,31 +795,35 @@ func whoHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
}
|
||||
}
|
||||
|
||||
client.Send(nil, server.nameString, RPL_ENDOFWHO, client.nickString, mask.String(), "End of WHO list")
|
||||
client.Send(nil, server.name, RPL_ENDOFWHO, client.nick, mask, "End of WHO list")
|
||||
return false
|
||||
}
|
||||
|
||||
// OPER <name> <password>
|
||||
func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
name := NewName(msg.Params[0])
|
||||
name, err := CasefoldName(msg.Params[0])
|
||||
if err != nil {
|
||||
client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, "Password incorrect")
|
||||
return true
|
||||
}
|
||||
hash := server.operators[name]
|
||||
password := []byte(msg.Params[1])
|
||||
|
||||
err := ComparePassword(hash, password)
|
||||
err = ComparePassword(hash, password)
|
||||
|
||||
if (hash == nil) || (err != nil) {
|
||||
client.Send(nil, server.nameString, ERR_PASSWDMISMATCH, client.nickString, "Password incorrect")
|
||||
client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, "Password incorrect")
|
||||
return true
|
||||
}
|
||||
|
||||
client.flags[Operator] = true
|
||||
client.Send(nil, server.nameString, RPL_YOUREOPER, client.nickString, "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{
|
||||
mode: Operator,
|
||||
op: Add,
|
||||
}}
|
||||
client.Send(nil, server.nameString, "MODE", client.nickString, modech.String())
|
||||
client.Send(nil, server.name, "MODE", client.nick, modech.String())
|
||||
return false
|
||||
}
|
||||
|
||||
@ -830,17 +849,17 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
var op ModeOp
|
||||
if client.flags[Away] {
|
||||
op = Add
|
||||
client.Send(nil, server.nameString, RPL_NOWAWAY, client.nickString, "You have been marked as being away")
|
||||
client.Send(nil, server.name, RPL_NOWAWAY, client.nick, "You have been marked as being away")
|
||||
} else {
|
||||
op = Remove
|
||||
client.Send(nil, server.nameString, RPL_UNAWAY, client.nickString, "You are no longer marked as being away")
|
||||
client.Send(nil, server.name, RPL_UNAWAY, client.nick, "You are no longer marked as being away")
|
||||
}
|
||||
//TODO(dan): Should this be sent automagically as part of setting the flag/mode?
|
||||
modech := ModeChanges{&ModeChange{
|
||||
mode: Away,
|
||||
op: op,
|
||||
}}
|
||||
client.Send(nil, server.nameString, "MODE", client.nickString, client.nickString, modech.String())
|
||||
client.Send(nil, server.name, "MODE", client.nick, client.nick, modech.String())
|
||||
|
||||
// dispatch away-notify
|
||||
for friend := range client.Friends() {
|
||||
@ -858,14 +877,20 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
func isonHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
var nicks = msg.Params
|
||||
|
||||
var err error
|
||||
var casefoldedNick string
|
||||
ison := make([]string, 0)
|
||||
for _, nick := range nicks {
|
||||
if iclient := server.clients.Get(Name(nick)); iclient != nil {
|
||||
ison = append(ison, iclient.Nick().String())
|
||||
casefoldedNick, err = CasefoldName(nick)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if iclient := server.clients.Get(casefoldedNick); iclient != nil {
|
||||
ison = append(ison, iclient.nick)
|
||||
}
|
||||
}
|
||||
|
||||
client.Send(nil, server.nameString, RPL_ISON, client.nickString, strings.Join(nicks, " "))
|
||||
client.Send(nil, server.name, RPL_ISON, client.nick, strings.Join(nicks, " "))
|
||||
return false
|
||||
}
|
||||
|
||||
@ -886,10 +911,9 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
targets := strings.Split(msg.Params[0], ",")
|
||||
message := msg.Params[1]
|
||||
|
||||
var target Name
|
||||
for _, targetString := range targets {
|
||||
target = Name(targetString)
|
||||
if target.IsChannel() {
|
||||
target, cerr := CasefoldChannel(targetString)
|
||||
if cerr == nil {
|
||||
channel := server.channels.Get(target)
|
||||
if channel == nil {
|
||||
// errors silently ignored with NOTICE as per RFC
|
||||
@ -897,12 +921,17 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
}
|
||||
channel.PrivMsg(client, message)
|
||||
} else {
|
||||
target, err := CasefoldName(targetString)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
user := server.clients.Get(target)
|
||||
if user == nil {
|
||||
// errors silently ignored with NOTICE as per RFC
|
||||
continue
|
||||
}
|
||||
user.Send(nil, client.nickMaskString, "NOTICE", user.nickString, message)
|
||||
user.Send(nil, client.nickMaskString, "NOTICE", user.nick, message)
|
||||
}
|
||||
}
|
||||
return false
|
||||
@ -913,7 +942,7 @@ func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
channels := strings.Split(msg.Params[0], ",")
|
||||
users := strings.Split(msg.Params[1], ",")
|
||||
if (len(channels) != len(users)) && (len(users) != 1) {
|
||||
client.Send(nil, server.nameString, ERR_NEEDMOREPARAMS, client.nickString, "KICK", "Not enough parameters")
|
||||
client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, "KICK", "Not enough parameters")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -931,15 +960,17 @@ func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
comment = msg.Params[2]
|
||||
}
|
||||
for chname, nickname := range kicks {
|
||||
channel := server.channels.Get(Name(chname))
|
||||
if channel == nil {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, chname, "No such channel")
|
||||
casefoldedChname, err := CasefoldChannel(chname)
|
||||
channel := server.channels.Get(casefoldedChname)
|
||||
if err != nil || channel == nil {
|
||||
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, "No such channel")
|
||||
continue
|
||||
}
|
||||
|
||||
target := server.clients.Get(Name(nickname))
|
||||
if target == nil {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHNICK, nickname, "No such nick")
|
||||
casefoldedNickname, err := CasefoldName(nickname)
|
||||
target := server.clients.Get(casefoldedNickname)
|
||||
if err != nil || target == nil {
|
||||
client.Send(nil, server.name, ERR_NOSUCHNICK, nickname, "No such nick")
|
||||
continue
|
||||
}
|
||||
|
||||
@ -968,7 +999,7 @@ func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
}
|
||||
channel.Kick(client, target, comment)
|
||||
} else {
|
||||
client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, chname, "You're not a channel operator")
|
||||
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, chname, "You're not a channel operator")
|
||||
}
|
||||
}
|
||||
return false
|
||||
@ -988,7 +1019,7 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
//TODO(dan): target server when we have multiple servers
|
||||
//TODO(dan): we should continue just fine if it's this current server though
|
||||
if target != "" {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHSERVER, client.nickString, target, "No such server")
|
||||
client.Send(nil, server.name, ERR_NOSUCHSERVER, client.nick, target, "No such server")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -1001,15 +1032,16 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
}
|
||||
} else {
|
||||
for _, chname := range channels {
|
||||
channel := server.channels.Get(Name(chname))
|
||||
if channel == nil || (!client.flags[Operator] && channel.flags[Secret]) {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, chname, "No such channel")
|
||||
casefoldedChname, err := CasefoldChannel(chname)
|
||||
channel := server.channels.Get(casefoldedChname)
|
||||
if err != nil || channel == nil || (!client.flags[Operator] && channel.flags[Secret]) {
|
||||
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, "No such channel")
|
||||
continue
|
||||
}
|
||||
client.RplList(channel)
|
||||
}
|
||||
}
|
||||
client.Send(nil, server.nameString, RPL_LISTEND, client.nickString, "End of LIST")
|
||||
client.Send(nil, server.name, RPL_LISTEND, client.nick, "End of LIST")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -1026,7 +1058,7 @@ func (target *Client) RplList(channel *Channel) {
|
||||
}
|
||||
}
|
||||
|
||||
target.Send(nil, target.server.nameString, RPL_LIST, target.nickString, channel.nameString, string(memberCount), channel.topic)
|
||||
target.Send(nil, target.server.name, RPL_LIST, target.nick, channel.name, string(memberCount), channel.topic)
|
||||
}
|
||||
|
||||
// NAMES [<channel>{,<channel>}]
|
||||
@ -1048,9 +1080,10 @@ func namesHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
}
|
||||
|
||||
for _, chname := range channels {
|
||||
channel := server.channels.Get(Name(chname))
|
||||
if channel == nil {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, chname, "No such channel")
|
||||
casefoldedChname, err := CasefoldChannel(chname)
|
||||
channel := server.channels.Get(casefoldedChname)
|
||||
if err != nil || channel == nil {
|
||||
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, "No such channel")
|
||||
continue
|
||||
}
|
||||
channel.Names(client)
|
||||
@ -1064,12 +1097,13 @@ func versionHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
|
||||
if len(msg.Params) > 0 {
|
||||
target = msg.Params[0]
|
||||
}
|
||||
if (target != "") && (Name(target) != server.name) {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHSERVER, client.nickString, target, "No such server")
|
||||
casefoldedTarget, err := Casefold(target)
|
||||
if (target != "") && err != nil || (casefoldedTarget != server.nameCasefolded) {
|
||||
client.Send(nil, server.name, ERR_NOSUCHSERVER, client.nick, target, "No such server")
|
||||
return false
|
||||
}
|
||||
|
||||
client.Send(nil, server.nameString, RPL_VERSION, client.nickString, VER, server.nameString)
|
||||
client.Send(nil, server.name, RPL_VERSION, client.nick, VER, server.name)
|
||||
client.RplISupport()
|
||||
return false
|
||||
}
|
||||
@ -1079,16 +1113,18 @@ func inviteHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
nickname := msg.Params[0]
|
||||
channelName := msg.Params[1]
|
||||
|
||||
target := server.clients.Get(Name(nickname))
|
||||
if target == nil {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHNICK, client.nickString, nickname, "No such nick")
|
||||
casefoldedNickname, err := CasefoldName(nickname)
|
||||
target := server.clients.Get(casefoldedNickname)
|
||||
if err != nil || target == nil {
|
||||
client.Send(nil, server.name, ERR_NOSUCHNICK, client.nick, nickname, "No such nick")
|
||||
return false
|
||||
}
|
||||
|
||||
channel := server.channels.Get(Name(channelName))
|
||||
if channel == nil {
|
||||
client.Send(nil, server.nameString, RPL_INVITING, client.nickString, target.nickString, channelName)
|
||||
target.Send(nil, client.nickMaskString, "INVITE", target.nickString, channel.nameString)
|
||||
casefoldedChannelName, err := CasefoldChannel(channelName)
|
||||
channel := server.channels.Get(casefoldedChannelName)
|
||||
if err != nil || channel == nil {
|
||||
client.Send(nil, server.name, RPL_INVITING, client.nick, target.nick, channelName)
|
||||
target.Send(nil, client.nickMaskString, "INVITE", target.nick, channel.name)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1102,11 +1138,12 @@ func timeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
if len(msg.Params) > 0 {
|
||||
target = msg.Params[0]
|
||||
}
|
||||
if (target != "") && (Name(target) != server.name) {
|
||||
client.Send(nil, server.nameString, ERR_NOSUCHSERVER, client.nickString, target, "No such server")
|
||||
casefoldedTarget, err := Casefold(target)
|
||||
if (target != "") && err != nil || (casefoldedTarget != server.nameCasefolded) {
|
||||
client.Send(nil, server.name, ERR_NOSUCHSERVER, client.nick, target, "No such server")
|
||||
return false
|
||||
}
|
||||
client.Send(nil, server.nameString, RPL_TIME, client.nickString, server.nameString, time.Now().Format(time.RFC1123))
|
||||
client.Send(nil, server.name, RPL_TIME, client.nick, server.name, time.Now().Format(time.RFC1123))
|
||||
return false
|
||||
}
|
||||
|
||||
@ -1118,13 +1155,14 @@ func killHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
comment = msg.Params[1]
|
||||
}
|
||||
|
||||
target := server.clients.Get(Name(nickname))
|
||||
if target == nil {
|
||||
client.Send(nil, client.server.nameString, ERR_NOSUCHNICK, nickname, "No such nick")
|
||||
casefoldedNickname, err := CasefoldName(nickname)
|
||||
target := server.clients.Get(casefoldedNickname)
|
||||
if err != nil || target == nil {
|
||||
client.Send(nil, client.server.name, ERR_NOSUCHNICK, nickname, "No such nick")
|
||||
return false
|
||||
}
|
||||
|
||||
quitMsg := fmt.Sprintf("Killed (%s (%s))", client.nickString, comment)
|
||||
quitMsg := fmt.Sprintf("Killed (%s (%s))", client.nick, comment)
|
||||
target.Quit(quitMsg)
|
||||
target.destroy()
|
||||
return false
|
||||
@ -1143,15 +1181,15 @@ func whowasHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
// target = msg.Params[2]
|
||||
//}
|
||||
for _, nickname := range nicknames {
|
||||
results := server.whoWas.Find(Name(nickname), count)
|
||||
results := server.whoWas.Find(nickname, count)
|
||||
if len(results) == 0 {
|
||||
client.Send(nil, server.nameString, ERR_WASNOSUCHNICK, client.nickString, nickname, "There was no such nickname")
|
||||
client.Send(nil, server.name, ERR_WASNOSUCHNICK, client.nick, nickname, "There was no such nickname")
|
||||
} else {
|
||||
for _, whoWas := range results {
|
||||
client.Send(nil, server.nameString, RPL_WHOWASUSER, client.nickString, whoWas.nickname.String(), whoWas.username.String(), whoWas.hostname.String(), "*", whoWas.realname)
|
||||
client.Send(nil, server.name, RPL_WHOWASUSER, client.nick, whoWas.nickname, whoWas.username, whoWas.hostname, "*", whoWas.realname)
|
||||
}
|
||||
}
|
||||
client.Send(nil, server.nameString, RPL_ENDOFWHOWAS, client.nickString, nickname, "End of WHOWAS")
|
||||
client.Send(nil, server.name, RPL_ENDOFWHOWAS, client.nick, nickname, "End of WHOWAS")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
120
irc/strings.go
120
irc/strings.go
@ -6,91 +6,71 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/unicode/norm"
|
||||
"golang.org/x/text/secure/precis"
|
||||
)
|
||||
|
||||
var (
|
||||
// regexps
|
||||
// these get replaced with real regexes at server load time
|
||||
|
||||
ChannelNameExpr = regexp.MustCompile("^$")
|
||||
NicknameExpr = regexp.MustCompile("^$")
|
||||
errInvalidCharacter = errors.New("Invalid character")
|
||||
)
|
||||
|
||||
// Names are normalized and canonicalized to remove formatting marks
|
||||
// and simplify usage. They are things like hostnames and usermasks.
|
||||
type Name string
|
||||
|
||||
func NewName(str string) Name {
|
||||
return Name(norm.NFKC.String(str))
|
||||
// Casefold returns a casefolded string, without doing any name or channel character checks.
|
||||
func Casefold(str string) (string, error) {
|
||||
return precis.Nickname.String(str)
|
||||
}
|
||||
|
||||
func NewNames(strs []string) []Name {
|
||||
names := make([]Name, len(strs))
|
||||
for index, str := range strs {
|
||||
names[index] = NewName(str)
|
||||
// CasefoldChannel returns a casefolded version of a channel name.
|
||||
func CasefoldChannel(name string) (string, error) {
|
||||
lowered, err := precis.Nickname.String(name)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return names
|
||||
|
||||
if lowered[0] != '#' {
|
||||
return "", errInvalidCharacter
|
||||
}
|
||||
|
||||
// space can't be used
|
||||
// , is used as a separator
|
||||
// * is used in mask matching
|
||||
// ? is used in mask matching
|
||||
if strings.Contains(lowered, " ") || strings.Contains(lowered, ",") ||
|
||||
strings.Contains(lowered, "*") || strings.Contains(lowered, "?") {
|
||||
return "", errInvalidCharacter
|
||||
}
|
||||
|
||||
return lowered, err
|
||||
}
|
||||
|
||||
// tests
|
||||
// CasefoldName returns a casefolded version of a nick/user name.
|
||||
func CasefoldName(name string) (string, error) {
|
||||
lowered, err := precis.Nickname.String(name)
|
||||
|
||||
func (name Name) IsChannel() bool {
|
||||
return ChannelNameExpr.MatchString(name.String())
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
func (name Name) IsNickname() bool {
|
||||
namestr := name.String()
|
||||
// * is used for unregistered clients
|
||||
// * is used for mask matching
|
||||
// ? is used for mask matching
|
||||
// . is used to denote server names
|
||||
// , is used as a separator by the protocol
|
||||
// ! separates username from nickname
|
||||
// @ separates nick+user from hostname
|
||||
// space can't be used
|
||||
// , is used as a separator
|
||||
// * is used in mask matching
|
||||
// ? is used in mask matching
|
||||
// . denotes a server name
|
||||
// ! separates nickname from username
|
||||
// @ separates username from hostname
|
||||
// : means trailing
|
||||
// # is a channel prefix
|
||||
// ~&@%+ are channel membership prefixes
|
||||
// - is typically disallowed from first char of nicknames
|
||||
// nicknames can't start with digits
|
||||
if strings.Contains(namestr, "*") || strings.Contains(namestr, "?") ||
|
||||
strings.Contains(namestr, ".") || strings.Contains(namestr, ",") ||
|
||||
strings.Contains(namestr, "!") || strings.Contains(namestr, "@") ||
|
||||
strings.Contains("#~&@%+-1234567890", string(namestr[0])) {
|
||||
return false
|
||||
// - I feel like disallowing
|
||||
if strings.Contains(lowered, " ") || strings.Contains(lowered, ",") ||
|
||||
strings.Contains(lowered, "*") || strings.Contains(lowered, "?") ||
|
||||
strings.Contains(lowered, ".") || strings.Contains(lowered, "!") ||
|
||||
strings.Contains(lowered, "@") ||
|
||||
strings.Contains("#~&@%+-", string(lowered[0])) {
|
||||
return "", errInvalidCharacter
|
||||
}
|
||||
// names that look like hostnames are restricted to servers, as with other ircds
|
||||
if IsHostname(namestr) {
|
||||
return false
|
||||
}
|
||||
return NicknameExpr.MatchString(namestr)
|
||||
}
|
||||
|
||||
// conversions
|
||||
|
||||
func (name Name) String() string {
|
||||
return string(name)
|
||||
}
|
||||
|
||||
func (name Name) ToLower() Name {
|
||||
return Name(strings.ToLower(name.String()))
|
||||
}
|
||||
|
||||
// It's safe to coerce a Name to Text. Name is a strict subset of Text.
|
||||
func (name Name) Text() Text {
|
||||
return Text(name)
|
||||
}
|
||||
|
||||
// Text is PRIVMSG, NOTICE, or TOPIC data. It's canonicalized UTF8
|
||||
// data to simplify but keeps all formatting.
|
||||
type Text string
|
||||
|
||||
func NewText(str string) Text {
|
||||
return Text(norm.NFC.String(str))
|
||||
}
|
||||
|
||||
func (text Text) String() string {
|
||||
return string(text)
|
||||
|
||||
return lowered, err
|
||||
}
|
||||
|
22
irc/types.go
22
irc/types.go
@ -13,25 +13,29 @@ import (
|
||||
// simple types
|
||||
//
|
||||
|
||||
type ChannelNameMap map[Name]*Channel
|
||||
type ChannelNameMap map[string]*Channel
|
||||
|
||||
func (channels ChannelNameMap) Get(name Name) *Channel {
|
||||
return channels[name.ToLower()]
|
||||
func (channels ChannelNameMap) Get(name string) *Channel {
|
||||
name, err := CasefoldChannel(name)
|
||||
if err == nil {
|
||||
return channels[name]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (channels ChannelNameMap) Add(channel *Channel) error {
|
||||
if channels[channel.name.ToLower()] != nil {
|
||||
if channels[channel.nameCasefolded] != nil {
|
||||
return fmt.Errorf("%s: already set", channel.name)
|
||||
}
|
||||
channels[channel.name.ToLower()] = channel
|
||||
channels[channel.nameCasefolded] = channel
|
||||
return nil
|
||||
}
|
||||
|
||||
func (channels ChannelNameMap) Remove(channel *Channel) error {
|
||||
if channel != channels[channel.name.ToLower()] {
|
||||
if channel != channels[channel.nameCasefolded] {
|
||||
return fmt.Errorf("%s: mismatch", channel.name)
|
||||
}
|
||||
delete(channels, channel.name.ToLower())
|
||||
delete(channels, channel.nameCasefolded)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -118,6 +122,6 @@ func (channels ChannelSet) First() *Channel {
|
||||
//
|
||||
|
||||
type Identifiable interface {
|
||||
Id() Name
|
||||
Nick() Name
|
||||
Id() string
|
||||
Nick() string
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2012-2014 Jeremy Latt
|
||||
// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
|
||||
// released under the MIT license
|
||||
|
||||
package irc
|
||||
@ -10,10 +11,11 @@ type WhoWasList struct {
|
||||
}
|
||||
|
||||
type WhoWas struct {
|
||||
nickname Name
|
||||
username Name
|
||||
hostname Name
|
||||
realname string
|
||||
nicknameCasefolded string
|
||||
nickname string
|
||||
username string
|
||||
hostname string
|
||||
realname string
|
||||
}
|
||||
|
||||
func NewWhoWasList(size uint) *WhoWasList {
|
||||
@ -24,10 +26,11 @@ func NewWhoWasList(size uint) *WhoWasList {
|
||||
|
||||
func (list *WhoWasList) Append(client *Client) {
|
||||
list.buffer[list.end] = &WhoWas{
|
||||
nickname: client.Nick(),
|
||||
username: client.username,
|
||||
hostname: client.hostname,
|
||||
realname: client.realname,
|
||||
nicknameCasefolded: client.nickCasefolded,
|
||||
nickname: client.nick,
|
||||
username: client.username,
|
||||
hostname: client.hostname,
|
||||
realname: client.realname,
|
||||
}
|
||||
list.end = (list.end + 1) % len(list.buffer)
|
||||
if list.end == list.start {
|
||||
@ -35,10 +38,16 @@ func (list *WhoWasList) Append(client *Client) {
|
||||
}
|
||||
}
|
||||
|
||||
func (list *WhoWasList) Find(nickname Name, limit int64) []*WhoWas {
|
||||
func (list *WhoWasList) Find(nickname string, limit int64) []*WhoWas {
|
||||
results := make([]*WhoWas, 0)
|
||||
|
||||
casefoldedNickname, err := CasefoldName(nickname)
|
||||
if err != nil {
|
||||
return results
|
||||
}
|
||||
|
||||
for whoWas := range list.Each() {
|
||||
if nickname != whoWas.nickname {
|
||||
if casefoldedNickname != whoWas.nicknameCasefolded {
|
||||
continue
|
||||
}
|
||||
results = append(results, whoWas)
|
||||
|
Loading…
Reference in New Issue
Block a user