diff --git a/src/irc/client.go b/src/irc/client.go index 3dee9c91..b24b07a1 100644 --- a/src/irc/client.go +++ b/src/irc/client.go @@ -6,17 +6,27 @@ import ( ) type Client struct { - conn net.Conn - hostname string - send chan<- Reply - recv <-chan string + // communication + conn net.Conn + send chan<- Reply + recv <-chan string + // basic info username string realname string + hostname string nick string - registered bool - invisible bool - channels ChannelSet - server *Server + serverPass bool + // modes + away bool + registered bool + invisible bool + wallOps bool + restricted bool + operator bool + localOperator bool + // relations + server *Server + channels ChannelSet } type ClientSet map[*Client]bool diff --git a/src/irc/commands.go b/src/irc/commands.go index 9eabc031..8c1b1141 100644 --- a/src/irc/commands.go +++ b/src/irc/commands.go @@ -13,8 +13,8 @@ type Message interface { } var ( - ErrNotEnoughArgs = errors.New("not enough arguments") - ErrUModeUnknownFlag = errors.New("unknown umode flag") + NotEnoughArgsError = errors.New("not enough arguments") + UModeUnknownFlagError = errors.New("unknown umode flag") ) // unknown @@ -38,7 +38,7 @@ type PingMessage struct { func NewPingMessage(args []string) (Message, error) { if len(args) < 1 { - return nil, ErrNotEnoughArgs + return nil, NotEnoughArgsError } msg := &PingMessage{server: args[0]} if len(args) > 1 { @@ -60,7 +60,7 @@ type PongMessage struct { func NewPongMessage(args []string) (Message, error) { if len(args) < 1 { - return nil, ErrNotEnoughArgs + return nil, NotEnoughArgsError } message := &PongMessage{server1: args[0]} if len(args) > 1 { @@ -73,6 +73,27 @@ func (m *PongMessage) Handle(s *Server, c *Client) { // no-op } +// PASS + +type PassMessage struct { + password string +} + +func NewPassMessage(args []string) (Message, error) { + if len(args) < 1 { + return nil, NotEnoughArgsError + } + return &PassMessage{password: args[0]} +} + +func (m *PassMessage) Handle(s *Server, c *Client) { + if m.password == server.password { + c.serverPass = true + } else { + c.send <- ErrPass + } +} + // NICK type NickMessage struct { @@ -81,7 +102,7 @@ type NickMessage struct { func NewNickMessage(args []string) (Message, error) { if len(args) != 1 { - return nil, ErrNotEnoughArgs + return nil, NotEnoughArgsError } return &NickMessage{args[0]}, nil } @@ -101,7 +122,7 @@ type UserMessage struct { func NewUserMessage(args []string) (Message, error) { if len(args) != 4 { - return nil, ErrNotEnoughArgs + return nil, NotEnoughArgsError } msg := &UserMessage{ user: args[0], @@ -119,7 +140,7 @@ func (m *UserMessage) Handle(s *Server, c *Client) { s.UserLogin(c, m.user, m.realname) } -// QUIT +// QUIT [ ] type QuitMessage struct { message string @@ -137,18 +158,19 @@ func (m *QuitMessage) Handle(s *Server, c *Client) { s.Quit(c, m.message) } -// MODE +// MODE *( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) ) type ModeMessage struct { nickname string modes []string } -var MODE_RE = regexp.MustCompile("^[-+][a-zA-Z]+$") +// mode s is accepted but ignored, like some other modes +var MODE_RE = regexp.MustCompile("^[-+][iwroOs]+$") func NewModeMessage(args []string) (Message, error) { if len(args) < 1 { - return nil, ErrNotEnoughArgs + return nil, NotEnoughArgsError } msg := &ModeMessage{ nickname: args[0], @@ -174,7 +196,7 @@ func (m *ModeMessage) Handle(s *Server, c *Client) { s.ChangeUserMode(c, m.modes) } -// JOIN +// JOIN ( *( "," ) [ *( "," ) ] ) / "0" type JoinMessage struct { channels []string @@ -224,7 +246,7 @@ type PartMessage struct { func NewPartMessage(args []string) (Message, error) { if len(args) < 1 { - return nil, ErrNotEnoughArgs + return nil, NotEnoughArgsError } msg := &PartMessage{channels: strings.Split(args[0], ",")} if len(args) > 1 { @@ -255,7 +277,7 @@ type PrivMsgMessage struct { func NewPrivMsgMessage(args []string) (Message, error) { if len(args) < 2 { - return nil, ErrNotEnoughArgs + return nil, NotEnoughArgsError } return &PrivMsgMessage{ target: args[0], @@ -298,7 +320,7 @@ type TopicMessage struct { func NewTopicMessage(args []string) (Message, error) { if len(args) < 1 { - return nil, ErrNotEnoughArgs + return nil, NotEnoughArgsError } msg := &TopicMessage{channel: args[0]} if len(args) > 1 { @@ -329,7 +351,7 @@ type InviteMessage struct { func NewInviteMessage(args []string) (Message, error) { if len(args) < 2 { - return nil, ErrNotEnoughArgs + return nil, NotEnoughArgsError } return &InviteMessage{ nickname: args[0], @@ -352,3 +374,28 @@ func (m *InviteMessage) Handle(s *Server, c *Client) { channel.Invite(c, invitee) } + +// OPER + +type OperMessage struct { + name string + password string +} + +func NewOperMessage(args []string) Message { + if len(args) < 2 { + return nil, NotEnoughArgsError + } + return &OperMessage{ + name: args[0], + password: args[1], + } +} + +func (m *OperMessage) Handle(s *Server, c *Client) { + if s.operators[m.name] == m.password { + c.send <- RplYoureOper(s) + } else { + c.send <- ErrPasswdMismatch(s) + } +} diff --git a/src/irc/parse.go b/src/irc/parse.go index 131e0f56..b2835a78 100644 --- a/src/irc/parse.go +++ b/src/irc/parse.go @@ -15,12 +15,14 @@ var ( "MODE": NewModeMessage, "NICK": NewNickMessage, "PART": NewPartMessage, + "PASS": NewPassMessage, "PING": NewPingMessage, "PONG": NewPongMessage, "PRIVMSG": NewPrivMsgMessage, "QUIT": NewQuitMessage, "TOPIC": NewTopicMessage, "USER": NewUserMessage, + "OPER": NewOperMessage, } ) diff --git a/src/irc/reply.go b/src/irc/reply.go index 04968d2a..dc155706 100644 --- a/src/irc/reply.go +++ b/src/irc/reply.go @@ -77,7 +77,7 @@ func RplCreated(server *Server) Reply { func RplMyInfo(server *Server) Reply { return NewReply(server, RPL_MYINFO, - fmt.Sprintf("%s %s i ik", server.name, VERSION)) + fmt.Sprintf("%s %s iwroO ik", server.name, VERSION)) } func RplUModeIs(server *Server, client *Client) Reply { @@ -122,6 +122,12 @@ func RplPong(server *Server) Reply { return NewReply(server, RPL_PONG, "") } +// server functions + +func RplYoureOper(server *Server) Reply { + return NewReply(server, RPL_YOUREOPER, ":You are now an IRC operator") +} + // errors func ErrAlreadyRegistered(source Identifier) Reply { @@ -177,3 +183,7 @@ func ErrNoSuchNick(source Identifier, nick string) Reply { return NewReply(source, ERR_NOSUCHNICK, nick+" :No such nick/channel") } + +func ErrPasswdMismatch(server *Server) Reply { + return NewReply(server, ERR_PASSWDMISMATCH, ":Password incorrect") +} diff --git a/src/irc/server.go b/src/irc/server.go index 1cd7deab..90b0fe62 100644 --- a/src/irc/server.go +++ b/src/irc/server.go @@ -7,12 +7,14 @@ import ( ) type Server struct { - hostname string - ctime time.Time - name string - recv chan<- *ClientMessage - nicks map[string]*Client - channels map[string]*Channel + hostname string + ctime time.Time + name string + recv chan<- *ClientMessage + nicks map[string]*Client + channels map[string]*Channel + password string + operators map[string]string } type ClientMessage struct { @@ -68,6 +70,10 @@ func (s *Server) GetOrMakeChannel(name string) *Channel { return channel } +func (s *Server) AddOperator(name string, password string) { + s.operators[name] = password +} + // Send a message to clients of channels fromClient is a member. func (s *Server) SendToInterestedClients(fromClient *Client, reply Reply) { clients := make(map[*Client]bool) @@ -114,7 +120,7 @@ func (s *Server) UserLogin(c *Client, user string, realName string) { } func (s *Server) tryRegister(c *Client) { - if !c.registered && c.HasNick() && c.HasUser() { + if !c.registered && c.HasNick() && c.HasUser() && (s.password == "" || c.serverAuth) { c.registered = true c.send <- RplWelcome(s, c) c.send <- RplYourHost(s, c) @@ -134,10 +140,21 @@ func (s *Server) Quit(c *Client, message string) { func (s *Server) ChangeUserMode(c *Client, modes []string) { for _, mode := range modes { - if mode == "+i" { + switch mode { + case "+i": c.invisible = true - } else if mode == "-i" { + case "-i": c.invisible = false + case "-o": + c.operator = false + case "-O": + c.localOperator = false + case "+r": + c.restricted = true + case "+w": + c.wallOps = true + case "-w": + c.wallOps = false } } c.send <- RplUModeIs(s, c)