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