Separated code more
This commit is contained in:
parent
e13297a24b
commit
1e18fc3c5a
2
Makefile
2
Makefile
@ -1,5 +1,5 @@
|
|||||||
rosella: main.go rosella.go
|
rosella: main.go rosella.go
|
||||||
go build -o ircd main.go rosella.go
|
go build -o ircd main.go rosella.go server.go client.go
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm ircd
|
rm ircd
|
||||||
|
249
client.go
Normal file
249
client.go
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) setNick(nick string) {
|
||||||
|
if c.nick != "" {
|
||||||
|
delete(c.server.clientMap, c.nick)
|
||||||
|
for _, channel := range c.channelMap {
|
||||||
|
delete(channel.clientMap, c.nick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set up new nick
|
||||||
|
oldNick := c.nick
|
||||||
|
c.nick = nick
|
||||||
|
c.server.clientMap[c.nick] = c
|
||||||
|
|
||||||
|
clients := make([]string, 0, 100)
|
||||||
|
|
||||||
|
for _, channel := range c.channelMap {
|
||||||
|
channel.clientMap[c.nick] = c
|
||||||
|
|
||||||
|
//Collect list of client nicks who can see us
|
||||||
|
for client := range channel.clientMap {
|
||||||
|
clients = append(clients, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//By sorting the nicks and skipping duplicates we send each client one message
|
||||||
|
sort.Strings(clients)
|
||||||
|
prevNick := ""
|
||||||
|
for _, nick := range clients {
|
||||||
|
if nick == prevNick {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prevNick = nick
|
||||||
|
|
||||||
|
client, exists := c.server.clientMap[nick]
|
||||||
|
if exists {
|
||||||
|
client.reply(rplNickChange, oldNick, c.nick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) joinChannel(channelName string) {
|
||||||
|
channel, exists := c.server.channelMap[channelName]
|
||||||
|
if exists == false {
|
||||||
|
channel = &Channel{name: channelName,
|
||||||
|
topic: "",
|
||||||
|
clientMap: make(map[string]*Client)}
|
||||||
|
c.server.channelMap[channelName] = channel
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.clientMap[c.nick] = c
|
||||||
|
c.channelMap[channelName] = channel
|
||||||
|
|
||||||
|
for _, client := range channel.clientMap {
|
||||||
|
client.reply(rplJoin, c.nick, channelName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if channel.topic != "" {
|
||||||
|
c.reply(rplTopic, channelName, channel.topic)
|
||||||
|
} else {
|
||||||
|
c.reply(rplNoTopic, channelName)
|
||||||
|
}
|
||||||
|
|
||||||
|
nicks := make([]string, 0, 100)
|
||||||
|
for nick := range channel.clientMap {
|
||||||
|
nicks = append(nicks, nick)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.reply(rplNames, channelName, strings.Join(nicks, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) partChannel(channelName string) {
|
||||||
|
channel, exists := c.server.channelMap[channelName]
|
||||||
|
if exists == false {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Notify clients of the part
|
||||||
|
for _, client := range channel.clientMap {
|
||||||
|
client.reply(rplPart, c.nick, channelName)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(channel.clientMap, c.nick)
|
||||||
|
delete(c.channelMap, channelName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) disconnect() {
|
||||||
|
c.connected = false
|
||||||
|
c.signalChan <- signalStop
|
||||||
|
}
|
||||||
|
|
||||||
|
//Send a reply to a user with the code specified
|
||||||
|
func (c *Client) reply(code int, args ...string) {
|
||||||
|
if c.connected == false {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch code {
|
||||||
|
case rplWelcome:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 001 %s :Welcome to %s", c.server.name, c.nick, c.server.name)
|
||||||
|
case rplJoin:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s JOIN %s", args[0], args[1])
|
||||||
|
case rplPart:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s PART %s", args[0], args[1])
|
||||||
|
case rplTopic:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 332 %s %s :%s", c.server.name, c.nick, args[0], args[1])
|
||||||
|
case rplNoTopic:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 331 %s %s :No topic is set", c.server.name, c.nick, args[0])
|
||||||
|
case rplNames:
|
||||||
|
//TODO: break long lists up into multiple messages
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 353 %s = %s :%s", c.server.name, c.nick, args[0], args[1])
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 366 %s", c.server.name, c.nick)
|
||||||
|
case rplNickChange:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s NICK %s", args[0], args[1])
|
||||||
|
case rplKill:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s KILL %s A %s", c.server.name, c.nick, args[0])
|
||||||
|
case rplMsg:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s PRIVMSG %s %s", args[0], args[1], args[2])
|
||||||
|
case rplList:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 321 %s", c.server.name, c.nick)
|
||||||
|
for _, listItem := range args {
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 322 %s %s", c.server.name, c.nick, listItem)
|
||||||
|
}
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 323 %s", c.server.name, c.nick)
|
||||||
|
case rplOper:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 381 %s :You are now an operator", c.server.name, c.nick)
|
||||||
|
case errMoreArgs:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 461 %s :Not enough params", c.server.name, c.nick)
|
||||||
|
case errNoNick:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 431 %s :No nickname given", c.server.name, c.nick)
|
||||||
|
case errInvalidNick:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 432 %s %s :Erronenous nickname", c.server.name, c.nick, args[0])
|
||||||
|
case errNickInUse:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 433 %s %s :Nick already in use", c.server.name, c.nick, args[0])
|
||||||
|
case errAlreadyReg:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 462 :You need a valid nick first", c.server.name)
|
||||||
|
case errNoSuchNick:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 401 %s %s :No such nick/channel", c.server.name, c.nick, args[0])
|
||||||
|
case errUnknownCommand:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 421 %s %s :Unknown command", c.server.name, c.nick, args[0])
|
||||||
|
case errNotReg:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 451 :You have not registered", c.server.name)
|
||||||
|
case errPassword:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 464 %s :Error, password incorrect", c.server.name, c.nick)
|
||||||
|
case errNoPriv:
|
||||||
|
c.outputChan <- fmt.Sprintf(":%s 481 %s :Permission denied", c.server.name, c.nick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) clientThread() {
|
||||||
|
defer c.connection.Close()
|
||||||
|
|
||||||
|
readSignalChan := make(chan int, 3)
|
||||||
|
writeSignalChan := make(chan int, 3)
|
||||||
|
writeChan := make(chan string, 100)
|
||||||
|
|
||||||
|
go c.readThread(readSignalChan)
|
||||||
|
go c.writeThread(writeSignalChan, writeChan)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
//Part from all channels
|
||||||
|
for channelName := range c.channelMap {
|
||||||
|
c.partChannel(channelName)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(c.server.clientMap, c.nick)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case signal := <-c.signalChan:
|
||||||
|
if signal == signalStop {
|
||||||
|
readSignalChan <- signalStop
|
||||||
|
writeSignalChan <- signalStop
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case line := <-c.outputChan:
|
||||||
|
select {
|
||||||
|
case writeChan <- line:
|
||||||
|
//It worked
|
||||||
|
default:
|
||||||
|
log.Printf("Dropped a line for client: %q", c.nick)
|
||||||
|
//Do nothing, dropping the line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) readThread(signalChan chan int) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case signal := <-signalChan:
|
||||||
|
if signal == signalStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
c.connection.SetReadDeadline(time.Now().Add(time.Second * 3))
|
||||||
|
buf := make([]byte, 512)
|
||||||
|
ln, err := c.connection.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
c.disconnect()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rawLines := buf[:ln]
|
||||||
|
lines := bytes.Split(rawLines, []byte("\r\n"))
|
||||||
|
for _, line := range lines {
|
||||||
|
if len(line) > 0 {
|
||||||
|
c.server.eventChan <- Event{client: c, input: string(line)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) writeThread(signalChan chan int, outputChan chan string) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case signal := <-signalChan:
|
||||||
|
if signal == signalStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case output := <-outputChan:
|
||||||
|
line := []byte(fmt.Sprintf("%s\r\n", output))
|
||||||
|
|
||||||
|
c.connection.SetWriteDeadline(time.Now().Add(time.Second * 30))
|
||||||
|
_, err := c.connection.Write(line)
|
||||||
|
if err != nil {
|
||||||
|
c.disconnect()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
560
rosella.go
560
rosella.go
@ -1,17 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "net"
|
||||||
"bytes"
|
|
||||||
"crypto/sha1"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
eventChan chan Event
|
eventChan chan Event
|
||||||
@ -34,17 +23,17 @@ type Client struct {
|
|||||||
channelMap map[string]*Channel
|
channelMap map[string]*Channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
client *Client
|
||||||
|
input string
|
||||||
|
}
|
||||||
|
|
||||||
type Channel struct {
|
type Channel struct {
|
||||||
name string
|
name string
|
||||||
topic string
|
topic string
|
||||||
clientMap map[string]*Client
|
clientMap map[string]*Client
|
||||||
}
|
}
|
||||||
|
|
||||||
type Event struct {
|
|
||||||
client *Client
|
|
||||||
input string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
signalStop int = iota
|
signalStop int = iota
|
||||||
)
|
)
|
||||||
@ -72,540 +61,3 @@ const (
|
|||||||
errPassword
|
errPassword
|
||||||
errNoPriv
|
errNoPriv
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
nickRegexp = regexp.MustCompile(`^[a-zA-Z\[\]_^{|}][a-zA-Z0-9\[\]_^{|}]*$`)
|
|
||||||
channelRegexp = regexp.MustCompile(`^#[a-z0-9_\-]+$`)
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewServer() *Server {
|
|
||||||
return &Server{eventChan: make(chan Event),
|
|
||||||
name: "rosella",
|
|
||||||
clientMap: make(map[string]*Client),
|
|
||||||
channelMap: make(map[string]*Channel),
|
|
||||||
operatorMap: make(map[string]string)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Run() {
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
event := <-s.eventChan
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
log.Printf("Recovered from panic()")
|
|
||||||
log.Printf("%s sent %q", event.client.nick, event.input)
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
s.handleEvent(<-s.eventChan)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) HandleConnection(conn net.Conn) {
|
|
||||||
|
|
||||||
client := &Client{server: s,
|
|
||||||
connection: conn,
|
|
||||||
outputChan: make(chan string),
|
|
||||||
signalChan: make(chan int, 3),
|
|
||||||
channelMap: make(map[string]*Channel),
|
|
||||||
connected: true}
|
|
||||||
|
|
||||||
go client.clientThread()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleEvent(e Event) {
|
|
||||||
fields := strings.Fields(e.input)
|
|
||||||
|
|
||||||
if len(fields) < 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(fields[0], ":") {
|
|
||||||
fields = fields[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
command := strings.ToUpper(fields[0])
|
|
||||||
args := fields[1:]
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case command == "NICK":
|
|
||||||
if len(args) < 1 {
|
|
||||||
e.client.reply(errNoNick)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
newNick := args[0]
|
|
||||||
|
|
||||||
//Check newNick is of valid formatting (regex)
|
|
||||||
if nickRegexp.MatchString(newNick) == false {
|
|
||||||
e.client.reply(errInvalidNick, newNick)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, exists := s.clientMap[newNick]; exists {
|
|
||||||
e.client.reply(errNickInUse, newNick)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//Protect the server name from being used
|
|
||||||
if newNick == s.name {
|
|
||||||
e.client.reply(errNickInUse, newNick)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
e.client.setNick(newNick)
|
|
||||||
|
|
||||||
case command == "USER":
|
|
||||||
if e.client.nick == "" {
|
|
||||||
e.client.reply(rplKill, "Your nickname is already being used")
|
|
||||||
e.client.disconnect()
|
|
||||||
} else {
|
|
||||||
e.client.reply(rplWelcome)
|
|
||||||
e.client.registered = true
|
|
||||||
}
|
|
||||||
|
|
||||||
case command == "JOIN":
|
|
||||||
if e.client.registered == false {
|
|
||||||
e.client.reply(errNotReg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) < 1 {
|
|
||||||
e.client.reply(errMoreArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if args[0] == "0" {
|
|
||||||
//Quit all channels
|
|
||||||
for channel := range e.client.channelMap {
|
|
||||||
e.client.partChannel(channel)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channels := strings.Split(args[0], ",")
|
|
||||||
for _, channel := range channels {
|
|
||||||
//Join the channel if it's valid
|
|
||||||
if channelRegexp.Match([]byte(channel)) {
|
|
||||||
e.client.joinChannel(channel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case command == "PART":
|
|
||||||
if e.client.registered == false {
|
|
||||||
e.client.reply(errNotReg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) < 1 {
|
|
||||||
e.client.reply(errMoreArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channels := strings.Split(args[0], ",")
|
|
||||||
for _, channel := range channels {
|
|
||||||
//Part the channel if it's valid
|
|
||||||
if channelRegexp.Match([]byte(channel)) {
|
|
||||||
e.client.partChannel(channel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case command == "PRIVMSG":
|
|
||||||
if e.client.registered == false {
|
|
||||||
e.client.reply(errNotReg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) < 2 {
|
|
||||||
e.client.reply(errMoreArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
message := strings.Join(args[1:], " ")
|
|
||||||
|
|
||||||
channel, chanExists := s.channelMap[args[0]]
|
|
||||||
client, clientExists := s.clientMap[args[0]]
|
|
||||||
|
|
||||||
if chanExists {
|
|
||||||
for _, c := range channel.clientMap {
|
|
||||||
if c != e.client {
|
|
||||||
c.reply(rplMsg, e.client.nick, args[0], message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if clientExists {
|
|
||||||
client.reply(rplMsg, e.client.nick, client.nick, message)
|
|
||||||
} else {
|
|
||||||
e.client.reply(errNoSuchNick, args[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
case command == "QUIT":
|
|
||||||
if e.client.registered == false {
|
|
||||||
e.client.reply(errNotReg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
e.client.disconnect()
|
|
||||||
|
|
||||||
case command == "TOPIC":
|
|
||||||
if e.client.registered == false {
|
|
||||||
e.client.reply(errNotReg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) < 1 {
|
|
||||||
e.client.reply(errMoreArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channel, exists := s.channelMap[args[0]]
|
|
||||||
if exists == false {
|
|
||||||
e.client.reply(errNoSuchNick, args[0])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channelName := args[0]
|
|
||||||
|
|
||||||
if len(args) == 1 {
|
|
||||||
e.client.reply(rplTopic, channelName, channel.topic)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if args[1] == ":" {
|
|
||||||
channel.topic = ""
|
|
||||||
for _, client := range channel.clientMap {
|
|
||||||
client.reply(rplNoTopic, channelName)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
topic := strings.Join(args[1:], " ")
|
|
||||||
topic = strings.TrimPrefix(topic, ":")
|
|
||||||
channel.topic = topic
|
|
||||||
|
|
||||||
for _, client := range channel.clientMap {
|
|
||||||
client.reply(rplTopic, channelName, channel.topic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case command == "LIST":
|
|
||||||
if e.client.registered == false {
|
|
||||||
e.client.reply(errNotReg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) == 0 {
|
|
||||||
chanList := make([]string, 0, len(s.channelMap))
|
|
||||||
|
|
||||||
for channelName, channel := range s.channelMap {
|
|
||||||
listItem := fmt.Sprintf("%s %d :%s", channelName, len(channel.clientMap), channel.topic)
|
|
||||||
chanList = append(chanList, listItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
e.client.reply(rplList, chanList...)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
channels := strings.Split(args[0], ",")
|
|
||||||
chanList := make([]string, 0, len(channels))
|
|
||||||
|
|
||||||
for _, channelName := range channels {
|
|
||||||
if channel, exists := s.channelMap[channelName]; exists {
|
|
||||||
listItem := fmt.Sprintf("%s %d :%s", channelName, len(channel.clientMap), channel.topic)
|
|
||||||
chanList = append(chanList, listItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.client.reply(rplList, chanList...)
|
|
||||||
}
|
|
||||||
case command == "OPER":
|
|
||||||
if e.client.registered == false {
|
|
||||||
e.client.reply(errNotReg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) < 2 {
|
|
||||||
e.client.reply(errMoreArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
username := args[0]
|
|
||||||
password := args[1]
|
|
||||||
|
|
||||||
if hashedPassword, exists := s.operatorMap[username]; exists {
|
|
||||||
h := sha1.New()
|
|
||||||
io.WriteString(h, password)
|
|
||||||
pass := fmt.Sprintf("%x", h.Sum(nil))
|
|
||||||
if hashedPassword == pass {
|
|
||||||
e.client.operator = true
|
|
||||||
e.client.reply(rplOper)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e.client.reply(errPassword)
|
|
||||||
|
|
||||||
case command == "KILL":
|
|
||||||
if e.client.registered == false {
|
|
||||||
e.client.reply(errNotReg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.client.operator == false {
|
|
||||||
e.client.reply(errNoPriv)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) < 1 {
|
|
||||||
e.client.reply(errMoreArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
nick := args[0]
|
|
||||||
|
|
||||||
if client, exists := s.clientMap[nick]; exists {
|
|
||||||
client.reply(rplKill, "An operator has disconnected you.")
|
|
||||||
client.disconnect()
|
|
||||||
} else {
|
|
||||||
e.client.reply(errNoSuchNick, nick)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
e.client.reply(errUnknownCommand, command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) joinChannel(channelName string) {
|
|
||||||
channel, exists := c.server.channelMap[channelName]
|
|
||||||
if exists == false {
|
|
||||||
channel = &Channel{name: channelName,
|
|
||||||
topic: "",
|
|
||||||
clientMap: make(map[string]*Client)}
|
|
||||||
c.server.channelMap[channelName] = channel
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.clientMap[c.nick] = c
|
|
||||||
c.channelMap[channelName] = channel
|
|
||||||
|
|
||||||
for _, client := range channel.clientMap {
|
|
||||||
client.reply(rplJoin, c.nick, channelName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if channel.topic != "" {
|
|
||||||
c.reply(rplTopic, channelName, channel.topic)
|
|
||||||
} else {
|
|
||||||
c.reply(rplNoTopic, channelName)
|
|
||||||
}
|
|
||||||
|
|
||||||
nicks := make([]string, 0, 100)
|
|
||||||
for nick := range channel.clientMap {
|
|
||||||
nicks = append(nicks, nick)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.reply(rplNames, channelName, strings.Join(nicks, " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) partChannel(channelName string) {
|
|
||||||
channel, exists := c.server.channelMap[channelName]
|
|
||||||
if exists == false {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//Notify clients of the part
|
|
||||||
for _, client := range channel.clientMap {
|
|
||||||
client.reply(rplPart, c.nick, channelName)
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(channel.clientMap, c.nick)
|
|
||||||
delete(c.channelMap, channelName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) clientThread() {
|
|
||||||
defer c.connection.Close()
|
|
||||||
|
|
||||||
readSignalChan := make(chan int, 3)
|
|
||||||
writeSignalChan := make(chan int, 3)
|
|
||||||
writeChan := make(chan string, 100)
|
|
||||||
|
|
||||||
go c.readThread(readSignalChan)
|
|
||||||
go c.writeThread(writeSignalChan, writeChan)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
//Part from all channels
|
|
||||||
for channelName := range c.channelMap {
|
|
||||||
c.partChannel(channelName)
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(c.server.clientMap, c.nick)
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case signal := <-c.signalChan:
|
|
||||||
if signal == signalStop {
|
|
||||||
readSignalChan <- signalStop
|
|
||||||
writeSignalChan <- signalStop
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case line := <-c.outputChan:
|
|
||||||
select {
|
|
||||||
case writeChan <- line:
|
|
||||||
//It worked
|
|
||||||
default:
|
|
||||||
log.Printf("Dropped a line for client: %q", c.nick)
|
|
||||||
//Do nothing, dropping the line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) readThread(signalChan chan int) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case signal := <-signalChan:
|
|
||||||
if signal == signalStop {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
c.connection.SetReadDeadline(time.Now().Add(time.Second * 3))
|
|
||||||
buf := make([]byte, 512)
|
|
||||||
ln, err := c.connection.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
c.disconnect()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rawLines := buf[:ln]
|
|
||||||
lines := bytes.Split(rawLines, []byte("\r\n"))
|
|
||||||
for _, line := range lines {
|
|
||||||
if len(line) > 0 {
|
|
||||||
c.server.eventChan <- Event{client: c, input: string(line)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) writeThread(signalChan chan int, outputChan chan string) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case signal := <-signalChan:
|
|
||||||
if signal == signalStop {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case output := <-outputChan:
|
|
||||||
line := []byte(fmt.Sprintf("%s\r\n", output))
|
|
||||||
|
|
||||||
c.connection.SetWriteDeadline(time.Now().Add(time.Second * 30))
|
|
||||||
_, err := c.connection.Write(line)
|
|
||||||
if err != nil {
|
|
||||||
c.disconnect()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) disconnect() {
|
|
||||||
c.connected = false
|
|
||||||
c.signalChan <- signalStop
|
|
||||||
}
|
|
||||||
|
|
||||||
//Send a reply to a user with the code specified
|
|
||||||
func (c *Client) reply(code int, args ...string) {
|
|
||||||
if c.connected == false {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch code {
|
|
||||||
case rplWelcome:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 001 %s :Welcome to %s", c.server.name, c.nick, c.server.name)
|
|
||||||
case rplJoin:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s JOIN %s", args[0], args[1])
|
|
||||||
case rplPart:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s PART %s", args[0], args[1])
|
|
||||||
case rplTopic:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 332 %s %s :%s", c.server.name, c.nick, args[0], args[1])
|
|
||||||
case rplNoTopic:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 331 %s %s :No topic is set", c.server.name, c.nick, args[0])
|
|
||||||
case rplNames:
|
|
||||||
//TODO: break long lists up into multiple messages
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 353 %s = %s :%s", c.server.name, c.nick, args[0], args[1])
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 366 %s", c.server.name, c.nick)
|
|
||||||
case rplNickChange:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s NICK %s", args[0], args[1])
|
|
||||||
case rplKill:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s KILL %s A %s", c.server.name, c.nick, args[0])
|
|
||||||
case rplMsg:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s PRIVMSG %s %s", args[0], args[1], args[2])
|
|
||||||
case rplList:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 321 %s", c.server.name, c.nick)
|
|
||||||
for _, listItem := range args {
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 322 %s %s", c.server.name, c.nick, listItem)
|
|
||||||
}
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 323 %s", c.server.name, c.nick)
|
|
||||||
case rplOper:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 381 %s :You are now an operator", c.server.name, c.nick)
|
|
||||||
case errMoreArgs:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 461 %s :Not enough params", c.server.name, c.nick)
|
|
||||||
case errNoNick:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 431 %s :No nickname given", c.server.name, c.nick)
|
|
||||||
case errInvalidNick:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 432 %s %s :Erronenous nickname", c.server.name, c.nick, args[0])
|
|
||||||
case errNickInUse:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 433 %s %s :Nick already in use", c.server.name, c.nick, args[0])
|
|
||||||
case errAlreadyReg:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 462 :You need a valid nick first", c.server.name)
|
|
||||||
case errNoSuchNick:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 401 %s %s :No such nick/channel", c.server.name, c.nick, args[0])
|
|
||||||
case errUnknownCommand:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 421 %s %s :Unknown command", c.server.name, c.nick, args[0])
|
|
||||||
case errNotReg:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 451 :You have not registered", c.server.name)
|
|
||||||
case errPassword:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 464 %s :Error, password incorrect", c.server.name, c.nick)
|
|
||||||
case errNoPriv:
|
|
||||||
c.outputChan <- fmt.Sprintf(":%s 481 %s :Permission denied", c.server.name, c.nick)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) setNick(nick string) {
|
|
||||||
if c.nick != "" {
|
|
||||||
delete(c.server.clientMap, c.nick)
|
|
||||||
for _, channel := range c.channelMap {
|
|
||||||
delete(channel.clientMap, c.nick)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Set up new nick
|
|
||||||
oldNick := c.nick
|
|
||||||
c.nick = nick
|
|
||||||
c.server.clientMap[c.nick] = c
|
|
||||||
|
|
||||||
clients := make([]string, 0, 100)
|
|
||||||
|
|
||||||
for _, channel := range c.channelMap {
|
|
||||||
channel.clientMap[c.nick] = c
|
|
||||||
|
|
||||||
//Collect list of client nicks who can see us
|
|
||||||
for client := range channel.clientMap {
|
|
||||||
clients = append(clients, client)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//By sorting the nicks and skipping duplicates we send each client one message
|
|
||||||
sort.Strings(clients)
|
|
||||||
prevNick := ""
|
|
||||||
for _, nick := range clients {
|
|
||||||
if nick == prevNick {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
prevNick = nick
|
|
||||||
|
|
||||||
client, exists := c.server.clientMap[nick]
|
|
||||||
if exists {
|
|
||||||
client.reply(rplNickChange, oldNick, c.nick)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
308
server.go
Normal file
308
server.go
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
nickRegexp = regexp.MustCompile(`^[a-zA-Z\[\]_^{|}][a-zA-Z0-9\[\]_^{|}]*$`)
|
||||||
|
channelRegexp = regexp.MustCompile(`^#[a-z0-9_\-]+$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewServer() *Server {
|
||||||
|
return &Server{eventChan: make(chan Event),
|
||||||
|
name: "rosella",
|
||||||
|
clientMap: make(map[string]*Client),
|
||||||
|
channelMap: make(map[string]*Channel),
|
||||||
|
operatorMap: make(map[string]string)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Run() {
|
||||||
|
go func() {
|
||||||
|
for event := range s.eventChan {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Printf("Recovered from panic()")
|
||||||
|
log.Printf("%s sent %q", event.client.nick, event.input)
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
s.handleEvent(<-s.eventChan)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) HandleConnection(conn net.Conn) {
|
||||||
|
client := &Client{server: s,
|
||||||
|
connection: conn,
|
||||||
|
outputChan: make(chan string),
|
||||||
|
signalChan: make(chan int, 3),
|
||||||
|
channelMap: make(map[string]*Channel),
|
||||||
|
connected: true}
|
||||||
|
|
||||||
|
go client.clientThread()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleEvent(e Event) {
|
||||||
|
fields := strings.Fields(e.input)
|
||||||
|
|
||||||
|
if len(fields) < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(fields[0], ":") {
|
||||||
|
fields = fields[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
command := strings.ToUpper(fields[0])
|
||||||
|
args := fields[1:]
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case command == "NICK":
|
||||||
|
if len(args) < 1 {
|
||||||
|
e.client.reply(errNoNick)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newNick := args[0]
|
||||||
|
|
||||||
|
//Check newNick is of valid formatting (regex)
|
||||||
|
if nickRegexp.MatchString(newNick) == false {
|
||||||
|
e.client.reply(errInvalidNick, newNick)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := s.clientMap[newNick]; exists {
|
||||||
|
e.client.reply(errNickInUse, newNick)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Protect the server name from being used
|
||||||
|
if newNick == s.name {
|
||||||
|
e.client.reply(errNickInUse, newNick)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.client.setNick(newNick)
|
||||||
|
|
||||||
|
case command == "USER":
|
||||||
|
if e.client.nick == "" {
|
||||||
|
e.client.reply(rplKill, "Your nickname is already being used")
|
||||||
|
e.client.disconnect()
|
||||||
|
} else {
|
||||||
|
e.client.reply(rplWelcome)
|
||||||
|
e.client.registered = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case command == "JOIN":
|
||||||
|
if e.client.registered == false {
|
||||||
|
e.client.reply(errNotReg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
e.client.reply(errMoreArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if args[0] == "0" {
|
||||||
|
//Quit all channels
|
||||||
|
for channel := range e.client.channelMap {
|
||||||
|
e.client.partChannel(channel)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channels := strings.Split(args[0], ",")
|
||||||
|
for _, channel := range channels {
|
||||||
|
//Join the channel if it's valid
|
||||||
|
if channelRegexp.Match([]byte(channel)) {
|
||||||
|
e.client.joinChannel(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case command == "PART":
|
||||||
|
if e.client.registered == false {
|
||||||
|
e.client.reply(errNotReg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
e.client.reply(errMoreArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channels := strings.Split(args[0], ",")
|
||||||
|
for _, channel := range channels {
|
||||||
|
//Part the channel if it's valid
|
||||||
|
if channelRegexp.Match([]byte(channel)) {
|
||||||
|
e.client.partChannel(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case command == "PRIVMSG":
|
||||||
|
if e.client.registered == false {
|
||||||
|
e.client.reply(errNotReg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) < 2 {
|
||||||
|
e.client.reply(errMoreArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
message := strings.Join(args[1:], " ")
|
||||||
|
|
||||||
|
channel, chanExists := s.channelMap[args[0]]
|
||||||
|
client, clientExists := s.clientMap[args[0]]
|
||||||
|
|
||||||
|
if chanExists {
|
||||||
|
for _, c := range channel.clientMap {
|
||||||
|
if c != e.client {
|
||||||
|
c.reply(rplMsg, e.client.nick, args[0], message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if clientExists {
|
||||||
|
client.reply(rplMsg, e.client.nick, client.nick, message)
|
||||||
|
} else {
|
||||||
|
e.client.reply(errNoSuchNick, args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
case command == "QUIT":
|
||||||
|
if e.client.registered == false {
|
||||||
|
e.client.reply(errNotReg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.client.disconnect()
|
||||||
|
|
||||||
|
case command == "TOPIC":
|
||||||
|
if e.client.registered == false {
|
||||||
|
e.client.reply(errNotReg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
e.client.reply(errMoreArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, exists := s.channelMap[args[0]]
|
||||||
|
if exists == false {
|
||||||
|
e.client.reply(errNoSuchNick, args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channelName := args[0]
|
||||||
|
|
||||||
|
if len(args) == 1 {
|
||||||
|
e.client.reply(rplTopic, channelName, channel.topic)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if args[1] == ":" {
|
||||||
|
channel.topic = ""
|
||||||
|
for _, client := range channel.clientMap {
|
||||||
|
client.reply(rplNoTopic, channelName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
topic := strings.Join(args[1:], " ")
|
||||||
|
topic = strings.TrimPrefix(topic, ":")
|
||||||
|
channel.topic = topic
|
||||||
|
|
||||||
|
for _, client := range channel.clientMap {
|
||||||
|
client.reply(rplTopic, channelName, channel.topic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case command == "LIST":
|
||||||
|
if e.client.registered == false {
|
||||||
|
e.client.reply(errNotReg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
chanList := make([]string, 0, len(s.channelMap))
|
||||||
|
|
||||||
|
for channelName, channel := range s.channelMap {
|
||||||
|
listItem := fmt.Sprintf("%s %d :%s", channelName, len(channel.clientMap), channel.topic)
|
||||||
|
chanList = append(chanList, listItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.client.reply(rplList, chanList...)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
channels := strings.Split(args[0], ",")
|
||||||
|
chanList := make([]string, 0, len(channels))
|
||||||
|
|
||||||
|
for _, channelName := range channels {
|
||||||
|
if channel, exists := s.channelMap[channelName]; exists {
|
||||||
|
listItem := fmt.Sprintf("%s %d :%s", channelName, len(channel.clientMap), channel.topic)
|
||||||
|
chanList = append(chanList, listItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.client.reply(rplList, chanList...)
|
||||||
|
}
|
||||||
|
case command == "OPER":
|
||||||
|
if e.client.registered == false {
|
||||||
|
e.client.reply(errNotReg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) < 2 {
|
||||||
|
e.client.reply(errMoreArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username := args[0]
|
||||||
|
password := args[1]
|
||||||
|
|
||||||
|
if hashedPassword, exists := s.operatorMap[username]; exists {
|
||||||
|
h := sha1.New()
|
||||||
|
io.WriteString(h, password)
|
||||||
|
pass := fmt.Sprintf("%x", h.Sum(nil))
|
||||||
|
if hashedPassword == pass {
|
||||||
|
e.client.operator = true
|
||||||
|
e.client.reply(rplOper)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.client.reply(errPassword)
|
||||||
|
|
||||||
|
case command == "KILL":
|
||||||
|
if e.client.registered == false {
|
||||||
|
e.client.reply(errNotReg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.client.operator == false {
|
||||||
|
e.client.reply(errNoPriv)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
e.client.reply(errMoreArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nick := args[0]
|
||||||
|
|
||||||
|
if client, exists := s.clientMap[nick]; exists {
|
||||||
|
client.reply(rplKill, "An operator has disconnected you.")
|
||||||
|
client.disconnect()
|
||||||
|
} else {
|
||||||
|
e.client.reply(errNoSuchNick, nick)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
e.client.reply(errUnknownCommand, command)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user