irc/client.go

327 lines
7.9 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)
}
// MakeOper makes this client a server operator
func (c *Client) MakeOper() {
c.AddMode(UserModeOperator)
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.MODE, Params: []string{c.Nickname, "+o"}}
for name, client := range c.Server.clientsByNick {
if name == c.Nickname {
continue
}
client.Encode(&m)
}
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_YOUREOPER, Params: []string{c.Nickname}, Trailing: "You are now an IRC operator"}
c.Encode(&m)
}