3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-29 15:40:02 +01:00

urgh this should not even be commited yet, this will all be squashed out

This commit is contained in:
Daniel Oaks 2016-06-17 22:17:42 +10:00
parent dbca03e948
commit e83283e7fd
7 changed files with 1117 additions and 1124 deletions

View File

@ -9,6 +9,8 @@ import (
"fmt" "fmt"
"net" "net"
"time" "time"
"github.com/DanielOaks/girc-go/ircmsg"
) )
const ( const (
@ -65,51 +67,44 @@ func NewClient(server *Server, conn net.Conn) *Client {
func (client *Client) run() { func (client *Client) run() {
var command Command var command Command
var err error var err error
var isExiting bool
var line string var line string
var msg ircmsg.IrcMessage
// Set the hostname for this client. The client may later send a PROXY // Set the hostname for this client. The client may later send a PROXY
// command from stunnel that sets the hostname to something more accurate. // command from stunnel that sets the hostname to something more accurate.
client.hostname = AddrLookupHostname(client.socket.conn.RemoteAddr()) client.hostname = AddrLookupHostname(client.socket.conn.RemoteAddr())
for err == nil { //TODO(dan): Make this a socketreactor from ircbnc
//TODO(dan): does this read sockets correctly and split lines properly? (think that ZNC bug that kept happening with mammon) for {
if line, err = client.socket.Read(); err != nil { line, err = client.socket.Read()
command = NewQuitCommand("connection closed") if err != nil {
client.Quit("connection closed")
} else if command, err = ParseCommand(line); err != nil { break
switch err {
case ErrParseCommand:
//TODO(dan): why is this a notice? there's a proper numeric for this I swear
client.Reply(RplNotice(client.server, client,
NewText("failed to parse command")))
}
// so the read loop will continue
err = nil
continue
} else if checkPass, ok := command.(checkPasswordCommand); ok {
checkPass.LoadPassword(client.server)
// Block the client thread while handling a potentially expensive
// password bcrypt operation. Since the server is single-threaded
// for commands, we don't want the server to perform the bcrypt,
// blocking anyone else from sending commands until it
// completes. This could be a form of DoS if handled naively.
checkPass.CheckPassword()
} }
client.send(command) msg, err = ParseLine(line)
if err != nil {
client.Quit("received malformed command")
break
}
isExiting = Run(client.server, client, msg)
if isExiting {
break
} }
} }
func (client *Client) send(command Command) { // ensure client connection gets closed
command.SetClient(client) client.Destroy()
client.server.commands <- command
} }
//
// quit timer goroutine // quit timer goroutine
//
func (client *Client) connectionTimeout() { func (client *Client) connectionTimeout() {
client.send(NewQuitCommand("connection timeout")) client.Quit("connection timeout")
} }
// //
@ -158,31 +153,6 @@ func (client *Client) Register() {
client.Touch() client.Touch()
} }
func (client *Client) destroy() {
// clean up channels
for channel := range client.channels {
channel.Quit(client)
}
// clean up server
client.server.clients.Remove(client)
// clean up self
if client.idleTimer != nil {
client.idleTimer.Stop()
}
if client.quitTimer != nil {
client.quitTimer.Stop()
}
client.socket.Close()
Log.debug.Printf("%s: destroyed", client)
}
func (client *Client) IdleTime() time.Duration { func (client *Client) IdleTime() time.Duration {
return time.Since(client.atime) return time.Since(client.atime)
} }
@ -238,6 +208,7 @@ func (c *Client) String() string {
return c.Id().String() return c.Id().String()
} }
// 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)
friends.Add(client) friends.Add(client)
@ -276,16 +247,36 @@ func (client *Client) Reply(reply string) error {
} }
func (client *Client) Quit(message Text) { func (client *Client) Quit(message Text) {
if client.hasQuit { client.Send("QUIT", message)
}
func (client *Client) destroy() {
if client.isDestroyed {
return return
} }
client.hasQuit = true client.isDestroyed = true
client.Reply(RplError("quit"))
client.server.whoWas.Append(client) client.server.whoWas.Append(client)
friends := client.Friends() friends := client.Friends()
friends.Remove(client) friends.Remove(client)
client.destroy()
// clean up channels
for channel := range client.channels {
channel.Quit(client)
}
// clean up server
client.server.clients.Remove(client)
// clean up self
if client.idleTimer != nil {
client.idleTimer.Stop()
}
if client.quitTimer != nil {
client.quitTimer.Stop()
}
client.socket.Close()
if len(friends) > 0 { if len(friends) > 0 {
reply := RplQuit(client, message) reply := RplQuit(client, message)

550
irc/commandhandlers.go Normal file
View File

@ -0,0 +1,550 @@
// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2014-2015 Edmund Huber
// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
package irc
import (
"fmt"
"github.com/DanielOaks/girc-go/ircmsg"
)
// NICK <nickname>
func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
// check NICK validity
// send NICK change to primary server thread for processing
// |-> ensure no other client exists with that nickname
return true
}
type ModeChange struct {
mode UserMode
op ModeOp
}
func (change *ModeChange) String() string {
return fmt.Sprintf("%s%s", change.op, change.mode)
}
type ModeChanges []*ModeChange
func (changes ModeChanges) String() string {
if len(changes) == 0 {
return ""
}
op := changes[0].op
str := changes[0].op.String()
for _, change := range changes {
if change.op != op {
op = change.op
str += change.op.String()
}
str += change.mode.String()
}
return str
}
/*
type ModeCommand struct {
BaseCommand
nickname Name
changes ModeChanges
}
// MODE <nickname> ( "+" / "-" )? *( "+" / "-" / <mode character> )
func ParseUserModeCommand(nickname Name, args []string) (Command, error) {
cmd := &ModeCommand{
nickname: nickname,
changes: make(ModeChanges, 0),
}
// account for MODE command with no args to list things
if len(args) < 1 {
// don't do any further processing
return cmd, nil
}
modeArg := args[0]
op := ModeOp(modeArg[0])
if (op == Add) || (op == Remove) {
modeArg = modeArg[1:]
} else {
return nil, ErrParseCommand
}
for _, mode := range modeArg {
if mode == '-' || mode == '+' {
op = ModeOp(mode)
continue
}
cmd.changes = append(cmd.changes, &ModeChange{
mode: UserMode(mode),
op: op,
})
}
return cmd, nil
}
*/
type ChannelModeChange struct {
mode ChannelMode
op ModeOp
arg string
}
func (change *ChannelModeChange) String() (str string) {
if (change.op == Add) || (change.op == Remove) {
str = change.op.String()
}
str += change.mode.String()
if change.arg != "" {
str += " " + change.arg
}
return
}
type ChannelModeChanges []*ChannelModeChange
func (changes ChannelModeChanges) String() string {
if len(changes) == 0 {
return ""
}
op := changes[0].op
str := changes[0].op.String()
for _, change := range changes {
if change.op != op {
op = change.op
str += change.op.String()
}
str += change.mode.String()
}
for _, change := range changes {
if change.arg == "" {
continue
}
str += " " + change.arg
}
return str
}
type ChannelModeCommand struct {
channel Name
changes ChannelModeChanges
}
// MODE <channel> ( "+" / "-" )? *( "+" / "-" / <mode character> ) *<modeparams>
func ParseChannelModeCommand(channel Name, args []string) (Command, error) {
cmd := &ChannelModeCommand{
channel: channel,
changes: make(ChannelModeChanges, 0),
}
// account for MODE command with no args to list things
if len(args) < 1 {
// don't do any further processing
return cmd, nil
}
modeArg := args[0]
op := ModeOp(modeArg[0])
if (op == Add) || (op == Remove) {
modeArg = modeArg[1:]
} else {
return nil, ErrParseCommand
}
currentArgIndex := 1
for _, mode := range modeArg {
if mode == '-' || mode == '+' {
op = ModeOp(mode)
continue
}
change := &ChannelModeChange{
mode: ChannelMode(mode),
op: op,
}
switch change.mode {
// TODO(dan): separate this into the type A/B/C/D args and use those lists here
case Key, BanMask, ExceptMask, InviteMask, UserLimit,
ChannelOperator, ChannelFounder, ChannelAdmin, Halfop, Voice:
if len(args) > currentArgIndex {
change.arg = args[currentArgIndex]
currentArgIndex++
} else {
// silently skip this mode
continue
}
}
cmd.changes = append(cmd.changes, change)
}
return cmd, nil
}
/*
func ParseModeCommand(args []string) (Command, error) {
name := NewName(args[0])
if name.IsChannel() {
return ParseChannelModeCommand(name, args[1:])
} else {
return ParseUserModeCommand(name, args[1:])
}
}
type WhoisCommand struct {
BaseCommand
target Name
masks []Name
}
// WHOIS [ <target> ] <mask> *( "," <mask> )
func ParseWhoisCommand(args []string) (Command, error) {
var masks string
var target string
if len(args) > 1 {
target = args[0]
masks = args[1]
} else {
masks = args[0]
}
return &WhoisCommand{
target: NewName(target),
masks: NewNames(strings.Split(masks, ",")),
}, nil
}
type WhoCommand struct {
BaseCommand
mask Name
operatorOnly bool
}
// WHO [ <mask> [ "o" ] ]
func ParseWhoCommand(args []string) (Command, error) {
cmd := &WhoCommand{}
if len(args) > 0 {
cmd.mask = NewName(args[0])
}
if (len(args) > 1) && (args[1] == "o") {
cmd.operatorOnly = true
}
return cmd, nil
}
type OperCommand struct {
PassCommand
name Name
}
func (msg *OperCommand) LoadPassword(server *Server) {
msg.hash = server.operators[msg.name]
}
// OPER <name> <password>
func ParseOperCommand(args []string) (Command, error) {
cmd := &OperCommand{
name: NewName(args[0]),
}
cmd.password = []byte(args[1])
return cmd, nil
}
type CapCommand struct {
BaseCommand
subCommand CapSubCommand
capabilities CapabilitySet
}
func ParseCapCommand(args []string) (Command, error) {
cmd := &CapCommand{
subCommand: CapSubCommand(strings.ToUpper(args[0])),
capabilities: make(CapabilitySet),
}
if len(args) > 1 {
strs := spacesExpr.Split(args[1], -1)
for _, str := range strs {
cmd.capabilities[Capability(str)] = true
}
}
return cmd, nil
}
// HAPROXY support
type ProxyCommand struct {
BaseCommand
net Name
sourceIP Name
destIP Name
sourcePort Name
destPort Name
hostname Name // looked up in socket thread
}
func NewProxyCommand(hostname Name) *ProxyCommand {
cmd := &ProxyCommand{
hostname: hostname,
}
cmd.code = PROXY
return cmd
}
func ParseProxyCommand(args []string) (Command, error) {
return &ProxyCommand{
net: NewName(args[0]),
sourceIP: NewName(args[1]),
destIP: NewName(args[2]),
sourcePort: NewName(args[3]),
destPort: NewName(args[4]),
hostname: LookupHostname(NewName(args[1])),
}, nil
}
type AwayCommand struct {
BaseCommand
text Text
}
func ParseAwayCommand(args []string) (Command, error) {
cmd := &AwayCommand{}
if len(args) > 0 {
cmd.text = NewText(args[0])
}
return cmd, nil
}
type IsOnCommand struct {
BaseCommand
nicks []Name
}
func ParseIsOnCommand(args []string) (Command, error) {
return &IsOnCommand{
nicks: NewNames(args),
}, nil
}
type MOTDCommand struct {
BaseCommand
target Name
}
func ParseMOTDCommand(args []string) (Command, error) {
cmd := &MOTDCommand{}
if len(args) > 0 {
cmd.target = NewName(args[0])
}
return cmd, nil
}
type NoticeCommand struct {
BaseCommand
target Name
message Text
}
func ParseNoticeCommand(args []string) (Command, error) {
return &NoticeCommand{
target: NewName(args[0]),
message: NewText(args[1]),
}, nil
}
type KickCommand struct {
BaseCommand
kicks map[Name]Name
comment Text
}
func (msg *KickCommand) Comment() Text {
if msg.comment == "" {
return msg.Client().Nick().Text()
}
return msg.comment
}
func ParseKickCommand(args []string) (Command, error) {
channels := NewNames(strings.Split(args[0], ","))
users := NewNames(strings.Split(args[1], ","))
if (len(channels) != len(users)) && (len(users) != 1) {
return nil, NotEnoughArgsError
}
cmd := &KickCommand{
kicks: make(map[Name]Name),
}
for index, channel := range channels {
if len(users) == 1 {
cmd.kicks[channel] = users[0]
} else {
cmd.kicks[channel] = users[index]
}
}
if len(args) > 2 {
cmd.comment = NewText(args[2])
}
return cmd, nil
}
type ListCommand struct {
BaseCommand
channels []Name
target Name
}
func ParseListCommand(args []string) (Command, error) {
cmd := &ListCommand{}
if len(args) > 0 {
cmd.channels = NewNames(strings.Split(args[0], ","))
}
if len(args) > 1 {
cmd.target = NewName(args[1])
}
return cmd, nil
}
type NamesCommand struct {
BaseCommand
channels []Name
target Name
}
func ParseNamesCommand(args []string) (Command, error) {
cmd := &NamesCommand{}
if len(args) > 0 {
cmd.channels = NewNames(strings.Split(args[0], ","))
}
if len(args) > 1 {
cmd.target = NewName(args[1])
}
return cmd, nil
}
type DebugCommand struct {
BaseCommand
subCommand Name
}
func ParseDebugCommand(args []string) (Command, error) {
return &DebugCommand{
subCommand: NewName(strings.ToUpper(args[0])),
}, nil
}
type VersionCommand struct {
BaseCommand
target Name
}
func ParseVersionCommand(args []string) (Command, error) {
cmd := &VersionCommand{}
if len(args) > 0 {
cmd.target = NewName(args[0])
}
return cmd, nil
}
type InviteCommand struct {
BaseCommand
nickname Name
channel Name
}
func ParseInviteCommand(args []string) (Command, error) {
return &InviteCommand{
nickname: NewName(args[0]),
channel: NewName(args[1]),
}, nil
}
func ParseTheaterCommand(args []string) (Command, error) {
if upperSubCmd := strings.ToUpper(args[0]); upperSubCmd == "IDENTIFY" && len(args) == 3 {
return &TheaterIdentifyCommand{
channel: NewName(args[1]),
PassCommand: PassCommand{password: []byte(args[2])},
}, nil
} else if upperSubCmd == "PRIVMSG" && len(args) == 4 {
return &TheaterPrivMsgCommand{
channel: NewName(args[1]),
asNick: NewName(args[2]),
message: NewText(args[3]),
}, nil
} else if upperSubCmd == "ACTION" && len(args) == 4 {
return &TheaterActionCommand{
channel: NewName(args[1]),
asNick: NewName(args[2]),
action: NewCTCPText(args[3]),
}, nil
} else {
return nil, ErrParseCommand
}
}
type TimeCommand struct {
BaseCommand
target Name
}
func ParseTimeCommand(args []string) (Command, error) {
cmd := &TimeCommand{}
if len(args) > 0 {
cmd.target = NewName(args[0])
}
return cmd, nil
}
type KillCommand struct {
BaseCommand
nickname Name
comment Text
}
func ParseKillCommand(args []string) (Command, error) {
return &KillCommand{
nickname: NewName(args[0]),
comment: NewText(args[1]),
}, nil
}
type WhoWasCommand struct {
BaseCommand
nicknames []Name
count int64
target Name
}
func ParseWhoWasCommand(args []string) (Command, error) {
cmd := &WhoWasCommand{
nicknames: NewNames(strings.Split(args[0], ",")),
}
if len(args) > 1 {
cmd.count, _ = strconv.ParseInt(args[1], 10, 64)
}
if len(args) > 2 {
cmd.target = NewName(args[2])
}
return cmd, nil
}
func ParseOperNickCommand(args []string) (Command, error) {
return &OperNickCommand{
target: NewName(args[0]),
nick: NewName(args[1]),
}, nil
}
*/

File diff suppressed because it is too large Load Diff

View File

@ -9,15 +9,16 @@ import (
"runtime/debug" "runtime/debug"
"runtime/pprof" "runtime/pprof"
"time" "time"
"github.com/DanielOaks/girc-go/ircmsg"
) )
func (msg *DebugCommand) HandleServer(server *Server) { func debugHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
client := msg.Client()
if !client.flags[Operator] { if !client.flags[Operator] {
return return
} }
switch msg.subCommand { switch msg.Params[0] {
case "GCSTATS": case "GCSTATS":
stats := debug.GCStats{ stats := debug.GCStats{
Pause: make([]time.Duration, 10), Pause: make([]time.Duration, 10),

View File

@ -122,6 +122,7 @@ var (
// commands // commands
// //
/*
func (m *ModeCommand) HandleServer(s *Server) { func (m *ModeCommand) HandleServer(s *Server) {
client := m.Client() client := m.Client()
target := s.clients.Get(m.nickname) target := s.clients.Get(m.nickname)
@ -185,3 +186,4 @@ func (msg *ChannelModeCommand) HandleServer(server *Server) {
channel.Mode(client, msg.changes) channel.Mode(client, msg.changes)
} }
*/

View File

@ -4,6 +4,7 @@
package irc package irc
/*
type NickCommand struct { type NickCommand struct {
BaseCommand BaseCommand
nickname Name nickname Name
@ -98,3 +99,4 @@ func (msg *OperNickCommand) HandleServer(server *Server) {
target.ChangeNickname(msg.nick) target.ChangeNickname(msg.nick)
} }
*/

View File

@ -15,21 +15,14 @@ import (
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
"github.com/DanielOaks/girc-go/ircmsg"
) )
type ServerCommand interface {
Command
HandleServer(*Server)
}
type RegServerCommand interface {
Command
HandleRegServer(*Server)
}
type Server struct { type Server struct {
channels ChannelNameMap channels ChannelNameMap
clients *ClientLookupSet clients *ClientLookupSet
@ -217,6 +210,7 @@ func (server *Server) processCommand(cmd Command) {
func (server *Server) Shutdown() { func (server *Server) Shutdown() {
server.db.Close() server.db.Close()
for _, client := range server.clients.byNick { for _, client := range server.clients.byNick {
client.Send("notice")
client.Reply(RplNotice(server, client, "shutting down")) client.Reply(RplNotice(server, client, "shutting down"))
} }
} }
@ -331,6 +325,7 @@ func (s *Server) tryRegister(c *Client) {
return return
} }
c.Send("Intro to the network")
c.Register() c.Register()
c.RplWelcome() c.RplWelcome()
c.RplYourHost() c.RplYourHost()
@ -342,14 +337,17 @@ func (s *Server) tryRegister(c *Client) {
func (server *Server) MOTD(client *Client) { func (server *Server) MOTD(client *Client) {
if len(server.motdLines) < 1 { if len(server.motdLines) < 1 {
c.Send("send")
client.ErrNoMOTD() client.ErrNoMOTD()
return return
} }
client.RplMOTDStart() client.RplMOTDStart()
for _, line := range server.motdLines { for _, line := range server.motdLines {
c.Send("send")
client.RplMOTD(line) client.RplMOTD(line)
} }
c.Send("send")
client.RplMOTDEnd() client.RplMOTDEnd()
} }
@ -365,103 +363,132 @@ func (s *Server) Nick() Name {
return s.Id() return s.Id()
} }
func (server *Server) Reply(target *Client, message string) {
target.Reply(RplPrivMsg(server, target, NewText(message)))
}
func (server *Server) Replyf(target *Client, format string, args ...interface{}) {
server.Reply(target, fmt.Sprintf(format, args...))
}
// //
// registration commands // registration commands
// //
func (msg *PassCommand) HandleRegServer(server *Server) { // PASS <password>
client := msg.Client() func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if msg.err != nil { if client.Registered {
client.ErrPasswdMismatch() client.Send("send")
client.ErrAlreadyRegistered()
return false
}
// check the provided password
logger.Fatal("Implement PASS command")
password := []byte(args[0])
if ComparePassword(server.password, password) != nil {
logger.Fatal("SEND BACK REJECTION")
client.Quit("bad password") client.Quit("bad password")
return return true
} }
client.authorized = true client.authorized = true
return false
} }
func (msg *ProxyCommand) HandleRegServer(server *Server) { // PROXY TCP4/6 SOURCEIP DESTIP SOURCEPORT DESTPORT
client := msg.Client() // http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
func proxyHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
clientAddress := IPString(client.socket.conn.RemoteAddr()).String() clientAddress := IPString(client.socket.conn.RemoteAddr()).String()
clientHostname := client.hostname.String() clientHostname := client.hostname.String()
for _, address := range server.proxyAllowedFrom { for _, address := range server.proxyAllowedFrom {
if clientHostname == address || clientAddress == address { if clientHostname == address || clientAddress == address {
client.hostname = msg.hostname client.hostname = LookupHostname(NewName(msg.Params[1]))
return return false
} }
} }
client.Quit("PROXY command is not usable from your address") client.Quit("PROXY command is not usable from your address")
return true
}
// USER <username> * 0 <realname>
func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if client.Registered {
client.Send("send")
client.ErrAlreadyRegistered()
return false
} }
func (msg *UserCommand) HandleRegServer(server *Server) {
client := msg.Client()
if !client.authorized { if !client.authorized {
client.ErrPasswdMismatch()
client.Quit("bad password") client.Quit("bad password")
return return true
}
if client.username != "" && client.realname != "" {
return false
} }
// set user info and log client in // set user info and log client in
server.clients.Remove(client)
//TODO(dan): Could there be a race condition here with adding/removing the client? //TODO(dan): Could there be a race condition here with adding/removing the client?
client.username, client.realname = msg.username, msg.realname //TODO(dan): we should do something like server.clients.Replace(client) instead
// we do it this way to ONLY replace what hasn't already been set
server.clients.Remove(client)
if client.username != "" {
client.username = msg.username
}
if client.realname != "" {
client.realname = msg.realname
}
server.clients.Add(client) server.clients.Add(client)
server.tryRegister(client) server.tryRegister(client)
} }
func (msg *QuitCommand) HandleRegServer(server *Server) { // QUIT [<reason>]
msg.Client().Quit(msg.message) func quitHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
reason := "Quit"
if len(msg.Params) > 0 {
reason += ": " + msg.Params[0]
}
client.Quit(msg.message)
return true
} }
// //
// normal commands // normal commands
// //
func (m *PassCommand) HandleServer(s *Server) { // PING <server1> [<server2>]
m.Client().ErrAlreadyRegistered() func pingHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
// client.Socket.Send(response here)
return true
} }
func (m *PingCommand) HandleServer(s *Server) { // PONG <server> [ <server2> ]
client := m.Client() func pongHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
client.Reply(RplPong(client, m.server.Text())) //TODO(dan): update client idle timer from this
//TODO(dan): use this to affect how often we send pings
return true
} }
func (m *PongCommand) HandleServer(s *Server) { // JOIN <channel>{,<channel>} [<key>{,<key>}]
// no-op // JOIN 0
} func joinHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
// handle JOIN 0
func (m *UserCommand) HandleServer(s *Server) { if msg.Params[0] == "0" {
m.Client().ErrAlreadyRegistered()
}
func (msg *QuitCommand) HandleServer(server *Server) {
msg.Client().Quit(msg.message)
}
func (m *JoinCommand) HandleServer(s *Server) {
client := m.Client()
if m.zero {
for channel := range client.channels { for channel := range client.channels {
channel.Part(client, client.Nick().Text()) channel.Part(client, client.Nick().Text())
} }
return return false
} }
for name, key := range m.channels { // handle regular JOINs
channels := strings.Split(msg.Params[0], ",")
var keys []string
if len(msg.Params) > 1 {
keys = strings.Split(msg.Params[1], ",")
}
for i, name := range channels {
if !name.IsChannel() { if !name.IsChannel() {
client.ErrNoSuchChannel(name) log.Fatal("Implement ErrNoSuchChannel")
continue continue
} }
@ -469,17 +496,29 @@ func (m *JoinCommand) HandleServer(s *Server) {
if channel == nil { if channel == nil {
channel = NewChannel(s, name, true) channel = NewChannel(s, name, true)
} }
var key string
if len(keys) > i {
key = keys[i]
}
channel.Join(client, key) channel.Join(client, key)
} }
} }
func (m *PartCommand) HandleServer(server *Server) { // PART <channel>{,<channel>} [<reason>]
client := m.Client() func partHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
for _, chname := range m.channels { channels := strings.Split(msg.Params[0], ",")
var reason string //TODO(dan): should this be the user's nickname instead of empty?
if len(msg.Params) > 1 {
reason = msg.Params[1]
}
for _, chname := range channels {
channel := server.channels.Get(chname) channel := server.channels.Get(chname)
if channel == nil { if channel == nil {
m.Client().ErrNoSuchChannel(chname) log.Fatal("Implement ErrNoSuchChannel")
continue continue
} }
@ -487,42 +526,47 @@ func (m *PartCommand) HandleServer(server *Server) {
} }
} }
func (msg *TopicCommand) HandleServer(server *Server) { // TOPIC <channel> [<topic>]
client := msg.Client() func topicHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
channel := server.channels.Get(msg.channel) channel := server.channels.Get(msg.Params[0])
if channel == nil { if channel == nil {
client.ErrNoSuchChannel(msg.channel) log.Fatal("Implement ErrNoSuchChannel")
return return
} }
if msg.setTopic { if len(msg.Params) > 1 {
channel.SetTopic(client, msg.topic) channel.SetTopic(client, msg.Params[1])
} else { } else {
channel.GetTopic(client) channel.GetTopic(client)
} }
} }
func (msg *PrivMsgCommand) HandleServer(server *Server) { // PRIVMSG <target>{,<target>} <message>
client := msg.Client() func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if msg.target.IsChannel() { targets := strings.Split(msg.Params[0], ",")
channel := server.channels.Get(msg.target) message := msg.Params[1]
for _, target := range targets {
if target.IsChannel() {
channel := server.channels.Get(target)
if channel == nil { if channel == nil {
client.ErrNoSuchChannel(msg.target) client.Send("send")
client.ErrNoSuchChannel(target)
continue
}
channel.PrivMsg(client, message)
} else {
user := server.clients.Get(target)
if user == nil {
client.Send("send")
client.ErrNoSuchNick(target)
return return
} }
user.Send("content here")
channel.PrivMsg(client, msg.message) if user.flags[Away] {
return client.Send("target is AWAY")
} }
target := server.clients.Get(msg.target)
if target == nil {
client.ErrNoSuchNick(msg.target)
return
} }
target.Reply(RplPrivMsg(client, target, msg.message))
if target.flags[Away] {
client.RplAway(target)
} }
} }
@ -541,19 +585,29 @@ func (client *Client) WhoisChannelsNames(target *Client) []string {
return chstrs return chstrs
} }
func (m *WhoisCommand) HandleServer(server *Server) { // WHOIS [ <target> ] <mask> *( "," <mask> )
client := m.Client() func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
var masks string
var target string
if len(msg.Params) > 1 {
target = msg.Params[0]
masks = msg.Params[1]
} else {
masks = msg.Params[0]
}
// TODO implement target query // TODO implement target query
for _, mask := range masks {
for _, mask := range m.masks {
matches := server.clients.FindAll(mask) matches := server.clients.FindAll(mask)
if len(matches) == 0 { if len(matches) == 0 {
client.ErrNoSuchNick(mask) client.ErrNoSuchNick(mask)
client.Send("NOSUCHNICK")
continue continue
} }
for mclient := range matches { for mclient := range matches {
client.RplWhois(mclient) client.RplWhois(mclient)
client.Send("WHOIS")
} }
} }
} }
@ -561,15 +615,27 @@ func (m *WhoisCommand) HandleServer(server *Server) {
func whoChannel(client *Client, channel *Channel, friends ClientSet) { func whoChannel(client *Client, channel *Channel, friends ClientSet) {
for member := range channel.members { for member := range channel.members {
if !client.flags[Invisible] || friends[client] { if !client.flags[Invisible] || friends[client] {
client.Send("send")
client.RplWhoReply(channel, member) client.RplWhoReply(channel, member)
} }
} }
} }
func (msg *WhoCommand) HandleServer(server *Server) { // WHO [ <mask> [ "o" ] ]
client := msg.Client() func whoHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
friends := client.Friends() friends := client.Friends()
mask := msg.mask
var mask string
if len(msg.Params) > 0 {
mask = NewName(msg.Params[0])
}
//TODO(dan): is this used and would I put this param in the Modern doc?
// if not, can we remove it?
var operatorOnly bool
if len(msg.Params) > 1 && msr.Params[1] == "o" {
operatorOnly = true
}
if mask == "" { if mask == "" {
for _, channel := range server.channels { for _, channel := range server.channels {
@ -584,101 +650,161 @@ func (msg *WhoCommand) HandleServer(server *Server) {
} else { } else {
for mclient := range server.clients.FindAll(mask) { for mclient := range server.clients.FindAll(mask) {
client.RplWhoReply(nil, mclient) client.RplWhoReply(nil, mclient)
client.Send("REPLY")
} }
} }
client.RplEndOfWho(mask) client.RplEndOfWho(mask)
client.Send("ENDOFWHO")
} }
func (msg *OperCommand) HandleServer(server *Server) { // OPER <name> <password>
client := msg.Client() func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
name = NewName(msg.Params[0])
hash = server.operators[name]
password = []byte(msg.Params[1])
if (msg.hash == nil) || (msg.err != nil) { err = ComparePassword(hash, password)
if (hash == nil) || (err != nil) {
client.ErrPasswdMismatch() client.ErrPasswdMismatch()
return client.Send("PASSWDBAD")
return true
} }
//TODO(dan): Split this into client.makeOper() ??
client.flags[Operator] = true client.flags[Operator] = true
client.RplYoureOper() client.RplYoureOper()
client.Send("YOUROPER")
client.Reply(RplModeChanges(client, client, ModeChanges{&ModeChange{ client.Reply(RplModeChanges(client, client, ModeChanges{&ModeChange{
mode: Operator, mode: Operator,
op: Add, op: Add,
}})) }}))
client.Send("OPERMODECHANGE")
} }
func (msg *AwayCommand) HandleServer(server *Server) { // AWAY [<message>]
client := msg.Client() func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if len(msg.text) > 0 { var isAway bool
var text string
if len(msg.Params) > 0 {
isAway = True
text = NewText(msg.Params[0])
}
if isAway {
client.flags[Away] = true client.flags[Away] = true
} else { } else {
delete(client.flags, Away) delete(client.flags, Away)
} }
client.awayMessage = msg.text client.awayMessage = text
var op ModeOp var op ModeOp
if client.flags[Away] { if client.flags[Away] {
op = Add op = Add
client.Send("imaway")
client.RplNowAway() client.RplNowAway()
} else { } else {
op = Remove op = Remove
client.Send("unaway")
client.RplUnAway() client.RplUnAway()
} }
client.Send("mode changes I guess?")
client.Reply(RplModeChanges(client, client, ModeChanges{&ModeChange{ client.Reply(RplModeChanges(client, client, ModeChanges{&ModeChange{
mode: Away, mode: Away,
op: op, op: op,
}})) }}))
} }
func (msg *IsOnCommand) HandleServer(server *Server) { // ISON <nick>{ <nick>}
client := msg.Client() func isonHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
var nicks = NewNames(msg.Params)
ison := make([]string, 0) ison := make([]string, 0)
for _, nick := range msg.nicks { for _, nick := range nicks {
if iclient := server.clients.Get(nick); iclient != nil { if iclient := server.clients.Get(nick); iclient != nil {
ison = append(ison, iclient.Nick().String()) ison = append(ison, iclient.Nick().String())
} }
} }
client.Send("ISON")
client.RplIsOn(ison) client.RplIsOn(ison)
} }
func (msg *MOTDCommand) HandleServer(server *Server) { // MOTD [<target>]
func motdHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
//TODO(dan): hook this up when we have multiple servers I guess???
var target string
if len(msg.Params) > 0 {
target = NewName(msg.Params[0])
}
client.Send("MOTD")
server.MOTD(msg.Client()) server.MOTD(msg.Client())
} }
func (msg *NoticeCommand) HandleServer(server *Server) { // NOTICE <target>{,<target>} <message>
client := msg.Client() func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if msg.target.IsChannel() { targetName := NewName(msg.Params[0])
channel := server.channels.Get(msg.target) message := NewText(msg.Params[1])
if targetName.IsChannel() {
channel := server.channels.Get(targetName)
if channel == nil { if channel == nil {
client.ErrNoSuchChannel(msg.target) client.Send("ERRNOSUCHCHAN")
client.ErrNoSuchChannel(targetName)
return return
} }
channel.Notice(client, msg.message) channel.Notice(client, message)
return return
} }
target := server.clients.Get(msg.target) target := server.clients.Get(targetName)
if target == nil { if target == nil {
client.ErrNoSuchNick(msg.target) client.Send("ERRNOSUCHNICK")
client.ErrNoSuchNick(targetName)
return return
} }
target.Reply(RplNotice(client, target, msg.message)) client.Send("NOTICE")
target.Reply(RplNotice(client, target, message))
} }
func (msg *KickCommand) HandleServer(server *Server) { // KICK <channel>{,<channel>} <user>{,<user>} [<comment>]
client := msg.Client() func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
for chname, nickname := range msg.kicks { channels := NewNames(strings.Split(msg.Params[0], ","))
users := NewNames(strings.Split(msg.Params[1], ","))
if (len(channels) != len(users)) && (len(users) != 1) {
client.Send("NotEnoughArgs??")
return false
// not needed return nil, NotEnoughArgsError
}
kicks := make(map[Name]Name)
for index, channel := range channels {
if len(users) == 1 {
kicks[channel] = users[0]
} else {
kicks[channel] = users[index]
}
}
var comment string
if len(msg.Params) > 2 {
comment = msg.Params[2]
}
for chname, nickname := range kicks {
channel := server.channels.Get(chname) channel := server.channels.Get(chname)
if channel == nil { if channel == nil {
client.ErrNoSuchChannel(chname) client.ErrNoSuchChannel(chname)
client.Send("send")
continue continue
} }
target := server.clients.Get(nickname) target := server.clients.Get(nickname)
if target == nil { if target == nil {
client.ErrNoSuchNick(nickname) client.ErrNoSuchNick(nickname)
client.Send("send")
continue continue
} }
@ -700,128 +826,196 @@ func (msg *KickCommand) HandleServer(server *Server) {
} }
if hasPrivs { if hasPrivs {
channel.Kick(client, target, msg.Comment()) if comment == "" {
channel.Kick(client, target, nickname)
} else {
channel.Kick(client, target, comment)
}
} else { } else {
client.ErrChanOPrivIsNeeded(channel) client.ErrChanOPrivIsNeeded(channel)
client.Send("send")
} }
} }
} }
func (msg *ListCommand) HandleServer(server *Server) { // LIST [<channel>{,<channel>} [<server>]]
client := msg.Client() func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
var channels []Name
if len(args) > 0 {
channels = NewNames(strings.Split(args[0], ","))
}
var target Name
if len(args) > 1 {
target = NewName(args[1])
}
// TODO target server //TODO(dan): target server when we have multiple servers
if msg.target != "" { //TODO(dan): we should continue just fine if it's this current server though
if target != "" {
client.ErrNoSuchServer(msg.target) client.ErrNoSuchServer(msg.target)
client.Send("send")
return return
} }
if len(msg.channels) == 0 { if len(channels) == 0 {
for _, channel := range server.channels { for _, channel := range server.channels {
if !client.flags[Operator] && channel.flags[Secret] { if !client.flags[Operator] && channel.flags[Secret] {
continue continue
} }
client.RplList(channel) client.RplList(channel)
client.Send("send")
} }
} else { } else {
for _, chname := range msg.channels { for _, chname := range channels {
channel := server.channels.Get(chname) channel := server.channels.Get(chname)
if channel == nil || (!client.flags[Operator] && channel.flags[Secret]) { if channel == nil || (!client.flags[Operator] && channel.flags[Secret]) {
client.ErrNoSuchChannel(chname) client.ErrNoSuchChannel(chname)
client.Send("send")
continue continue
} }
client.RplList(channel) client.RplList(channel)
client.Send("send")
} }
} }
client.RplListEnd(server) client.RplListEnd(server)
client.Send("send")
} }
func (msg *NamesCommand) HandleServer(server *Server) { // NAMES [<channel>{,<channel>}]
client := msg.Client() func namesHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if len(server.channels) == 0 { var channels []Name
if len(args) > 0 {
channels = NewNames(strings.Split(args[0], ","))
}
var target Name
if len(args) > 1 {
target = NewName(args[1])
}
if len(channels) == 0 {
for _, channel := range server.channels { for _, channel := range server.channels {
channel.Names(client) channel.Names(client)
} }
return return false
} }
for _, chname := range msg.channels { for _, chname := range channels {
channel := server.channels.Get(chname) channel := server.channels.Get(chname)
if channel == nil { if channel == nil {
client.ErrNoSuchChannel(chname) client.ErrNoSuchChannel(chname)
client.Send("send")
continue continue
} }
channel.Names(client) channel.Names(client)
client.Send("send")
} }
} }
func (msg *VersionCommand) HandleServer(server *Server) { // VERSION [<server>]
client := msg.Client() func versionHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if (msg.target != "") && (msg.target != server.name) { var target Name
client.ErrNoSuchServer(msg.target) if len(args) > 0 {
target = NewName(args[0])
}
if (target != "") && (target != server.name) {
client.ErrNoSuchServer(target)
client.Send("send")
return return
} }
client.RplVersion() client.RplVersion()
client.Send("send")
client.RplISupport() client.RplISupport()
client.Send("send")
} }
func (msg *InviteCommand) HandleServer(server *Server) { // INVITE <nickname> <channel>
client := msg.Client() func inviteHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
nickname := NewName(msg.Params[0])
channelName := NewName(msg.Params[1])
target := server.clients.Get(msg.nickname) target := server.clients.Get(nickname)
if target == nil { if target == nil {
client.ErrNoSuchNick(msg.nickname) client.ErrNoSuchNick(nickname)
client.Send("send")
return return
} }
channel := server.channels.Get(msg.channel) channel := server.channels.Get(channelName)
if channel == nil { if channel == nil {
client.RplInviting(target, msg.channel) client.RplInviting(target, channelName)
target.Reply(RplInviteMsg(client, target, msg.channel)) client.Send("send")
target.Reply(RplInviteMsg(client, target, channelName))
client.Send("send")
return return
} }
channel.Invite(target, client) channel.Invite(target, client)
} }
func (msg *TimeCommand) HandleServer(server *Server) { // TIME [<server>]
client := msg.Client() func timeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if (msg.target != "") && (msg.target != server.name) { var target Name
client.ErrNoSuchServer(msg.target) if len(msg.Params) > 0 {
target = NewName(msg.Params[0])
}
if (target != "") && (target != server.name) {
client.ErrNoSuchServer(target)
client.Send("send")
return return
} }
client.RplTime() client.RplTime()
client.Send("send")
} }
func (msg *KillCommand) HandleServer(server *Server) { // KILL <nickname> <comment>
client := msg.Client() func killHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
nickname := NewName(msg.Params[0])
comment := NewText(msg.Params[1])
if !client.flags[Operator] { if !client.flags[Operator] {
client.ErrNoPrivileges() client.ErrNoPrivileges()
client.Send("send")
return return
} }
target := server.clients.Get(msg.nickname) target := server.clients.Get(nickname)
if target == nil { if target == nil {
client.ErrNoSuchNick(msg.nickname) client.ErrNoSuchNick(nickname)
client.Send("send")
return return
} }
quitMsg := fmt.Sprintf("KILLed by %s: %s", client.Nick(), msg.comment) //TODO(dan): make below format match that from other IRCds
quitMsg := fmt.Sprintf("KILLed by %s: %s", client.Nick(), comment)
target.Quit(NewText(quitMsg)) target.Quit(NewText(quitMsg))
return true
} }
func (msg *WhoWasCommand) HandleServer(server *Server) { // WHOWAS <nickname> [<count> [<server>]]
client := msg.Client() func whowasHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
for _, nickname := range msg.nicknames { nicknames := NewNames(strings.Split(msg.Params[0], ","))
var count int
if len(msg.Params) > 1 {
count, _ = strconv.ParseInt(msg.Params[1], 10, 64)
}
var target Name
if len(msg.Params) > 2 {
target = NewName(msg.Params[2])
}
for _, nickname := range nicknames {
results := server.whoWas.Find(nickname, msg.count) results := server.whoWas.Find(nickname, msg.count)
if len(results) == 0 { if len(results) == 0 {
client.ErrWasNoSuchNick(nickname) client.ErrWasNoSuchNick(nickname)
client.Send("send")
} else { } else {
for _, whoWas := range results { for _, whoWas := range results {
client.RplWhoWasUser(whoWas) client.RplWhoWasUser(whoWas)
client.Send("send")
} }
} }
client.RplEndOfWhoWas(nickname) client.RplEndOfWhoWas(nickname)
client.Send("send")
} }
} }