3
0
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:
Daniel Oaks 2016-10-11 23:51:46 +10:00
parent 2bfcc553ce
commit 5e72409695
16 changed files with 587 additions and 515 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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, "?")

View File

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

View File

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

View File

@ -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...)...)
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 "", err
} }
return names
if lowered[0] != '#' {
return "", errInvalidCharacter
}
// space can't be used
// , is used as a separator
// * is used in mask matching
// ? is used in mask matching
if strings.Contains(lowered, " ") || strings.Contains(lowered, ",") ||
strings.Contains(lowered, "*") || strings.Contains(lowered, "?") {
return "", errInvalidCharacter
}
return lowered, err
} }
// tests // CasefoldName returns a casefolded version of a nick/user name.
func CasefoldName(name string) (string, error) {
lowered, err := precis.Nickname.String(name)
func (name Name) IsChannel() bool { if err != nil {
return ChannelNameExpr.MatchString(name.String()) return "", err
} }
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 // . denotes a server name
// . is used to denote server names // ! separates nickname from username
// , is used as a separator by the protocol // @ separates username from hostname
// ! separates username from nickname // : means trailing
// @ separates nick+user from hostname
// # 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 lowered, err
return false
}
return NicknameExpr.MatchString(namestr)
}
// conversions
func (name Name) String() string {
return string(name)
}
func (name Name) ToLower() Name {
return Name(strings.ToLower(name.String()))
}
// It's safe to coerce a Name to Text. Name is a strict subset of Text.
func (name Name) Text() Text {
return Text(name)
}
// Text is PRIVMSG, NOTICE, or TOPIC data. It's canonicalized UTF8
// data to simplify but keeps all formatting.
type Text string
func NewText(str string) Text {
return Text(norm.NFC.String(str))
}
func (text Text) String() string {
return string(text)
} }

View File

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

View File

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