Base IRC server started.
Support for NICK, USER, QUIT, PING, and PONG commands added
This commit is contained in:
parent
89428e0e2e
commit
acfc3f2c60
170
client.go
Normal file
170
client.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
package irc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
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, client.idle)
|
||||||
|
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, Params: []string{"JuddBot"}, Trailing: "JuddBot"}
|
||||||
|
c.Encode(&m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pong sends an IRC PONG command to the client
|
||||||
|
func (c *Client) Pong() {
|
||||||
|
m := irc.Message{Command: irc.PONG, Params: []string{"JuddBot"}, Trailing: "JuddBot"}
|
||||||
|
c.Encode(&m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) handleIncoming() {
|
||||||
|
c.server.AddClient(c)
|
||||||
|
for {
|
||||||
|
message, err := c.Decode()
|
||||||
|
if err != nil || message == nil {
|
||||||
|
|
||||||
|
_, closedError := err.(*net.OpError)
|
||||||
|
if err == io.EOF || err == io.ErrClosedPipe || closedError || strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
println("Error decoding incoming message", err.Error())
|
||||||
|
return
|
||||||
|
//continue
|
||||||
|
}
|
||||||
|
//println(message.String())
|
||||||
|
|
||||||
|
c.idleTimer.Stop()
|
||||||
|
c.idleTimer = time.AfterFunc(time.Minute, 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, c.quit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) quit() {
|
||||||
|
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.Username, Host: c.Host}
|
||||||
|
|
||||||
|
m := irc.Message{Prefix: c.server.Prefix, Command: irc.RPL_WELCOME,
|
||||||
|
Params: []string{c.Nickname, c.server.config.Welcome}}
|
||||||
|
|
||||||
|
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", c.server.config.Name)}}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
127
commands.go
Normal file
127
commands.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package irc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/sorcix/irc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommandHandler allows objects implementing this interface to be registered to serve a particular IRC command
|
||||||
|
type CommandHandler interface {
|
||||||
|
ServeIRC(message *irc.Message, client *Client)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandHandlerFunc is a wrapper to to use regular functions as a CommandHandler
|
||||||
|
type CommandHandlerFunc func(message *irc.Message, client *Client)
|
||||||
|
|
||||||
|
// ServeIRC services a given IRC message from the given client
|
||||||
|
func (f CommandHandlerFunc) ServeIRC(message *irc.Message, client *Client) {
|
||||||
|
f(message, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PingHandler is a CommandHandler to respond to IRC PING commands from a client
|
||||||
|
// Implemented according to RFC 1459 4.6.2 and RFC 2812 3.7.2
|
||||||
|
func PingHandler(message *irc.Message, client *Client) {
|
||||||
|
client.Pong()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// PongHandler is a CommandHandler to respond to IRC PONG commands from a client
|
||||||
|
// Implemented according to RFC 1459 4.6.3 and RFC 2812 3.7.3
|
||||||
|
func PongHandler(message *irc.Message, client *Client) {
|
||||||
|
//client.Ping()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuitHandler is a CommandHandler to respond to IRC QUIT commands from a client
|
||||||
|
// Implemented according to RFC 1459 4.1.6 and RFC 2812 3.1.7
|
||||||
|
func QuitHandler(message *irc.Message, client *Client) {
|
||||||
|
|
||||||
|
m := irc.Message{Prefix: client.server.Prefix, Command: irc.ERROR, Trailing: "quit"}
|
||||||
|
|
||||||
|
client.Encode(&m)
|
||||||
|
client.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NickHandler is a CommandHandler to respond to IRC NICK commands from a client
|
||||||
|
// Implemented according to RFC 1459 4.1.2 and RFC 2812 3.1.2
|
||||||
|
func NickHandler(message *irc.Message, client *Client) {
|
||||||
|
|
||||||
|
var m irc.Message
|
||||||
|
name := client.server.config.Name
|
||||||
|
nickname := client.Nickname
|
||||||
|
|
||||||
|
if len(message.Params) == 0 {
|
||||||
|
m = irc.Message{Prefix: &irc.Prefix{Name: name}, Command: irc.ERR_NONICKNAMEGIVEN, Trailing: "No nickname given"}
|
||||||
|
client.Encode(&m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newNickname := message.Params[0]
|
||||||
|
|
||||||
|
_, found := client.server.ClientsByNick[newNickname]
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !client.authorized:
|
||||||
|
m = irc.Message{Prefix: &irc.Prefix{Name: name}, Command: irc.ERR_PASSWDMISMATCH, Params: []string{newNickname}, Trailing: "Password incorrect"}
|
||||||
|
|
||||||
|
case found: // nickname already in use
|
||||||
|
fmt.Println("Nickname already used")
|
||||||
|
m = irc.Message{Prefix: &irc.Prefix{Name: name}, Command: irc.ERR_NICKNAMEINUSE, Params: []string{newNickname}, Trailing: "Nickname is already in use"}
|
||||||
|
|
||||||
|
default:
|
||||||
|
if len(client.Nickname) == 0 && len(client.Username) != 0 { // Client is connected now, show MOTD ...
|
||||||
|
client.Nickname = newNickname
|
||||||
|
client.server.AddClientNick(client)
|
||||||
|
client.Welcome()
|
||||||
|
} else { //change client name
|
||||||
|
client.Nickname = newNickname
|
||||||
|
client.server.UpdateClientNick(client, nickname)
|
||||||
|
//fmt.Println("Updating client name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.Command) != 0 {
|
||||||
|
client.Encode(&m)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserHandler is a CommandHandler to respond to IRC USER commands from a client
|
||||||
|
// Implemented according to RFC 1459 4.1.3 and RFC 2812 3.1.3
|
||||||
|
func UserHandler(message *irc.Message, client *Client) {
|
||||||
|
var m irc.Message
|
||||||
|
serverName := client.server.config.Name
|
||||||
|
//nickname := client.Nickname
|
||||||
|
|
||||||
|
if len(client.Username) != 0 { // Already registered
|
||||||
|
m = irc.Message{Prefix: &irc.Prefix{Name: serverName}, Command: irc.ERR_ALREADYREGISTRED, Trailing: "You may not reregister"}
|
||||||
|
client.Encode(&m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(message.Params) != 3 {
|
||||||
|
m = irc.Message{Prefix: &irc.Prefix{Name: serverName}, Command: irc.ERR_NEEDMOREPARAMS, Trailing: "Not enough parameters"}
|
||||||
|
client.Encode(&m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := message.Params[0]
|
||||||
|
username := message.Params[1]
|
||||||
|
hostname := message.Params[2]
|
||||||
|
realName := message.Trailing
|
||||||
|
|
||||||
|
client.Name = name
|
||||||
|
client.Username = username
|
||||||
|
client.Host = hostname
|
||||||
|
client.RealName = realName
|
||||||
|
if len(m.Command) == 0 && len(client.Nickname) != 0 { // Client has finished connecting
|
||||||
|
client.Welcome()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.Command) != 0 {
|
||||||
|
client.Encode(&m)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
34
mux.go
Normal file
34
mux.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package irc
|
||||||
|
|
||||||
|
import "github.com/sorcix/irc"
|
||||||
|
|
||||||
|
// CommandsMux multiplexes incoming IRC commands
|
||||||
|
type CommandsMux struct {
|
||||||
|
commands map[string]CommandHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommandsMux creates and returns a new CommandsMux
|
||||||
|
func NewCommandsMux() CommandsMux {
|
||||||
|
return CommandsMux{commands: map[string]CommandHandler{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle registers the given CommandHandler for a given IRC command
|
||||||
|
func (c *CommandsMux) Handle(command string, handler CommandHandler) {
|
||||||
|
c.commands[command] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleFunc registers the given handler function for a given IRC command
|
||||||
|
func (c *CommandsMux) HandleFunc(command string, handler CommandHandlerFunc) {
|
||||||
|
c.commands[command] = CommandHandler(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeIRC dispatches the incoming IRC command to the appropriate handler
|
||||||
|
func (c *CommandsMux) ServeIRC(message *irc.Message, client *Client) {
|
||||||
|
h, ok := c.commands[message.Command]
|
||||||
|
if !ok {
|
||||||
|
m := irc.Message{Command: irc.ERR_UNKNOWNCOMMAND}
|
||||||
|
client.Encode(&m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.ServeIRC(message, client)
|
||||||
|
}
|
136
server.go
Normal file
136
server.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package irc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sorcix/irc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server represents an IRC server
|
||||||
|
type Server struct {
|
||||||
|
config ServerConfig
|
||||||
|
|
||||||
|
Clients map[net.Addr]*Client
|
||||||
|
clientMutex sync.RWMutex
|
||||||
|
|
||||||
|
ClientsByNick map[string]*Client
|
||||||
|
clientByNickMutex sync.RWMutex
|
||||||
|
|
||||||
|
Prefix *irc.Prefix
|
||||||
|
CommandsMux CommandsMux
|
||||||
|
created time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerConfig contains configuration data for seeding a server
|
||||||
|
type ServerConfig struct {
|
||||||
|
Name string
|
||||||
|
MOTD string
|
||||||
|
Welcome string
|
||||||
|
TLSConfig *tls.Config
|
||||||
|
Addr string
|
||||||
|
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer creates and returns a new Server based on the provided config
|
||||||
|
func NewServer(config ServerConfig) *Server {
|
||||||
|
s := Server{}
|
||||||
|
s.config = config
|
||||||
|
s.Clients = map[net.Addr]*Client{}
|
||||||
|
s.CommandsMux = NewCommandsMux()
|
||||||
|
s.created = time.Now()
|
||||||
|
s.ClientsByNick = map[string]*Client{}
|
||||||
|
s.Prefix = &irc.Prefix{Name: config.Name}
|
||||||
|
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddClient adds a new Client
|
||||||
|
func (s *Server) AddClient(client *Client) {
|
||||||
|
s.clientMutex.Lock()
|
||||||
|
defer s.clientMutex.Unlock()
|
||||||
|
s.Clients[client.conn.RemoteAddr()] = client
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveClient removes a client
|
||||||
|
func (s *Server) RemoveClient(client *Client) {
|
||||||
|
s.clientMutex.Lock()
|
||||||
|
defer s.clientMutex.Unlock()
|
||||||
|
delete(s.Clients, client.conn.RemoteAddr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClient finds a client by its address and returns it
|
||||||
|
func (s *Server) GetClient(addr net.Addr) *Client {
|
||||||
|
s.clientMutex.RLock()
|
||||||
|
defer s.clientMutex.RUnlock()
|
||||||
|
return s.Clients[addr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddClientNick adds a client based on its nickname
|
||||||
|
func (s *Server) AddClientNick(client *Client) {
|
||||||
|
s.clientByNickMutex.Lock()
|
||||||
|
defer s.clientByNickMutex.Unlock()
|
||||||
|
s.ClientsByNick[client.Nickname] = client
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveClientNick removes a client based on its nickname
|
||||||
|
func (s *Server) RemoveClientNick(client *Client) {
|
||||||
|
s.clientByNickMutex.Lock()
|
||||||
|
defer s.clientByNickMutex.Unlock()
|
||||||
|
delete(s.ClientsByNick, client.Nickname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateClientNick updates the nickname of a client as it is stored by the server
|
||||||
|
func (s *Server) UpdateClientNick(client *Client, oldNick string) {
|
||||||
|
s.clientByNickMutex.Lock()
|
||||||
|
defer s.clientByNickMutex.Unlock()
|
||||||
|
delete(s.ClientsByNick, oldNick)
|
||||||
|
s.ClientsByNick[client.Nickname] = client
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClientByNick returns a client with the corresponding nickname
|
||||||
|
func (s *Server) GetClientByNick(nick string) *Client {
|
||||||
|
s.clientByNickMutex.RLock()
|
||||||
|
defer s.clientByNickMutex.RUnlock()
|
||||||
|
return s.ClientsByNick[nick]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the server listening on the configured port
|
||||||
|
func (s *Server) Start() {
|
||||||
|
var listener net.Listener
|
||||||
|
var err error
|
||||||
|
if s.config.TLSConfig != nil {
|
||||||
|
listener, err = tls.Listen("tcp", s.config.Addr, s.config.TLSConfig)
|
||||||
|
} else {
|
||||||
|
listener, err = net.Listen("tcp", s.config.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error starting listner", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error accepting connection", err.Error())
|
||||||
|
//return
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ircConn := irc.NewConn(conn)
|
||||||
|
client := s.newClient(ircConn, conn)
|
||||||
|
|
||||||
|
defer client.Close()
|
||||||
|
go func() {
|
||||||
|
fmt.Println("Incoming connection from:", conn.RemoteAddr())
|
||||||
|
client.handleIncoming()
|
||||||
|
fmt.Println("Disconnected with:", conn.RemoteAddr())
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user