2016-09-02 05:54:54 +00:00
package main
import (
2017-06-08 07:39:01 +00:00
"crypto/tls"
2017-12-02 07:50:51 +00:00
"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
2017-06-08 01:53:08 +00:00
"github.com/BurntSushi/toml"
2017-12-02 07:50:51 +00:00
"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
)
2017-12-02 07:50:51 +00:00
const (
2021-06-25 06:00:33 +00:00
commandHelp = "HELP"
commandInfo = "INFO"
2017-12-02 07:50:51 +00:00
// User commands
2021-06-25 06:00:33 +00:00
commandRegister = "REGISTER"
commandIdentify = "IDENTIFY"
commandToken = "TOKEN"
commandUsername = "USERNAME"
commandPassword = "PASSWORD"
2017-12-02 07:50:51 +00:00
2017-12-20 08:48:32 +00:00
// User/channel commands
2021-06-25 06:00:33 +00:00
commandMode = "MODE"
2017-12-20 08:48:32 +00:00
2017-12-02 07:50:51 +00:00
// Channel/server commands
2021-06-25 06:00:33 +00:00
commandFound = "FOUND"
commandDrop = "DROP"
commandGrant = "GRANT"
commandReveal = "REVEAL"
commandKick = "KICK"
commandBan = "BAN"
commandAudit = "AUDIT"
2017-12-02 07:50:51 +00:00
2017-12-20 08:48:32 +00:00
// Server admin commands
2021-06-25 06:00:33 +00:00
commandKill = "KILL"
commandStats = "STATS"
commandRehash = "REHASH"
commandUpgrade = "UPGRADE"
2017-12-02 07:50:51 +00:00
)
2021-06-25 06:00:33 +00:00
var serverCommands = [ ] string { commandKill , commandStats , commandRehash , commandUpgrade }
2017-12-11 11:01:24 +00:00
2017-12-20 08:48:32 +00:00
// TODO: Reorder
2017-12-11 11:01:24 +00:00
const (
2021-06-25 06:00:33 +00:00
permissionClient = 0
permissionRegistered = 1
permissionVIP = 2
permissionModerator = 3
permissionAdmin = 4
permissionSuperAdmin = 5
2017-12-11 11:01:24 +00:00
)
var permissionLabels = map [ int ] string {
2021-06-25 06:00:33 +00:00
permissionClient : "Client" ,
permissionRegistered : "Registered Client" ,
permissionVIP : "VIP" ,
permissionModerator : "Moderator" ,
permissionAdmin : "Administrator" ,
permissionSuperAdmin : "Super Administrator" ,
2017-12-11 11:01:24 +00:00
}
2021-06-25 06:00:33 +00:00
var allPermissions = "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 {
2021-06-25 06:00:33 +00:00
permissionRegistered : { commandToken , commandUsername , commandPassword , commandFound } ,
permissionModerator : { commandMode , commandReveal , commandKick , commandBan } ,
permissionAdmin : { commandGrant , commandAudit } ,
permissionSuperAdmin : { commandDrop , commandKill , commandStats , commandRehash , commandUpgrade } }
2017-12-11 11:01:24 +00:00
var helpDuration = "Duration can be 0 to never expire, or e.g. 30m, 1h, 2d, 3w"
var commandUsage = map [ string ] [ ] string {
2021-06-25 06:00:33 +00:00
commandHelp : { "[command|all]" ,
2017-12-16 05:26:22 +00:00
"Print usage information regarding a specific command or 'all'" ,
2017-12-20 08:48:32 +00:00
"Without a command or 'all', only commands you have permission to use are printed" } ,
2021-06-25 06:00:33 +00:00
commandInfo : { "[channel]" ,
2017-12-11 11:01:24 +00:00
"When a channel is specified, prints info including whether it is registered" ,
"Without a channel, server info is printed" } ,
2021-06-25 06:00:33 +00:00
commandRegister : { "<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" } ,
2021-06-25 06:00:33 +00:00
commandIdentify : { "[username] <password>" ,
2017-12-11 11:01:24 +00:00
"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" } ,
2021-06-25 06:00:33 +00:00
commandToken : { "<channel>" ,
2017-12-11 11:01:24 +00:00
"Returns a token which can be used by channel administrators to grant special access to your account" } ,
2021-06-25 06:00:33 +00:00
commandUsername : { "<username> <password> <new username> <confirm new username>" ,
2017-12-11 11:01:24 +00:00
"Change your username" } ,
2021-06-25 06:00:33 +00:00
commandPassword : { "<username> <password> <new password> <confirm new password>" ,
2017-12-11 11:01:24 +00:00
"Change your password" } ,
2021-06-25 06:00:33 +00:00
commandFound : { "<channel>" ,
2017-12-16 05:26:22 +00:00
"Take ownership of an unfounded channel" } ,
2021-06-25 06:00:33 +00:00
commandGrant : { "<channel> [account] [permission]" ,
2017-12-16 05:26:22 +00:00
"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" ,
2021-06-25 06:00:33 +00:00
"Permissions: " + allPermissions } ,
commandReveal : { "<channel> [page] [all]" ,
2017-12-11 11:01:24 +00:00
"Print channel log, allowing KICK/BAN to be used" ,
2021-06-25 06:00:33 +00:00
fmt . Sprintf ( "Results start at page 1, %d per page" , logsPerPage ) ,
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" } ,
2021-06-25 06:00:33 +00:00
commandAudit : { "<channel> [page]" ,
2017-12-16 05:26:22 +00:00
"Print channel audit log" ,
2021-06-25 06:00:33 +00:00
fmt . Sprintf ( "Results start at page 1, %d per page" , logsPerPage ) ,
2017-12-16 05:26:22 +00:00
"Page -1 shows all matching entries" } ,
2021-06-25 06:00:33 +00:00
commandKick : { "<channel> <5 digit log number> [reason]" ,
2017-12-11 11:01:24 +00:00
"Kick a user from a channel" } ,
2021-06-25 06:00:33 +00:00
commandBan : { "<channel> <5 digit log number> <duration> [reason]" ,
2017-12-11 11:01:24 +00:00
"Kick and ban a user from a channel" ,
helpDuration } ,
2021-06-25 06:00:33 +00:00
commandDrop : { "<channel> <confirm channel>" ,
2017-12-16 05:26:22 +00:00
"Delete all channel data, allowing it to be founded again" } ,
2021-06-25 06:00:33 +00:00
commandKill : { "<channel> <5 digit log number> <duration> [reason]" ,
2017-12-11 11:01:24 +00:00
"Disconnect and ban a user from the server" ,
helpDuration } ,
2021-06-25 06:00:33 +00:00
commandStats : { "" ,
2017-12-11 11:01:24 +00:00
"Print the current number of clients and channels" } ,
2021-06-25 06:00:33 +00:00
commandRehash : { "" ,
2017-12-11 11:01:24 +00:00
"Reload the server configuration" } ,
2021-06-25 06:00:33 +00:00
commandUpgrade : { "" ,
2017-12-11 11:01:24 +00:00
"Upgrade the server without disconnecting clients" } ,
}
2016-09-16 06:12:25 +00:00
type Config struct {
2017-12-20 08:48:32 +00:00
MOTD string
2017-12-02 07:50:51 +00:00
Salt string
DBDriver string
DBSource string
SSLCert string
SSLKey string
2016-09-16 06:12:25 +00:00
}
2016-09-02 05:54:54 +00:00
type Server struct {
2019-07-10 08:17:35 +00:00
config * Config
configfile string
created int64
motd [ ] string
clients * sync . Map
channels * sync . Map
2016-09-02 05:54:54 +00:00
2016-09-16 06:12:25 +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 { }
2017-12-02 07:50:51 +00:00
func NewServer ( configfile string ) * Server {
2017-09-09 02:22:12 +00:00
s := & Server { }
2017-09-13 05:51:51 +00:00
s . config = & Config { }
2017-12-02 07:50:51 +00:00
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 )
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
}
2017-12-02 07:50:51 +00:00
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 {
2017-12-02 07:50:51 +00:00
prefix := prefixAnonymous
2016-09-02 05:54:54 +00:00
if i > 1 {
prefix . Name += fmt . Sprintf ( "%d" , i )
}
return & prefix
}
2017-04-15 21:31:16 +00:00
func ( s * Server ) getChannel ( channel string ) * Channel {
2017-09-13 05:51:51 +00:00
if ch , ok := s . channels . Load ( channel ) ; ok {
2017-04-15 21:31:16 +00:00
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 )
2017-12-02 07:50:51 +00:00
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
}
2017-12-02 07:50:51 +00:00
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 {
2017-04-15 21:31:16 +00:00
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
}
2017-04-15 21:31:16 +00:00
ch := s . getChannel ( channel )
2017-12-02 07:50:51 +00:00
if ch == nil {
return clients
}
2017-04-15 21:31:16 +00:00
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
}
2017-12-02 07:50:51 +00:00
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 {
2017-12-02 07:50:51 +00:00
return nil
}
2017-12-15 01:39:18 +00:00
2017-12-11 11:01:24 +00:00
cls := s . getClients ( "" )
2017-12-02 07:50:51 +00:00
for _ , rcl := range cls {
2017-12-11 11:01:24 +00:00
if rcl . iphash == riphash || ( rcl . account > 0 && rcl . account == raccount ) {
2017-12-02 07:50:51 +00:00
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 {
2017-04-15 21:31:16 +00:00
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 ) {
2017-12-20 08:48:32 +00:00
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 ( )
2021-06-25 06:00:33 +00:00
requiredPermission := permissionClient
2017-12-20 08:48:32 +00:00
reason := ""
if channel [ 0 ] == '&' {
2021-06-25 06:00:33 +00:00
if permission < permissionVIP {
2017-12-20 08:48:32 +00:00
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
}
}
2021-06-25 06:00:33 +00:00
if permission < permissionVIP {
2017-12-20 09:06:42 +00:00
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"
}
}
}
2017-12-20 08:48:32 +00:00
if ch . hasMode ( "r" ) {
2021-06-25 06:00:33 +00:00
requiredPermission = permissionRegistered
2017-12-20 08:48:32 +00:00
reason = "only registered clients are allowed"
}
if ch . hasMode ( "i" ) {
2021-06-25 06:00:33 +00:00
requiredPermission = permissionVIP
2017-12-20 08:48:32 +00:00
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
}
2017-04-15 21:31:16 +00:00
cl := s . getClient ( client )
if cl == nil {
return
}
2017-12-02 07:50:51 +00:00
ch := s . getChannel ( channel )
2017-04-15 21:31:16 +00:00
if ch == nil {
2017-09-13 05:51:51 +00:00
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 {
2017-12-20 08:48:32 +00:00
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 } )
2017-12-02 07:50:51 +00:00
ch . Log ( cl , irc . JOIN , "" )
2016-09-02 05:54:54 +00:00
s . sendNames ( channel , client )
2017-12-02 07:50:51 +00:00
s . updateClientCount ( channel , client , "" )
2016-09-02 05:54:54 +00:00
s . sendTopic ( channel , client , false )
}
2016-09-06 06:49:05 +00:00
func ( s * Server ) partChannel ( channel string , client string , reason string ) {
2017-04-15 21:31:16 +00:00
ch := s . getChannel ( channel )
cl := s . getClient ( client )
if cl == nil || ! s . inChannel ( channel , client ) {
2016-09-06 06:49:05 +00:00
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 } )
2017-12-02 07:50:51 +00:00
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
2017-12-02 07:50:51 +00:00
s . updateClientCount ( channel , client , reason )
// TODO: Destroy empty channel
2016-09-02 05:54:54 +00:00
}
2017-12-02 07:50:51 +00:00
func ( s * Server ) partAllChannels ( client string , reason string ) {
2016-09-02 05:54:54 +00:00
for channelname := range s . getChannels ( client ) {
2017-12-02 07:50:51 +00:00
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 ) {
2017-12-02 07:50:51 +00:00
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 )
2017-12-02 07:50:51 +00:00
for _ , rev := range r {
cl . sendMessage ( rev )
2016-09-06 06:49:05 +00:00
}
}
func ( s * Server ) enforceModes ( channel string ) {
2017-04-15 21:31:16 +00:00
ch := s . getChannel ( channel )
if ch != nil && ch . hasMode ( "z" ) {
for client , cl := range s . getClients ( channel ) {
if ! cl . ssl {
2017-12-02 07:50:51 +00:00
s . partChannel ( channel , client , fmt . Sprintf ( "You must connect via SSL to join %s" , channel ) )
2016-09-06 06:49:05 +00:00
}
}
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 {
2017-04-15 21:31:16 +00:00
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 ( )
2017-04-15 21:31:16 +00:00
if ( ch . hasMode ( "c" ) || cl . hasMode ( "c" ) ) && ccount >= 2 {
2016-09-08 04:19:32 +00:00
return 2
}
2017-04-15 09:51:17 +00:00
return ccount
2016-09-08 04:19:32 +00:00
}
2016-09-04 09:22:33 +00:00
2017-12-02 07:50:51 +00:00
func ( s * Server ) updateClientCount ( channel string , client string , reason string ) {
2017-04-15 21:31:16 +00:00
ch := s . getChannel ( channel )
if ch == nil {
return
2016-09-08 04:19:32 +00:00
}
2017-04-15 21:31:16 +00:00
2017-12-02 07:50:51 +00:00
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 )
2017-04-15 21:31:16 +00:00
if cl == nil {
2017-09-13 05:51:51 +00:00
return true
} else if client != "" && ch . hasMode ( "D" ) && cl . identifier != client {
return true
2017-04-15 21:31:16 +00:00
}
2017-04-15 09:51:17 +00:00
2017-12-02 07:50:51 +00:00
reasonShown = false
2017-12-20 09:06:42 +00:00
chancount := s . anonCount ( channel , cl . identifier )
2017-04-15 22:45:53 +00:00
2016-09-04 09:22:33 +00:00
if ccount < chancount {
2017-04-15 21:31:16 +00:00
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 )
2016-09-04 09:22:33 +00:00
} else if ccount > chancount {
2017-04-15 21:31:16 +00:00
for i := ccount ; i > chancount ; i -- {
2017-12-02 07:50:51 +00:00
pr := ""
if ! reasonShown {
pr = reason
}
2017-12-15 01:39:18 +00:00
cl . write ( s . getAnonymousPrefix ( i - 1 ) , irc . PART , [ ] string { channel , pr } )
2017-12-02 07:50:51 +00:00
reasonShown = true
2016-09-02 05:54:54 +00:00
}
2017-04-15 21:31:16 +00:00
} else {
2017-09-13 05:51:51 +00:00
return true
2016-09-02 05:54:54 +00:00
}
2017-04-15 21:31:16 +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 ) {
2017-12-02 07:50:51 +00:00
if ! s . inChannel ( channel , clientname ) {
return
}
2017-04-15 22:45:53 +00:00
2017-12-02 07:50:51 +00:00
cl := s . getClient ( clientname )
if cl == nil {
return
}
2017-04-15 22:45:53 +00:00
2017-12-02 07:50:51 +00:00
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 )
2017-12-02 07:50:51 +00:00
for i := 1 ; i < ccount ; i ++ {
2017-04-15 22:45:53 +00:00
if cl . capHostInNames {
2017-12-02 07:50:51 +00:00
names = append ( names , s . getAnonymousPrefix ( i ) . String ( ) )
2016-09-02 05:54:54 +00:00
} else {
2017-12-02 07:50:51 +00:00
names = append ( names , s . getAnonymousPrefix ( i ) . Name )
2016-09-02 05:54:54 +00:00
}
}
2017-12-02 07:50:51 +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 ) {
2016-09-06 06:49:05 +00:00
return
2016-09-02 05:54:54 +00:00
}
2017-04-15 21:31:16 +00:00
ch := s . getChannel ( channel )
cl := s . getClient ( client )
if ch == nil || cl == nil {
return
}
2017-12-02 07:50:51 +00:00
tprefix := prefixAnonymous
2017-09-14 18:59:54 +00:00
tcommand := irc . TOPIC
if ! changed {
2017-12-02 07:50:51 +00:00
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 {
2017-12-02 07:50:51 +00:00
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 ) {
2017-04-15 21:31:16 +00:00
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 ) {
2017-04-15 21:31:16 +00:00
cl . sendNotice ( "Invalid use of TOPIC" )
2016-09-06 06:49:05 +00:00
return
2016-09-02 05:54:54 +00:00
}
2017-12-11 11:01:24 +00:00
chp , err := db . GetPermission ( cl . account , channel )
2017-12-02 07:50:51 +00:00
if err != nil {
2017-12-11 11:01:24 +00:00
log . Panicf ( "%+v" , err )
2021-06-25 06:00:33 +00:00
} else if ch . hasMode ( "t" ) && chp . Permission < permissionVIP {
cl . accessDenied ( permissionVIP )
2017-12-02 07:50:51 +00:00
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
} )
2017-12-02 07:50:51 +00:00
ch . Log ( cl , irc . TOPIC , ch . topic )
2016-09-02 05:54:54 +00:00
}
2017-12-20 08:48:32 +00:00
func ( s * Server ) handleChannelMode ( c * Client , params [ ] string ) {
ch := s . getChannel ( params [ 0 ] )
if ch == nil || ! s . inChannel ( params [ 0 ] , c . identifier ) {
2017-12-02 07:50:51 +00:00
return
}
2017-12-20 08:48:32 +00:00
if len ( params ) == 1 || params [ 1 ] == "" {
c . writeMessage ( strings . Join ( [ ] string { irc . RPL_CHANNELMODEIS , c . nick , params [ 0 ] , ch . printModes ( ch . getModes ( ) , nil ) } , " " ) , [ ] string { } )
2017-04-15 21:31:16 +00:00
2017-12-20 08:48:32 +00:00
// 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 ] == '-' ) {
2021-06-25 06:00:33 +00:00
if ! c . canUse ( commandMode , params [ 0 ] ) {
c . accessDenied ( c . permissionRequired ( commandMode ) )
2016-09-04 09:22:33 +00:00
return
}
2017-12-20 08:48:32 +00:00
lastmodes := make ( map [ string ] string )
for m , mv := range ch . getModes ( ) {
lastmodes [ m ] = mv
}
2017-12-02 07:50:51 +00:00
2017-12-20 08:48:32 +00:00
if params [ 1 ] [ 0 ] == '+' {
2017-12-20 09:06:42 +00:00
ch . addModes ( params [ 1 : ] )
2017-12-20 08:48:32 +00:00
} else {
ch . removeModes ( params [ 1 ] [ 1 : ] )
}
s . enforceModes ( params [ 0 ] )
2016-09-04 09:22:33 +00:00
2017-12-20 08:48:32 +00:00
if reflect . DeepEqual ( ch . getModes ( ) , lastmodes ) {
return
}
2016-09-04 09:22:33 +00:00
2017-12-20 08:48:32 +00:00
// TODO: Check if local modes were set/unset, only send changes to local client
addedmodes , removedmodes := ch . diffModes ( lastmodes )
2016-09-08 04:19:32 +00:00
2017-12-20 08:48:32 +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
}
2016-09-08 04:19:32 +00:00
2017-12-20 08:48:32 +00:00
if len ( addedmodes ) == 0 && len ( removedmodes ) == 0 {
addedmodes = c . getModes ( )
}
2016-09-08 04:19:32 +00:00
2017-12-20 08:48:32 +00:00
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
2017-12-20 08:48:32 +00:00
return true
} )
2016-09-06 06:49:05 +00:00
2017-12-20 08:48:32 +00:00
if resendusercount {
s . updateClientCount ( params [ 0 ] , c . identifier , "Enforcing MODEs" )
2016-09-04 09:22:33 +00:00
}
2017-12-20 08:48:32 +00:00
}
}
2016-09-04 09:22:33 +00:00
2017-12-20 08:48:32 +00:00
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
}
2016-09-04 09:22:33 +00:00
2017-12-20 08:48:32 +00:00
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 : ] )
2017-12-20 08:48:32 +00:00
} else {
c . removeModes ( params [ 1 ] [ 1 : ] )
2016-09-04 09:22:33 +00:00
}
2017-12-20 08:48:32 +00:00
}
2016-09-04 09:22:33 +00:00
2017-12-20 08:48:32 +00:00
if reflect . DeepEqual ( c . modes , lastmodes ) {
return
}
2016-09-08 04:19:32 +00:00
2017-12-20 08:48:32 +00:00
addedmodes , removedmodes := c . diffModes ( lastmodes )
2016-09-02 05:54:54 +00:00
2017-12-20 08:48:32 +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
}
2016-09-08 04:19:32 +00:00
2017-12-20 08:48:32 +00:00
if len ( addedmodes ) == 0 && len ( removedmodes ) == 0 {
addedmodes = c . getModes ( )
}
2016-09-08 04:19:32 +00:00
2017-12-20 08:48:32 +00:00
c . writeMessage ( strings . Join ( [ ] string { irc . MODE , c . nick } , " " ) , [ ] string { c . printModes ( addedmodes , removedmodes ) } )