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