e31fe5d24d
When no parameter is passed all channels and global are returned Now prevents invisible users from being revealed
304 lines
7.3 KiB
Go
304 lines
7.3 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
|
|
|
|
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*3, client.idle)
|
|
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()
|
|
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}
|
|
|
|
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)
|
|
}
|