496 lines
14 KiB
Go
496 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
"log"
|
|
"strconv"
|
|
"strings"
|
|
|
|
irc "gopkg.in/sorcix/irc.v2"
|
|
"math/rand"
|
|
"crypto/tls"
|
|
"reflect"
|
|
)
|
|
|
|
type Server struct {
|
|
config *Config
|
|
created int64
|
|
clients map[string]*Client
|
|
channels map[string]*Channel
|
|
|
|
*sync.RWMutex
|
|
}
|
|
|
|
func (s *Server) getAnonymousPrefix(i int) *irc.Prefix {
|
|
prefix := anonymous
|
|
if i > 1 {
|
|
prefix.Name += fmt.Sprintf("%d", i)
|
|
}
|
|
return &prefix
|
|
}
|
|
|
|
func (s *Server) getChannels(client string) map[string]*Channel {
|
|
channels := make(map[string]*Channel)
|
|
for channelname, channel := range s.channels {
|
|
if s.inChannel(channelname, client) {
|
|
channels[channelname] = channel
|
|
}
|
|
}
|
|
return channels
|
|
}
|
|
|
|
func (s *Server) getClient(client string) *Client {
|
|
if _, ok := s.clients[client]; ok {
|
|
return s.clients[client]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) getClients(channel string) map[string]*Client {
|
|
clients := make(map[string]*Client)
|
|
if !s.channelExists(channel) {
|
|
return clients
|
|
}
|
|
|
|
for clientname := range s.channels[channel].clients {
|
|
cl := s.getClient(clientname)
|
|
if cl != nil {
|
|
clients[clientname] = cl
|
|
}
|
|
}
|
|
|
|
return clients
|
|
}
|
|
|
|
func (s *Server) channelExists(channel string) bool {
|
|
if _, ok := s.channels[channel]; ok {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (s *Server) inChannel(channel string, client string) bool {
|
|
if s.channelExists(channel) {
|
|
if _, ok := s.channels[channel].clients[client]; ok {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (s *Server) joinChannel(channel string, client string) {
|
|
if s.inChannel(channel, client) {
|
|
return // Already in channel
|
|
}
|
|
|
|
if !s.channelExists(channel) {
|
|
s.channels[channel] = &Channel{Entity{ENTITY_CHANNEL, time.Now().Unix(), make(map[string]string), new(sync.RWMutex)}, make(map[string]int), "", 0}
|
|
}
|
|
s.channels[channel].Lock()
|
|
s.channels[channel].clients[client] = len(s.channels[channel].clients) + 1
|
|
s.channels[channel].Unlock()
|
|
|
|
s.clients[client].writebuffer <- &irc.Message{s.clients[client].getPrefix(), irc.JOIN, []string{channel}}
|
|
|
|
s.sendNames(channel, client)
|
|
s.updateUserCount(channel)
|
|
s.sendTopic(channel, client, false)
|
|
}
|
|
|
|
func (s *Server) partChannel(channel string, client string) {
|
|
if !s.inChannel(channel, client) {
|
|
return // Not in channel
|
|
}
|
|
|
|
s.clients[client].writebuffer <- &irc.Message{s.clients[client].getPrefix(), irc.PART, []string{channel}}
|
|
|
|
s.channels[channel].Lock()
|
|
delete(s.channels[channel].clients, client)
|
|
s.channels[channel].Unlock()
|
|
|
|
s.updateUserCount(channel)
|
|
}
|
|
|
|
func (s *Server) partAllChannels(client string) {
|
|
for channelname := range s.getChannels(client) {
|
|
s.partChannel(channelname, client)
|
|
}
|
|
}
|
|
|
|
func (s *Server) updateUserCount(channel string) {
|
|
for cclient, ccount := range s.channels[channel].clients {
|
|
chancount := 1
|
|
if !s.clients[cclient].hasMode("h") {
|
|
chancount = len(s.channels[channel].clients)
|
|
}
|
|
|
|
if ccount < chancount {
|
|
for i := ccount; i < chancount; i++ {
|
|
prefix := anonymous
|
|
if i > 1 {
|
|
prefix.Name += fmt.Sprintf("%d", i)
|
|
}
|
|
s.clients[cclient].writebuffer <- &irc.Message{s.getAnonymousPrefix(i), irc.JOIN, []string{channel}}
|
|
}
|
|
|
|
s.channels[channel].clients[cclient] = chancount
|
|
} else if ccount > chancount {
|
|
for i := ccount; i > chancount; i-- {
|
|
s.clients[cclient].writebuffer <- &irc.Message{s.getAnonymousPrefix(i - 1), irc.PART, []string{channel}}
|
|
}
|
|
|
|
s.channels[channel].clients[cclient] = chancount
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) sendNames(channel string, clientname string) {
|
|
if s.inChannel(channel, clientname) {
|
|
c := s.getClient(clientname)
|
|
names := []string{}
|
|
if c.capHostInNames {
|
|
names = append(names, c.getPrefix().String())
|
|
} else {
|
|
names = append(names, c.nick)
|
|
}
|
|
|
|
for i := 1; i < len(s.channels[channel].clients); i++ {
|
|
if c.capHostInNames {
|
|
names = append(names, s.getAnonymousPrefix(i).String())
|
|
} else {
|
|
names = append(names, s.getAnonymousPrefix(i).Name)
|
|
}
|
|
}
|
|
|
|
c.writebuffer <- &irc.Message{&anonirc, irc.RPL_NAMREPLY, []string{"=", channel, strings.Join(names, " ")}}
|
|
c.writebuffer <- &irc.Message{&anonirc, irc.RPL_ENDOFNAMES, []string{channel, "End of /NAMES list."}}
|
|
}
|
|
}
|
|
|
|
func (s *Server) sendTopic(channel string, client string, changed bool) {
|
|
if !s.inChannel(channel, client) {
|
|
return // Not in channel TODO: Send error instead
|
|
}
|
|
|
|
if s.channels[channel].topic != "" {
|
|
tprefix := anonymous
|
|
tcommand := irc.TOPIC
|
|
if !changed {
|
|
tprefix = anonirc
|
|
tcommand = irc.RPL_TOPIC
|
|
}
|
|
s.clients[client].writebuffer <- &irc.Message{&tprefix, tcommand, []string{channel, s.channels[channel].topic}}
|
|
|
|
if !changed {
|
|
s.clients[client].writebuffer <- &irc.Message{&anonirc, strings.Join([]string{irc.RPL_TOPICWHOTIME, s.clients[client].nick, channel, "Anonymous", fmt.Sprintf("%d", s.channels[channel].topictime)}, " "), nil}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleTopic(channel string, client string, topic string) {
|
|
if !s.inChannel(channel, client) {
|
|
return // Not in channel TODO: Send error
|
|
}
|
|
|
|
if topic != "" {
|
|
s.channels[channel].Lock()
|
|
s.channels[channel].topic = topic
|
|
s.channels[channel].topictime = time.Now().Unix()
|
|
s.channels[channel].Unlock()
|
|
|
|
for sclient := range s.channels[channel].clients {
|
|
s.sendTopic(channel, sclient, true)
|
|
}
|
|
} else {
|
|
s.sendTopic(channel, client, false)
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleMode(c *Client, params []string) {
|
|
if len(params) == 0 || len(params[0]) == 0 {
|
|
return // TODO: Send error
|
|
}
|
|
|
|
if params[0][0] == '#' {
|
|
if !s.channelExists(params[0]) {
|
|
return
|
|
}
|
|
|
|
channel := s.channels[params[0]]
|
|
if len(params) == 1 {
|
|
c.writebuffer <- &irc.Message{&anonirc, strings.Join([]string{irc.RPL_CHANNELMODEIS, c.nick, params[0], channel.printModes(nil)}, " "), []string{}}
|
|
|
|
// Send channel creation time
|
|
c.writebuffer <- &irc.Message{&anonirc, strings.Join([]string{"329", c.nick, params[0], fmt.Sprintf("%d", int32(channel.created))}, " "), []string{}}
|
|
} else if len(params) > 1 && len(params[1]) > 0 && (params[1][0] == '+' || params[1][0] == '-') {
|
|
lastmodes := make(map[string]string)
|
|
for mode, modevalue := range channel.modes {
|
|
lastmodes[mode] = modevalue
|
|
}
|
|
|
|
channel.Lock()
|
|
if params[1][0] == '+' {
|
|
channel.addModes(params[1][1:])
|
|
} else {
|
|
channel.removeModes(params[1][1:])
|
|
}
|
|
channel.Unlock()
|
|
|
|
if !reflect.DeepEqual(channel.modes, lastmodes) {
|
|
for sclient := range channel.clients {
|
|
s.clients[sclient].writebuffer <- &irc.Message{&anonymous, irc.MODE, []string{params[0], channel.printModes(lastmodes)}}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if len(params) == 1 {
|
|
c.writebuffer <- &irc.Message{&anonirc, strings.Join([]string{irc.RPL_UMODEIS, c.nick, c.printModes(nil)}, " "), []string{}}
|
|
return
|
|
}
|
|
|
|
lastmodes := make(map[string]string)
|
|
for mode, modevalue := range c.modes {
|
|
lastmodes[mode] = modevalue
|
|
}
|
|
|
|
forcedisplay := true
|
|
if len(params) > 1 && len(params[1]) > 0 && (params[1][0] == '+' || params[1][0] == '-') {
|
|
forcedisplay = false
|
|
|
|
c.Lock()
|
|
if params[1][0] == '+' {
|
|
c.addModes(params[1][1:])
|
|
} else {
|
|
c.removeModes(params[1][1:])
|
|
}
|
|
c.Unlock()
|
|
}
|
|
|
|
if forcedisplay || !reflect.DeepEqual(c.modes, lastmodes) {
|
|
printmodes := lastmodes
|
|
if forcedisplay {
|
|
printmodes = nil
|
|
}
|
|
|
|
c.writebuffer <- &irc.Message{&anonirc, strings.Join([]string{irc.MODE, c.nick}, " "), []string{c.printModes(printmodes)}}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) handlePrivmsg(channel string, client string, message string) {
|
|
if !s.inChannel(channel, client) {
|
|
return // Not in channel TODO: Send error message
|
|
}
|
|
|
|
for sclient := range s.channels[channel].clients {
|
|
if s.clients[sclient].identifier != client {
|
|
s.clients[sclient].writebuffer <- &irc.Message{&anonymous, irc.PRIVMSG, []string{channel, message}}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleRead(c *Client) {
|
|
for {
|
|
c.conn.SetDeadline(time.Now().Add(300 * time.Second))
|
|
msg, err := s.clients[c.identifier].reader.Decode()
|
|
if err != nil {
|
|
fmt.Println("Unable to read from client:", err)
|
|
s.partAllChannels(c.identifier)
|
|
return
|
|
}
|
|
if (msg.Command != irc.PING && msg.Command != irc.PONG) {
|
|
fmt.Println(c.identifier, "<-", fmt.Sprintf("%s", msg))
|
|
}
|
|
if (msg.Command == irc.CAP && len(msg.Params) > 0 && len(msg.Params[0]) > 0 && msg.Params[0] == irc.CAP_LS) {
|
|
c.writebuffer <- &irc.Message{&anonirc, irc.CAP, []string{msg.Params[0], "userhost-in-names"}}
|
|
} else if (msg.Command == irc.CAP && len(msg.Params) > 0 && len(msg.Params[0]) > 0 && msg.Params[0] == irc.CAP_REQ) {
|
|
if strings.Contains(msg.Trailing(), "userhost-in-names") {
|
|
c.capHostInNames = true
|
|
}
|
|
c.writebuffer <- &irc.Message{&anonirc, irc.CAP, []string{irc.CAP_ACK, msg.Trailing()}}
|
|
} else if (msg.Command == irc.CAP && len(msg.Params) > 0 && len(msg.Params[0]) > 0 && msg.Params[0] == irc.CAP_LIST) {
|
|
caps := []string{}
|
|
if c.capHostInNames {
|
|
caps = append(caps, "userhost-in-names")
|
|
}
|
|
c.writebuffer <- &irc.Message{&anonirc, irc.CAP, []string{msg.Params[0], strings.Join(caps, " ")}}
|
|
} else if (msg.Command == irc.PING) {
|
|
c.writebuffer <- &irc.Message{&anonirc, irc.PONG + " AnonIRC", []string{msg.Trailing()}}
|
|
} else if (msg.Command == irc.NICK && c.nick == "*" && len(msg.Params) > 0 && len(msg.Params[0]) > 0 && msg.Params[0] != "" && msg.Params[0] != "*") {
|
|
c.nick = strings.Trim(msg.Params[0], "\"")
|
|
} else if (msg.Command == irc.USER && c.user == "" && len(msg.Params) >= 3 && msg.Params[0] != "" && msg.Params[2] != "") {
|
|
c.user = strings.Trim(msg.Params[0], "\"")
|
|
c.host = strings.Trim(msg.Params[2], "\"")
|
|
|
|
c.writebuffer <- &irc.Message{&anonirc, irc.RPL_WELCOME, []string{"Welcome to AnonIRC " + c.getPrefix().String()}}
|
|
c.writebuffer <- &irc.Message{&anonirc, irc.RPL_YOURHOST, []string{"Your host is AnonIRC, running version AnonIRCd"}}
|
|
c.writebuffer <- &irc.Message{&anonirc, irc.RPL_CREATED, []string{fmt.Sprintf("This server was created %s", time.Unix(s.created, 0).UTC())}}
|
|
c.writebuffer <- &irc.Message{&anonirc, strings.Join([]string{irc.RPL_MYINFO, c.nick, "AnonIRC AnonIRCd", CLIENT_MODES, CHANNEL_MODES, CHANNEL_MODES_ARG}, " "), []string{}}
|
|
|
|
motdsplit := strings.Split(motd, "\n")
|
|
for i, motdmsg := range motdsplit {
|
|
var motdcode string
|
|
if (i == 0) {
|
|
motdcode = irc.RPL_MOTDSTART
|
|
} else if (i < len(motdsplit) - 1) {
|
|
motdcode = irc.RPL_MOTD
|
|
} else {
|
|
motdcode = irc.RPL_ENDOFMOTD
|
|
}
|
|
c.writebuffer <- &irc.Message{&anonirc, motdcode, []string{" " + motdmsg}}
|
|
}
|
|
|
|
s.joinChannel("#", c.identifier)
|
|
} else if (msg.Command == irc.JOIN && len(msg.Params) > 0 && len(msg.Params[0]) > 0 && msg.Params[0][0] == '#') {
|
|
for _, channel := range strings.Split(msg.Params[0], ",") {
|
|
s.joinChannel(channel, c.identifier)
|
|
}
|
|
} else if (msg.Command == irc.NAMES && len(msg.Params) > 0 && len(msg.Params[0]) > 0 && msg.Params[0][0] == '#') {
|
|
for _, channel := range strings.Split(msg.Params[0], ",") {
|
|
s.sendNames(channel, c.identifier)
|
|
}
|
|
} else if (msg.Command == irc.WHO && len(msg.Params) > 0 && len(msg.Params[0]) > 0 && msg.Params[0][0] == '#') {
|
|
for _, channel := range strings.Split(msg.Params[0], ",") {
|
|
if s.inChannel(channel, c.identifier) {
|
|
i := 0
|
|
for _, cl := range s.getClients(channel) {
|
|
var prfx *irc.Prefix
|
|
if cl.identifier == c.identifier {
|
|
prfx = c.getPrefix()
|
|
} else {
|
|
i++
|
|
prfx = s.getAnonymousPrefix(i)
|
|
}
|
|
|
|
c.writebuffer <- &irc.Message{&anonirc, irc.RPL_WHOREPLY, []string{channel, prfx.User, prfx.Host, "AnonIRC", prfx.Name, "H", "0 Anonymous"}}
|
|
}
|
|
c.writebuffer <- &irc.Message{&anonirc, irc.RPL_ENDOFWHO, []string{channel, "End of /WHO list."}}
|
|
}
|
|
}
|
|
} else if (msg.Command == irc.MODE && len(msg.Params) > 0 && len(msg.Params[0]) > 0) {
|
|
if len(msg.Params) == 2 && msg.Params[0][0] == '#' && msg.Params[1] == "b" {
|
|
c.writebuffer <- &irc.Message{&anonirc, irc.RPL_ENDOFBANLIST, []string{msg.Params[0], "End of Channel Ban List"}}
|
|
} else {
|
|
s.handleMode(c, msg.Params)
|
|
}
|
|
} else if (msg.Command == irc.TOPIC && len(msg.Params) > 0 && len(msg.Params[0]) > 0) {
|
|
s.handleTopic(msg.Params[0], c.identifier, msg.Trailing())
|
|
} else if (msg.Command == irc.PRIVMSG && len(msg.Params) > 0 && len(msg.Params[0]) > 0) {
|
|
s.handlePrivmsg(msg.Params[0], c.identifier, msg.Trailing())
|
|
} else if (msg.Command == irc.PART && len(msg.Params) > 0 && len(msg.Params[0]) > 0 && msg.Params[0][0] == '#') {
|
|
for _, channel := range strings.Split(msg.Params[0], ",") {
|
|
s.partChannel(channel, c.identifier)
|
|
}
|
|
} else if (msg.Command == irc.QUIT) {
|
|
s.partAllChannels(c.identifier)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleWrite(c *Client) {
|
|
for msg := range c.writebuffer {
|
|
addnick := false
|
|
if _, err := strconv.Atoi(msg.Command); err == nil {
|
|
addnick = true
|
|
} else if msg.Command == irc.CAP {
|
|
addnick = true
|
|
}
|
|
|
|
if addnick {
|
|
msg.Params = append([]string{c.nick}, msg.Params...)
|
|
}
|
|
|
|
if (msg.Command != irc.PING && msg.Command != irc.PONG) {
|
|
fmt.Println(c.identifier, "->", msg)
|
|
}
|
|
c.writer.Encode(msg)
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleConnection(conn net.Conn) {
|
|
defer conn.Close()
|
|
|
|
var identifier string
|
|
for {
|
|
identifier = randomIdentifier()
|
|
if _, ok := s.clients[identifier]; !ok {
|
|
break
|
|
}
|
|
}
|
|
|
|
client := Client{Entity{ENTITY_CLIENT, time.Now().Unix(), make(map[string]string), new(sync.RWMutex)}, identifier, "*", "", "", conn, []string{}, make(chan *irc.Message), irc.NewDecoder(conn), irc.NewEncoder(conn), false}
|
|
|
|
s.Lock()
|
|
s.clients[client.identifier] = &client
|
|
s.Unlock()
|
|
|
|
go s.handleWrite(&client)
|
|
s.handleRead(&client)
|
|
}
|
|
|
|
func (s *Server) listenPlain() {
|
|
listen, err := net.Listen("tcp", ":6667")
|
|
if err != nil {
|
|
log.Fatalf("Failed to listen: %v", err)
|
|
}
|
|
defer listen.Close()
|
|
|
|
for {
|
|
conn, err := listen.Accept()
|
|
if err != nil {
|
|
fmt.Println("Error accepting connection:", err)
|
|
continue
|
|
}
|
|
go s.handleConnection(conn)
|
|
}
|
|
}
|
|
|
|
func (s *Server) listenSSL() {
|
|
if s.config.SSLCert == "" {
|
|
return // SSL is disabled
|
|
}
|
|
|
|
cert, err := tls.LoadX509KeyPair(s.config.SSLCert, s.config.SSLKey)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load SSL certificate: %v", err)
|
|
}
|
|
|
|
listen, err := tls.Listen("tcp", ":6697", &tls.Config{Certificates:[]tls.Certificate{cert}})
|
|
if err != nil {
|
|
log.Fatalf("Failed to listen: %v", err)
|
|
}
|
|
defer listen.Close()
|
|
|
|
for {
|
|
conn, err := listen.Accept()
|
|
if err != nil {
|
|
fmt.Println("Error accepting connection:", err)
|
|
continue
|
|
}
|
|
go s.handleConnection(conn)
|
|
}
|
|
}
|
|
|
|
func (s *Server) pingClients() {
|
|
for {
|
|
for _, c := range s.clients {
|
|
ping := fmt.Sprintf("anonirc%d%d", int32(time.Now().Unix()), rand.Intn(1000))
|
|
//c.pings = append(c.pings, ping)
|
|
c.writebuffer <- &irc.Message{nil, irc.PING, []string{ping}}
|
|
}
|
|
time.Sleep(15 * time.Second)
|
|
}
|
|
}
|
|
|
|
func (s *Server) listen() {
|
|
go s.listenPlain()
|
|
go s.listenSSL()
|
|
|
|
s.pingClients()
|
|
}
|