ca503377dc
Added flag for client that is set when registration is completed. Providing middleware/wrapper for CommandHandlers to check if client is registered.
312 lines
7.5 KiB
Go
312 lines
7.5 KiB
Go
package irc
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/sorcix/irc"
|
|
)
|
|
|
|
// Client represents an IRC Client connection to the server
|
|
type Client struct {
|
|
*irc.Conn
|
|
conn net.Conn
|
|
Nickname string
|
|
Name string
|
|
Host string
|
|
Username string
|
|
RealName string
|
|
|
|
Prefix *irc.Prefix
|
|
|
|
Server *Server
|
|
Authorized bool
|
|
Registered bool
|
|
|
|
idleTimer *time.Timer
|
|
quitTimer *time.Timer
|
|
|
|
AwayMessage string
|
|
|
|
channels map[string]*Channel
|
|
channelMutex sync.RWMutex
|
|
|
|
*UserModeSet
|
|
}
|
|
|
|
func (s *Server) newClient(ircConn *irc.Conn, conn net.Conn) *Client {
|
|
client := &Client{Conn: ircConn, conn: conn, Server: s}
|
|
client.Authorized = len(s.Config.Password) == 0
|
|
client.idleTimer = time.AfterFunc(time.Minute*1, client.quit)
|
|
client.channels = map[string]*Channel{}
|
|
client.UserModeSet = NewUserModeSet()
|
|
return client
|
|
}
|
|
|
|
// Close cleans up the IRC client and closes the connection
|
|
func (c *Client) Close() error {
|
|
c.Server.RemoveClient(c)
|
|
c.Server.RemoveClientNick(c)
|
|
|
|
return c.Conn.Close()
|
|
}
|
|
|
|
// Ping sends an IRC PING command to a client
|
|
func (c *Client) Ping() {
|
|
m := irc.Message{Command: irc.PING, Trailing: c.Server.Config.Name}
|
|
c.Encode(&m)
|
|
}
|
|
|
|
// Pong sends an IRC PONG command to the client
|
|
func (c *Client) Pong() {
|
|
m := irc.Message{Command: irc.PONG, Trailing: c.Server.Config.Name}
|
|
c.Encode(&m)
|
|
}
|
|
|
|
func (c *Client) handleIncoming() {
|
|
c.Server.AddClient(c)
|
|
for {
|
|
message, err := c.Decode()
|
|
if err != nil {
|
|
|
|
_, closedError := err.(*net.OpError)
|
|
if err == io.EOF || err == io.ErrClosedPipe || closedError || strings.Contains(err.Error(), "use of closed network connection") {
|
|
return
|
|
}
|
|
|
|
continue
|
|
}
|
|
if message == nil || message.Len() == 0 {
|
|
continue
|
|
}
|
|
|
|
c.idleTimer.Stop()
|
|
|
|
if !c.Registered { // if client isn't registered don't bother with PINGs
|
|
c.idleTimer = time.AfterFunc(time.Minute*1, c.quit)
|
|
} else {
|
|
c.idleTimer = time.AfterFunc(time.Minute*3, c.idle)
|
|
}
|
|
|
|
if c.quitTimer != nil {
|
|
c.quitTimer.Stop()
|
|
c.quitTimer = nil
|
|
}
|
|
|
|
c.Server.CommandsMux.ServeIRC(message, c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func (c *Client) idle() {
|
|
c.Ping()
|
|
c.quitTimer = time.AfterFunc(time.Minute*3, c.quit)
|
|
}
|
|
|
|
func (c *Client) quit() {
|
|
// Have client leave/part each channel
|
|
for _, channel := range c.GetChannels() {
|
|
channel.Quit(c, "Disconnected")
|
|
}
|
|
c.Quit()
|
|
}
|
|
|
|
// Quit sends the IRC Quit command and closes the connection
|
|
func (c *Client) Quit() {
|
|
m := irc.Message{Prefix: &irc.Prefix{Name: c.Server.Config.Name}, Command: irc.QUIT,
|
|
Params: []string{c.Nickname}}
|
|
|
|
c.Encode(&m)
|
|
c.Close()
|
|
}
|
|
|
|
// Welcome handles initial client connection IRC protocols for a client.
|
|
// Welcome procedure includes IRC WELCOME, Host Info, and MOTD
|
|
func (c *Client) Welcome() {
|
|
|
|
// Have all client info now
|
|
c.Prefix = &irc.Prefix{Name: c.Nickname, User: c.Name, Host: c.Host}
|
|
c.Registered = true
|
|
|
|
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_WELCOME,
|
|
Params: []string{c.Nickname, "Welcome to the Internet Relay Network", c.Prefix.String()}}
|
|
|
|
err := c.Encode(&m)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_YOURHOST,
|
|
Params: []string{c.Nickname, fmt.Sprintf("Your host is %s, running version %s", c.Server.Config.Name, c.Server.Config.Version)}}
|
|
|
|
err = c.Encode(&m)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_CREATED,
|
|
Params: []string{c.Nickname, fmt.Sprintf("This server was created %s", c.Server.created)}}
|
|
|
|
err = c.Encode(&m)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_MYINFO,
|
|
Params: []string{c.Nickname, fmt.Sprintf("%s - Golang IRC server", c.Server.Config.Name)}}
|
|
|
|
err = c.Encode(&m)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Send MOTD
|
|
c.MOTD()
|
|
|
|
}
|
|
|
|
// MOTD returns the Message of the Day of the server to the client
|
|
func (c *Client) MOTD() {
|
|
|
|
if len(c.Server.Config.MOTD) == 0 {
|
|
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.ERR_NOMOTD, Params: []string{c.Nickname}, Trailing: "MOTD File is missing"}
|
|
c.Encode(&m)
|
|
}
|
|
|
|
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_MOTDSTART,
|
|
Params: []string{c.Nickname, fmt.Sprintf("%s - Message of the day", c.Server.Config.Name)}}
|
|
|
|
err := c.Encode(&m)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_MOTD,
|
|
Params: []string{c.Nickname, c.Server.Config.MOTD}}
|
|
|
|
err = c.Encode(&m)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_ENDOFMOTD,
|
|
Params: []string{c.Nickname, "End of MOTD"}}
|
|
|
|
err = c.Encode(&m)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
// AddChannel adds a channel to the client's active list
|
|
func (c *Client) AddChannel(channel *Channel) {
|
|
c.channelMutex.Lock()
|
|
defer c.channelMutex.Unlock()
|
|
c.channels[channel.Name] = channel
|
|
}
|
|
|
|
// RemoveChannel removes a channel to the client's active list
|
|
func (c *Client) RemoveChannel(channel *Channel) {
|
|
c.channelMutex.Lock()
|
|
defer c.channelMutex.Unlock()
|
|
delete(c.channels, channel.Name)
|
|
}
|
|
|
|
// GetChannels gets a list of channels this client is joined to
|
|
func (c *Client) GetChannels() map[string]*Channel {
|
|
return c.channels
|
|
}
|
|
|
|
// UpdateNick updates the clients nicknamae to a new nickname
|
|
func (c *Client) UpdateNick(newNick string) {
|
|
oldNick := c.Nickname
|
|
c.Nickname = newNick
|
|
|
|
c.Server.UpdateClientNick(c, oldNick)
|
|
c.channelMutex.RLock()
|
|
defer c.channelMutex.RUnlock()
|
|
|
|
// Notify all people that should know (people on channels with this client)
|
|
m := irc.Message{Prefix: c.Prefix, Command: irc.NICK, Trailing: c.Nickname}
|
|
notified := map[string]interface{}{} // Just notify people once
|
|
c.Encode(&m)
|
|
notified[c.Nickname] = nil
|
|
|
|
for _, channel := range c.channels {
|
|
|
|
channel.UpdateMemberNick(c, oldNick)
|
|
|
|
for client := range channel.members {
|
|
cl, ok := c.Server.GetClientByNick(client)
|
|
_, alreadyNotified := notified[client]
|
|
if ok && !alreadyNotified {
|
|
cl.Encode(&m)
|
|
notified[client] = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
c.Prefix.Name = newNick
|
|
}
|
|
|
|
// GetVisible returns a map of clients visible to this client
|
|
func (c *Client) GetVisible() map[string]*Client {
|
|
clients := map[string]*Client{}
|
|
for name, client := range c.Server.clientsByNick {
|
|
if client.HasMode(UserModeInvisible) {
|
|
continue
|
|
}
|
|
clients[name] = client
|
|
}
|
|
for _, channel := range c.channels {
|
|
for member := range channel.members {
|
|
tmp, ok := c.Server.GetClientByNick(member)
|
|
if ok {
|
|
clients[member] = tmp
|
|
}
|
|
}
|
|
}
|
|
return clients
|
|
}
|
|
|
|
// SendMessagetoVisible sends a message to all other visible clients
|
|
func (c *Client) SendMessagetoVisible(m *irc.Message) {
|
|
for _, client := range c.GetVisible() {
|
|
client.Encode(m)
|
|
}
|
|
}
|
|
|
|
// Who rmanages responding to the WHO request for all visible clients of this client
|
|
func (c *Client) Who() {
|
|
clients := map[string]*Client{}
|
|
for name, client := range c.Server.clientsByNick {
|
|
if client.HasMode(UserModeInvisible) {
|
|
continue
|
|
}
|
|
msg := whoLine(client, nil, c.Nickname)
|
|
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_WHOREPLY, Params: strings.Fields(msg)}
|
|
c.Encode(&m)
|
|
|
|
clients[name] = client
|
|
}
|
|
for _, channel := range c.channels {
|
|
for member := range channel.members {
|
|
tmp, ok := c.Server.GetClientByNick(member)
|
|
_, alreadySent := clients[member]
|
|
if ok && !alreadySent {
|
|
msg := whoLine(tmp, channel, c.Nickname)
|
|
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_WHOREPLY, Params: strings.Fields(msg)}
|
|
c.Encode(&m)
|
|
clients[member] = tmp
|
|
}
|
|
}
|
|
}
|
|
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_ENDOFWHO, Params: []string{c.Nickname, "*"}, Trailing: "End of WHO list"}
|
|
c.Encode(&m)
|
|
}
|