anonircd/server.go

1633 lines
40 KiB
Go
Raw Normal View History

2016-09-02 05:54:54 +00:00
package main
import (
2017-06-08 07:39:01 +00:00
"bufio"
"crypto/tls"
"encoding/base64"
2016-09-02 05:54:54 +00:00
"fmt"
"log"
2017-06-08 07:39:01 +00:00
"math/rand"
2016-09-16 01:25:52 +00:00
"net"
2017-06-08 07:39:01 +00:00
"os"
"reflect"
2017-12-11 11:01:24 +00:00
"sort"
2016-09-02 05:54:54 +00:00
"strconv"
"strings"
2016-09-16 01:25:52 +00:00
"sync"
"time"
2016-09-02 05:54:54 +00:00
"github.com/BurntSushi/toml"
"github.com/pkg/errors"
"golang.org/x/crypto/sha3"
2017-12-11 11:01:24 +00:00
"gopkg.in/sorcix/irc.v2"
2016-09-02 05:54:54 +00:00
)
const (
COMMAND_HELP = "HELP"
COMMAND_INFO = "INFO"
// User commands
COMMAND_REGISTER = "REGISTER"
COMMAND_IDENTIFY = "IDENTIFY"
2017-12-11 11:01:24 +00:00
COMMAND_TOKEN = "TOKEN"
COMMAND_USERNAME = "USERNAME"
COMMAND_PASSWORD = "PASSWORD"
// User/channel commands
COMMAND_MODE = "MODE"
// Channel/server commands
COMMAND_FOUND = "FOUND"
2017-12-11 11:01:24 +00:00
COMMAND_DROP = "DROP"
COMMAND_GRANT = "GRANT"
COMMAND_REVEAL = "REVEAL"
COMMAND_KICK = "KICK"
COMMAND_BAN = "BAN"
2017-12-16 05:26:22 +00:00
COMMAND_AUDIT = "AUDIT"
// Server admin commands
COMMAND_KILL = "KILL"
COMMAND_STATS = "STATS"
COMMAND_REHASH = "REHASH"
COMMAND_UPGRADE = "UPGRADE"
)
2017-12-11 11:01:24 +00:00
var serverCommands = []string{COMMAND_KILL, COMMAND_STATS, COMMAND_REHASH, COMMAND_UPGRADE}
// TODO: Reorder
2017-12-11 11:01:24 +00:00
const (
PERMISSION_CLIENT = 0
PERMISSION_REGISTERED = 1
PERMISSION_VIP = 2
PERMISSION_MODERATOR = 3
PERMISSION_ADMIN = 4
PERMISSION_SUPERADMIN = 5
)
var permissionLabels = map[int]string{
PERMISSION_CLIENT: "Client",
2017-12-15 01:39:18 +00:00
PERMISSION_REGISTERED: "Registered Client",
2017-12-11 11:01:24 +00:00
PERMISSION_VIP: "VIP",
PERMISSION_MODERATOR: "Moderator",
PERMISSION_ADMIN: "Administrator",
PERMISSION_SUPERADMIN: "Super Administrator",
}
var ALL_PERMISSIONS = "Client, Registered Client, VIP, Moderator, Administrator and Super Administrator"
2017-12-16 05:26:22 +00:00
2017-12-11 11:01:24 +00:00
var commandRestrictions = map[int][]string{
PERMISSION_REGISTERED: {COMMAND_TOKEN, COMMAND_USERNAME, COMMAND_PASSWORD, COMMAND_FOUND},
PERMISSION_MODERATOR: {COMMAND_MODE, COMMAND_REVEAL, COMMAND_KICK, COMMAND_BAN},
2017-12-16 05:26:22 +00:00
PERMISSION_ADMIN: {COMMAND_GRANT, COMMAND_AUDIT},
2017-12-11 11:01:24 +00:00
PERMISSION_SUPERADMIN: {COMMAND_DROP, COMMAND_KILL, COMMAND_STATS, COMMAND_REHASH, COMMAND_UPGRADE}}
var helpDuration = "Duration can be 0 to never expire, or e.g. 30m, 1h, 2d, 3w"
var commandUsage = map[string][]string{
2017-12-16 05:26:22 +00:00
COMMAND_HELP: {"[command|all]",
"Print usage information regarding a specific command or 'all'",
"Without a command or 'all', only commands you have permission to use are printed"},
2017-12-11 11:01:24 +00:00
COMMAND_INFO: {"[channel]",
"When a channel is specified, prints info including whether it is registered",
"Without a channel, server info is printed"},
COMMAND_REGISTER: {"<username> <password>",
2017-12-16 05:26:22 +00:00
"Create an account",
"Once you've registered, other users may GRANT permissions to you, or ",
"See IDENTIFY"},
2017-12-11 11:01:24 +00:00
COMMAND_IDENTIFY: {"[username] <password>",
"Identify to a previously registered account",
"If username is omitted, it will be replaced with your current nick",
"Note that you may automatically identify when connecting by specifying a server password of your username and password separated by a colon - Example: admin:hunter2"},
COMMAND_TOKEN: {"<channel>",
"Returns a token which can be used by channel administrators to grant special access to your account"},
COMMAND_USERNAME: {"<username> <password> <new username> <confirm new username>",
"Change your username"},
COMMAND_PASSWORD: {"<username> <password> <new password> <confirm new password>",
"Change your password"},
COMMAND_FOUND: {"<channel>",
2017-12-16 05:26:22 +00:00
"Take ownership of an unfounded channel"},
COMMAND_GRANT: {"<channel> [account] [permission]",
"When an account token isn't specified, all accounts with permissions are listed",
"Specify an account token and permission level to grant that permission",
"Specify an account token only to view that account's permission",
"To remove an account's permissions, set their permission to Client",
"Permissions: " + ALL_PERMISSIONS},
COMMAND_REVEAL: {"<channel> [page] [all]",
2017-12-11 11:01:24 +00:00
"Print channel log, allowing KICK/BAN to be used",
fmt.Sprintf("Results start at page 1, %d per page", CHANNEL_LOGS_PER_PAGE),
2017-12-16 05:26:22 +00:00
"Page -1 shows all matching entries",
"Joins and parts are hidden by default, add 'all' to show them"},
COMMAND_AUDIT: {"<channel> [page]",
"Print channel audit log",
fmt.Sprintf("Results start at page 1, %d per page", CHANNEL_LOGS_PER_PAGE),
"Page -1 shows all matching entries"},
2017-12-11 11:01:24 +00:00
COMMAND_KICK: {"<channel> <5 digit log number> [reason]",
"Kick a user from a channel"},
COMMAND_BAN: {"<channel> <5 digit log number> <duration> [reason]",
"Kick and ban a user from a channel",
helpDuration},
COMMAND_DROP: {"<channel> <confirm channel>",
2017-12-16 05:26:22 +00:00
"Delete all channel data, allowing it to be founded again"},
2017-12-11 11:01:24 +00:00
COMMAND_KILL: {"<channel> <5 digit log number> <duration> [reason]",
"Disconnect and ban a user from the server",
helpDuration},
COMMAND_STATS: {"",
"Print the current number of clients and channels"},
COMMAND_REHASH: {"",
"Reload the server configuration"},
COMMAND_UPGRADE: {"",
"Upgrade the server without disconnecting clients"},
}
type Config struct {
MOTD string
Salt string
DBDriver string
DBSource string
SSLCert string
SSLKey string
}
2016-09-02 05:54:54 +00:00
type Server struct {
2017-06-08 07:39:01 +00:00
config *Config
configfile string
2017-06-08 07:39:01 +00:00
created int64
motd []string
2017-09-13 05:51:51 +00:00
clients *sync.Map
channels *sync.Map
2017-06-08 07:39:01 +00:00
odyssey *os.File
odysseymutex *sync.RWMutex
2016-09-02 05:54:54 +00:00
restartplain chan bool
restartssl chan bool
2016-09-02 05:54:54 +00:00
*sync.RWMutex
}
2017-12-11 11:01:24 +00:00
var db = &Database{}
func NewServer(configfile string) *Server {
2017-09-09 02:22:12 +00:00
s := &Server{}
s.config = &Config{}
s.configfile = configfile
2017-09-09 02:22:12 +00:00
s.created = time.Now().Unix()
2017-09-13 05:51:51 +00:00
s.clients = new(sync.Map)
s.channels = new(sync.Map)
s.odysseymutex = new(sync.RWMutex)
2017-09-09 02:22:12 +00:00
s.restartplain = make(chan bool, 1)
s.restartssl = make(chan bool, 1)
s.RWMutex = new(sync.RWMutex)
return s
}
func (s *Server) hashPassword(username string, password string) string {
sha512 := sha3.New512()
_, err := sha512.Write([]byte(strings.Join([]string{username, s.config.Salt, password}, "-")))
if err != nil {
return ""
}
return base64.URLEncoding.EncodeToString(sha512.Sum(nil))
}
2016-09-02 05:54:54 +00:00
func (s *Server) getAnonymousPrefix(i int) *irc.Prefix {
prefix := prefixAnonymous
2016-09-02 05:54:54 +00:00
if i > 1 {
prefix.Name += fmt.Sprintf("%d", i)
}
return &prefix
}
func (s *Server) getChannel(channel string) *Channel {
2017-09-13 05:51:51 +00:00
if ch, ok := s.channels.Load(channel); ok {
return ch.(*Channel)
}
return nil
}
2016-09-02 05:54:54 +00:00
func (s *Server) getChannels(client string) map[string]*Channel {
channels := make(map[string]*Channel)
2017-09-13 05:51:51 +00:00
s.channels.Range(func(k, v interface{}) bool {
key := k.(string)
channel := v.(*Channel)
if client == "" || s.inChannel(key, client) {
2017-09-13 05:51:51 +00:00
channels[key] = channel
2016-09-02 05:54:54 +00:00
}
2017-09-13 05:51:51 +00:00
return true
})
2016-09-02 05:54:54 +00:00
return channels
}
func (s *Server) channelCount() int {
i := 0
s.channels.Range(func(k, v interface{}) bool {
i++
return true
})
return i
}
2016-09-02 05:54:54 +00:00
func (s *Server) getClient(client string) *Client {
2017-09-27 21:21:18 +00:00
if cl, ok := s.clients.Load(client); ok {
return cl.(*Client)
2016-09-02 05:54:54 +00:00
}
return nil
}
func (s *Server) getClients(channel string) map[string]*Client {
clients := make(map[string]*Client)
2017-12-11 11:01:24 +00:00
if channel == "" {
s.clients.Range(func(k, v interface{}) bool {
cl := s.getClient(k.(string))
if cl != nil {
clients[cl.identifier] = cl
}
return true
})
return clients
}
ch := s.getChannel(channel)
if ch == nil {
return clients
}
2017-09-13 05:51:51 +00:00
ch.clients.Range(func(k, v interface{}) bool {
cl := s.getClient(k.(string))
2017-12-11 11:01:24 +00:00
if cl != nil {
2017-09-13 05:51:51 +00:00
clients[cl.identifier] = cl
}
return true
})
2016-09-02 05:54:54 +00:00
return clients
}
func (s *Server) clientCount() int {
i := 0
s.clients.Range(func(k, v interface{}) bool {
i++
return true
})
return i
}
func (s *Server) revealClient(channel string, identifier string) *Client {
2017-12-11 11:01:24 +00:00
riphash, raccount := s.revealClientInfo(channel, identifier)
if riphash == "" && raccount == 0 {
return nil
}
2017-12-15 01:39:18 +00:00
2017-12-11 11:01:24 +00:00
cls := s.getClients("")
for _, rcl := range cls {
2017-12-11 11:01:24 +00:00
if rcl.iphash == riphash || (rcl.account > 0 && rcl.account == raccount) {
return rcl
}
}
return nil
}
2017-12-15 01:39:18 +00:00
func (s *Server) revealClientInfo(channel string, identifier string) (string, int64) {
2017-12-11 11:01:24 +00:00
if len(identifier) != 5 {
return "", 0
}
ch := s.getChannel(channel)
if ch == nil {
return "", 0
}
return ch.RevealInfo(identifier)
}
2016-09-02 05:54:54 +00:00
func (s *Server) inChannel(channel string, client string) bool {
ch := s.getChannel(channel)
if ch != nil {
2017-09-13 05:51:51 +00:00
_, ok := ch.clients.Load(client)
return ok
2016-09-02 05:54:54 +00:00
}
return false
}
2017-12-20 09:06:42 +00:00
func (s *Server) canJoin(c *Client, channel string, key string) (bool, string) {
dbch, err := db.Channel(channel)
if err != nil || dbch.Channel == "" {
return false, "invalid channel"
}
ch := s.getChannel(channel)
if ch == nil {
return false, "invalid channel"
} else if banned, reason := c.isBanned(channel); banned {
if reason != "" {
reason = fmt.Sprintf(" (%s)", reason)
}
return false, "you are banned" + reason
} else if ch.hasMode("z") && !c.ssl {
return false, "only clients connected via SSL are allowed"
}
permission := c.globalPermission()
requiredPermission := PERMISSION_CLIENT
reason := ""
if channel[0] == '&' {
if permission < PERMISSION_VIP {
return false, "restricted channel"
}
} else if channel[0] != '#' {
return false, "invalid channel"
}
if permission < requiredPermission && c.account > 0 {
chp, err := db.GetPermission(c.account, channel)
if err != nil && chp.Permission > permission {
permission = chp.Permission
}
}
2017-12-20 09:06:42 +00:00
if permission < PERMISSION_VIP {
if ch.hasMode("k") && ch.getMode("k") != key {
return false, "invalid channel key specified"
} else if ch.hasMode("l") {
var l int
var err error
l, err = strconv.Atoi(ch.getMode("l"))
if err != nil {
l = 0
}
if l > 0 && ch.clientCount() >= l {
return false, "limited channel, join again in a few moments"
}
}
}
if ch.hasMode("r") {
requiredPermission = PERMISSION_REGISTERED
reason = "only registered clients are allowed"
}
if ch.hasMode("i") {
requiredPermission = PERMISSION_VIP
reason = "only VIP are allowed"
}
return permission >= requiredPermission, reason
}
2017-12-20 09:06:42 +00:00
func (s *Server) joinChannel(client string, channel string, key string) {
2016-09-02 05:54:54 +00:00
if s.inChannel(channel, client) {
return // Already in channel
}
cl := s.getClient(client)
if cl == nil {
return
}
ch := s.getChannel(channel)
if ch == nil {
ch = NewChannel(channel)
2017-09-13 05:51:51 +00:00
s.channels.Store(channel, ch)
2017-12-20 09:06:42 +00:00
} else if canaccess, reason := s.canJoin(cl, channel, key); !canaccess {
errmsg := fmt.Sprintf("Cannot join %s: %s", channel, reason)
cl.writeMessage(irc.ERR_INVITEONLYCHAN, []string{channel, errmsg})
cl.sendNotice(errmsg)
2017-12-11 11:01:24 +00:00
return
}
2017-12-20 09:06:42 +00:00
ch.clients.Store(client, s.anonCount(channel, client)+1)
2017-12-15 01:39:18 +00:00
cl.write(cl.getPrefix(), irc.JOIN, []string{channel})
ch.Log(cl, irc.JOIN, "")
2016-09-02 05:54:54 +00:00
s.sendNames(channel, client)
s.updateClientCount(channel, client, "")
2016-09-02 05:54:54 +00:00
s.sendTopic(channel, client, false)
}
func (s *Server) partChannel(channel string, client string, reason string) {
ch := s.getChannel(channel)
cl := s.getClient(client)
if cl == nil || !s.inChannel(channel, client) {
return
2016-09-02 05:54:54 +00:00
}
2017-12-15 01:39:18 +00:00
cl.write(cl.getPrefix(), irc.PART, []string{channel, reason})
ch.Log(cl, irc.PART, reason)
2017-09-13 05:51:51 +00:00
ch.clients.Delete(client)
2016-09-02 05:54:54 +00:00
s.updateClientCount(channel, client, reason)
// TODO: Destroy empty channel
2016-09-02 05:54:54 +00:00
}
func (s *Server) partAllChannels(client string, reason string) {
2016-09-02 05:54:54 +00:00
for channelname := range s.getChannels(client) {
s.partChannel(channelname, client, reason)
}
}
2017-12-15 01:39:18 +00:00
func (s *Server) revealChannelLog(channel string, client string, page int, showAll bool) {
cl := s.getClient(client)
if cl == nil {
return
}
ch := s.getChannel(channel)
if ch == nil {
cl.sendError("Unable to reveal, invalid channel specified")
return
} else if !ch.HasClient(client) {
cl.sendError("Unable to reveal, you are not in that channel")
return
}
2017-12-15 01:39:18 +00:00
r := ch.RevealLog(page, showAll)
for _, rev := range r {
cl.sendMessage(rev)
}
}
func (s *Server) enforceModes(channel string) {
ch := s.getChannel(channel)
if ch != nil && ch.hasMode("z") {
for client, cl := range s.getClients(channel) {
if !cl.ssl {
s.partChannel(channel, client, fmt.Sprintf("You must connect via SSL to join %s", channel))
}
}
2016-09-02 05:54:54 +00:00
}
}
2017-12-20 09:06:42 +00:00
func (s *Server) anonCount(channel string, client string) int {
ch := s.getChannel(channel)
cl := s.getClient(client)
if ch == nil || cl == nil {
return 0
}
2017-12-20 09:06:42 +00:00
ccount := ch.clientCount()
if (ch.hasMode("c") || cl.hasMode("c")) && ccount >= 2 {
return 2
}
return ccount
}
func (s *Server) updateClientCount(channel string, client string, reason string) {
ch := s.getChannel(channel)
if ch == nil {
return
}
var reasonShown bool
2017-09-13 05:51:51 +00:00
ch.clients.Range(func(k, v interface{}) bool {
cl := s.getClient(k.(string))
ccount := v.(int)
if cl == nil {
2017-09-13 05:51:51 +00:00
return true
} else if client != "" && ch.hasMode("D") && cl.identifier != client {
return true
}
reasonShown = false
2017-12-20 09:06:42 +00:00
chancount := s.anonCount(channel, cl.identifier)
if ccount < chancount {
for i := ccount; i < chancount; i++ {
2017-12-15 01:39:18 +00:00
cl.write(s.getAnonymousPrefix(i), irc.JOIN, []string{channel})
2016-09-02 05:54:54 +00:00
}
2017-09-13 05:51:51 +00:00
ch.clients.Store(cl.identifier, chancount)
} else if ccount > chancount {
for i := ccount; i > chancount; i-- {
pr := ""
if !reasonShown {
pr = reason
}
2017-12-15 01:39:18 +00:00
cl.write(s.getAnonymousPrefix(i-1), irc.PART, []string{channel, pr})
reasonShown = true
2016-09-02 05:54:54 +00:00
}
} else {
2017-09-13 05:51:51 +00:00
return true
2016-09-02 05:54:54 +00:00
}
2017-09-13 05:51:51 +00:00
ch.clients.Store(cl.identifier, chancount)
return true
})
2016-09-02 05:54:54 +00:00
}
func (s *Server) sendNames(channel string, clientname string) {
if !s.inChannel(channel, clientname) {
return
}
cl := s.getClient(clientname)
if cl == nil {
return
}
names := []string{}
if cl.capHostInNames {
names = append(names, cl.getPrefix().String())
} else {
names = append(names, cl.nick)
}
2017-12-20 09:06:42 +00:00
ccount := s.anonCount(channel, clientname)
for i := 1; i < ccount; i++ {
if cl.capHostInNames {
names = append(names, s.getAnonymousPrefix(i).String())
2016-09-02 05:54:54 +00:00
} else {
names = append(names, s.getAnonymousPrefix(i).Name)
2016-09-02 05:54:54 +00:00
}
}
cl.writeMessage(irc.RPL_NAMREPLY, []string{"=", channel, strings.Join(names, " ")})
cl.writeMessage(irc.RPL_ENDOFNAMES, []string{channel, "End of /NAMES list."})
2016-09-02 05:54:54 +00:00
}
func (s *Server) sendTopic(channel string, client string, changed bool) {
if !s.inChannel(channel, client) {
return
2016-09-02 05:54:54 +00:00
}
ch := s.getChannel(channel)
cl := s.getClient(client)
if ch == nil || cl == nil {
return
}
tprefix := prefixAnonymous
2017-09-14 18:59:54 +00:00
tcommand := irc.TOPIC
if !changed {
tprefix = prefixAnonIRC
2017-09-14 18:59:54 +00:00
tcommand = irc.RPL_TOPIC
}
2017-12-15 01:39:18 +00:00
cl.write(&tprefix, tcommand, []string{channel, ch.topic})
2016-09-02 05:54:54 +00:00
2017-09-14 18:59:54 +00:00
if !changed {
cl.writeMessage(strings.Join([]string{irc.RPL_TOPICWHOTIME, cl.nick, channel, prefixAnonymous.Name, fmt.Sprintf("%d", ch.topictime)}, " "), nil)
2016-09-02 05:54:54 +00:00
}
}
func (s *Server) handleTopic(channel string, client string, topic string) {
ch := s.getChannel(channel)
cl := s.getClient(client)
if ch == nil || cl == nil {
return
}
2016-09-02 05:54:54 +00:00
if !s.inChannel(channel, client) {
cl.sendNotice("Invalid use of TOPIC")
return
2016-09-02 05:54:54 +00:00
}
2017-12-11 11:01:24 +00:00
chp, err := db.GetPermission(cl.account, channel)
if err != nil {
2017-12-11 11:01:24 +00:00
log.Panicf("%+v", err)
2017-12-20 10:22:40 +00:00
} else if ch.hasMode("t") && chp.Permission < PERMISSION_VIP {
2017-12-11 11:01:24 +00:00
cl.accessDenied(PERMISSION_VIP)
return
}
2017-09-14 18:59:54 +00:00
ch.topic = topic
ch.topictime = time.Now().Unix()
2016-09-02 05:54:54 +00:00
2017-09-14 18:59:54 +00:00
ch.clients.Range(func(k, v interface{}) bool {
s.sendTopic(channel, k.(string), true)
return true
})
ch.Log(cl, irc.TOPIC, ch.topic)
2016-09-02 05:54:54 +00:00
}
func (s *Server) handleChannelMode(c *Client, params []string) {
ch := s.getChannel(params[0])
if ch == nil || !s.inChannel(params[0], c.identifier) {
return
}
if len(params) == 1 || params[1] == "" {
c.writeMessage(strings.Join([]string{irc.RPL_CHANNELMODEIS, c.nick, params[0], ch.printModes(ch.getModes(), nil)}, " "), []string{})
// Send channel creation time
c.writeMessage(strings.Join([]string{"329", c.nick, params[0], fmt.Sprintf("%d", int32(ch.created))}, " "), []string{})
} else if len(params) > 1 && (params[1] == "" || params[1][0] == '+' || params[1][0] == '-') {
if !c.canUse(COMMAND_MODE, params[0]) {
c.accessDenied(c.permissionRequired(COMMAND_MODE))
return
}
lastmodes := make(map[string]string)
for m, mv := range ch.getModes() {
lastmodes[m] = mv
}
if params[1][0] == '+' {
2017-12-20 09:06:42 +00:00
ch.addModes(params[1:])
} else {
ch.removeModes(params[1][1:])
}
s.enforceModes(params[0])
if reflect.DeepEqual(ch.getModes(), lastmodes) {
return
}
// TODO: Check if local modes were set/unset, only send changes to local client
addedmodes, removedmodes := ch.diffModes(lastmodes)
resendusercount := false
if _, ok := addedmodes["c"]; ok {
resendusercount = true
}
if _, ok := removedmodes["c"]; ok {
resendusercount = true
}
if _, ok := removedmodes["D"]; ok {
resendusercount = true
}
if len(addedmodes) == 0 && len(removedmodes) == 0 {
addedmodes = c.getModes()
}
ch.clients.Range(func(k, v interface{}) bool {
cl := s.getClient(k.(string))
if cl != nil {
cl.write(&prefixAnonymous, irc.MODE, []string{params[0], ch.printModes(addedmodes, removedmodes)})
}
2017-09-13 05:51:51 +00:00
return true
})
if resendusercount {
s.updateClientCount(params[0], c.identifier, "Enforcing MODEs")
}
}
}
func (s *Server) handleUserMode(c *Client, params []string) {
if len(params) == 1 || params[1] == "" {
c.writeMessage(strings.Join([]string{irc.RPL_UMODEIS, c.nick, c.printModes(c.getModes(), nil)}, " "), []string{})
return
}
lastmodes := c.getModes()
if len(params) > 1 && len(params[1]) > 0 && (params[1][0] == '+' || params[1][0] == '-') {
if params[1][0] == '+' {
2017-12-20 09:06:42 +00:00
c.addModes(params[1:])
} else {
c.removeModes(params[1][1:])
}
}
if reflect.DeepEqual(c.modes, lastmodes) {
return
}
addedmodes, removedmodes := c.diffModes(lastmodes)
2016-09-02 05:54:54 +00:00
resendusercount := false
if _, ok := addedmodes["c"]; ok {
resendusercount = true
}
if _, ok := removedmodes["c"]; ok {
resendusercount = true
}
if _, ok := removedmodes["D"]; ok {
resendusercount = true
}
if len(addedmodes) == 0 && len(removedmodes) == 0 {
addedmodes = c.getModes()
}
c.writeMessage(strings.Join([]string{irc.MODE, c.nick}, " "), []string{c.printModes(addedmodes, removedmodes)})
if resendusercount {
for ch := range s.getChannels(c.identifier) {
s.updateClientCount(ch, c.identifier, "Enforcing MODEs")
}
2016-09-02 05:54:54 +00:00
}
}
func (s *Server) handleMode(c *Client, params []string) {
if c == nil {
return
}
if len(params) == 0 || len(params[0]) == 0 {
c.sendNotice("Invalid use of MODE")
return
}
if len(params) > 1 && params[1] == "b" {
c.writeMessage(irc.RPL_ENDOFBANLIST, []string{params[0], "End of Channel Ban List"})
return
}
if params[0][0] == '#' || params[0][0] == '&' {
s.handleChannelMode(c, params)
} else {
s.handleUserMode(c, params)
}
}
2017-12-16 05:26:22 +00:00
func (s *Server) sendUsage(cl *Client, command string) {
command = strings.ToUpper(command)
2017-12-16 05:26:22 +00:00
showAll := false
if command == "ALL" {
command = COMMAND_HELP
showAll = true
}
2017-12-16 05:26:22 +00:00
commands := make([]string, 0, len(commandUsage))
for cmd := range commandUsage {
commands = append(commands, cmd)
}
sort.Strings(commands)
2017-12-15 01:39:18 +00:00
var printedLabel bool
var usage []string
2017-12-15 01:39:18 +00:00
if command == COMMAND_HELP {
// Print all commands