2016-09-02 05:54:54 +00:00
package main
import (
2017-06-08 07:39:01 +00:00
"bufio"
"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 (
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"
2017-12-02 07:50:51 +00:00
COMMAND_USERNAME = "USERNAME"
COMMAND_PASSWORD = "PASSWORD"
// Channel/server commands
COMMAND_FOUND = "FOUND"
2017-12-11 11:01:24 +00:00
COMMAND_DROP = "DROP"
2017-12-02 07:50:51 +00:00
COMMAND_GRANT = "GRANT"
COMMAND_REVEAL = "REVEAL"
COMMAND_KICK = "KICK"
COMMAND_BAN = "BAN"
2017-12-16 05:26:22 +00:00
COMMAND_AUDIT = "AUDIT"
2017-12-02 07:50:51 +00:00
// Server admins only
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 }
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" ,
}
2017-12-16 05:26:22 +00:00
var ALL_PERMISSIONS = "Client, Registered Client, VIP, Moderator, Adminsitrator and Super Administrator"
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_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 currently available 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" } ,
}
2016-09-16 06:12:25 +00:00
type Config struct {
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 {
2017-06-08 07:39:01 +00:00
config * Config
2017-12-02 07:50:51 +00:00
configfile string
2017-06-08 07:39:01 +00:00
created int64
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
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-13 05:51:51 +00:00
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
}
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 {
log . Println ( "hash not found" )
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
}
func ( s * Server ) joinChannel ( channel string , client string ) {
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
if len ( channel ) == 0 {
return
} else if channel [ 0 ] == '&' {
2017-12-11 11:01:24 +00:00
if cl . globalPermission ( ) < PERMISSION_VIP {
cl . accessDenied ( 0 )
2017-12-02 07:50:51 +00:00
return
}
} else if channel [ 0 ] != '#' {
return
}
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-04-15 21:31:16 +00:00
} else if ch . hasMode ( "z" ) && ! cl . ssl {
2017-09-27 21:21:18 +00:00
cl . sendNotice ( "Unable to join " + channel + ": SSL connections only (channel mode +z)" )
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
banned , reason := cl . isBanned ( channel )
if banned {
ex := ""
if reason != "" {
ex = ". Reason: " + reason
}
cl . sendNotice ( "Unable to join " + channel + ": You are banned" + ex )
return
}
2017-12-02 07:50:51 +00:00
ch . clients . Store ( client , s . clientsInChannel ( 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
}
// TODO: Check channel permission
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-02 07:50:51 +00:00
func ( s * Server ) clientsInChannel ( 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-09-13 05:51:51 +00:00
ccount := 0
ch . clients . Range ( func ( k , v interface { } ) bool {
ccount ++
return true
} )
2017-04-15 09:51:17 +00:00
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
chancount := s . clientsInChannel ( 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 )
}
ccount := s . clientsInChannel ( channel , clientname )
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 )
} else if ch . hasMode ( "t" ) && ( chp . Permission < PERMISSION_VIP ) {
cl . accessDenied ( PERMISSION_VIP )
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
}
func ( s * Server ) handleMode ( c * Client , params [ ] string ) {
2016-09-04 09:22:33 +00:00
if len ( params ) == 0 || len ( params [ 0 ] ) == 0 {
2016-09-06 06:49:05 +00:00
c . sendNotice ( "Invalid use of MODE" )
return
2016-09-02 05:54:54 +00:00
}
2017-12-16 05:26:22 +00:00
if len ( params ) > 1 && params [ 1 ] == "b" {
c . writeMessage ( irc . RPL_ENDOFBANLIST , [ ] string { params [ 0 ] , "End of Channel Ban List" } )
2017-12-02 07:50:51 +00:00
return
}
if validChannelPrefix ( params [ 0 ] ) {
2017-04-15 21:31:16 +00:00
ch := s . getChannel ( params [ 0 ] )
if ch == nil {
2016-09-04 09:22:33 +00:00
return
}
2016-09-08 04:19:32 +00:00
if len ( params ) == 1 || params [ 1 ] == "" {
2017-12-02 07:50:51 +00:00
c . writeMessage ( strings . Join ( [ ] string { irc . RPL_CHANNELMODEIS , c . nick , params [ 0 ] , ch . printModes ( ch . getModes ( ) , nil ) } , " " ) , [ ] string { } )
2016-09-04 09:22:33 +00:00
// Send channel creation time
2017-12-02 07:50:51 +00:00
c . writeMessage ( strings . Join ( [ ] string { "329" , c . nick , params [ 0 ] , fmt . Sprintf ( "%d" , int32 ( ch . created ) ) } , " " ) , [ ] string { } )
2017-12-16 05:26:22 +00:00
} else if len ( params ) > 1 && len ( params [ 1 ] ) > 0 && params [ 1 ] [ 0 ] == '+' || params [ 1 ] [ 0 ] == '-' {
2017-12-11 11:01:24 +00:00
if ! c . canUse ( irc . MODE , params [ 0 ] ) {
c . accessDenied ( c . permissionRequired ( irc . MODE ) )
2017-12-02 07:50:51 +00:00
return
}
2016-09-04 09:22:33 +00:00
lastmodes := make ( map [ string ] string )
2017-09-13 05:51:51 +00:00
for m , mv := range ch . getModes ( ) {
lastmodes [ m ] = mv
2016-09-04 09:22:33 +00:00
}
if params [ 1 ] [ 0 ] == '+' {
2017-04-15 21:31:16 +00:00
ch . addModes ( params [ 1 ] [ 1 : ] )
2016-09-04 09:22:33 +00:00
} else {
2017-04-15 21:31:16 +00:00
ch . removeModes ( params [ 1 ] [ 1 : ] )
2016-09-04 09:22:33 +00:00
}
2016-09-08 04:19:32 +00:00
s . enforceModes ( params [ 0 ] )
2016-09-04 09:22:33 +00:00
2017-09-13 05:51:51 +00:00
if ! reflect . DeepEqual ( ch . getModes ( ) , lastmodes ) {
2016-09-08 04:19:32 +00:00
// TODO: Check if local modes were set/unset, only send changes to local client
2017-04-15 21:31:16 +00:00
addedmodes , removedmodes := ch . diffModes ( lastmodes )
2016-09-08 04:19:32 +00:00
resendusercount := false
if _ , ok := addedmodes [ "c" ] ; ok {
resendusercount = true
}
if _ , ok := removedmodes [ "c" ] ; ok {
resendusercount = true
}
2017-07-26 03:41:15 +00:00
if _ , ok := removedmodes [ "D" ] ; ok {
resendusercount = true
}
2016-09-08 04:19:32 +00:00
2016-09-16 01:25:52 +00:00
if len ( addedmodes ) == 0 && len ( removedmodes ) == 0 {
2017-04-15 21:31:16 +00:00
addedmodes = c . getModes ( )
2016-09-08 04:19:32 +00:00
}
2017-09-13 05:51:51 +00:00
ch . clients . Range ( func ( k , v interface { } ) bool {
cl := s . getClient ( k . ( string ) )
2017-04-15 21:31:16 +00:00
if cl != nil {
2017-12-15 01:39:18 +00:00
cl . write ( & prefixAnonymous , irc . MODE , [ ] string { params [ 0 ] , ch . printModes ( addedmodes , removedmodes ) } )
2017-04-15 21:31:16 +00:00
}
2017-09-13 05:51:51 +00:00
return true
} )
2016-09-06 06:49:05 +00:00
2016-09-08 04:19:32 +00:00
if resendusercount {
2017-12-02 07:50:51 +00:00
s . updateClientCount ( params [ 0 ] , c . identifier , "Enforcing MODEs" )
2016-09-08 04:19:32 +00:00
}
2016-09-04 09:22:33 +00:00
}
}
} else {
2016-09-08 04:19:32 +00:00
if len ( params ) == 1 || params [ 1 ] == "" {
2017-12-02 07:50:51 +00:00
c . writeMessage ( strings . Join ( [ ] string { irc . RPL_UMODEIS , c . nick , c . printModes ( c . getModes ( ) , nil ) } , " " ) , [ ] string { } )
2016-09-04 09:22:33 +00:00
return
}
2017-04-15 21:31:16 +00:00
lastmodes := c . getModes ( )
2016-09-04 09:22:33 +00:00
2016-09-16 01:25:52 +00:00
if len ( params ) > 1 && len ( params [ 1 ] ) > 0 && ( params [ 1 ] [ 0 ] == '+' || params [ 1 ] [ 0 ] == '-' ) {
2016-09-04 09:22:33 +00:00
if params [ 1 ] [ 0 ] == '+' {
c . addModes ( params [ 1 ] [ 1 : ] )
} else {
c . removeModes ( params [ 1 ] [ 1 : ] )
}
}
2016-09-08 04:19:32 +00:00
if ! reflect . DeepEqual ( c . modes , lastmodes ) {
addedmodes , removedmodes := c . diffModes ( lastmodes )
resendusercount := false
if _ , ok := addedmodes [ "c" ] ; ok {
resendusercount = true
}
if _ , ok := removedmodes [ "c" ] ; ok {
resendusercount = true
2016-09-04 09:22:33 +00:00
}
2017-07-26 03:41:15 +00:00
if _ , ok := removedmodes [ "D" ] ; ok {
resendusercount = true
}
2016-09-02 05:54:54 +00:00
2016-09-16 01:25:52 +00:00
if len ( addedmodes ) == 0 && len ( removedmodes ) == 0 {
2017-04-15 21:31:16 +00:00
addedmodes = c . getModes ( )
2016-09-08 04:19:32 +00:00
}
2017-12-02 07:50:51 +00:00
c . writeMessage ( strings . Join ( [ ] string { irc . MODE , c . nick } , " " ) , [ ] string { c . printModes ( addedmodes , removedmodes ) } )
2016-09-08 04:19:32 +00:00
if resendusercount {
for ch := range s . getChannels ( c . identifier ) {
2017-12-02 07:50:51 +00:00
s . updateClientCount ( ch , c . identifier , "Enforcing MODEs" )
2016-09-08 04:19:32 +00:00
}
}
2016-09-04 09:22:33 +00:00
}
2016-09-02 05:54:54 +00:00
}
}
2017-12-16 05:26:22 +00:00
func ( s * Server ) sendUsage ( cl * Client , command string ) {
2017-12-02 07:50:51 +00:00
command = strings . ToUpper ( command )
2017-12-16 05:26:22 +00:00
showAll := false
if command == "ALL" {
command = COMMAND_HELP
showAll = true
}
2017-12-02 07:50:51 +00:00
2017-12-16 05:26:22 +00:00
commands := make ( [ ] string , 0 , len ( commandUsage ) )
for cmd := range commandUsage {
2017-12-02 07:50:51 +00:00
commands = append ( commands , cmd )
}
sort . Strings ( commands )
2017-12-15 01:39:18 +00:00
var printedLabel bool
2017-12-02 07:50:51 +00:00
var usage [ ] string
2017-12-15 01:39:18 +00:00
if command == COMMAND_HELP {
// Print all commands
var perms [ ] int
for permission := range permissionLabels {
perms = append ( perms , permission )
}
sort . Ints ( perms )
for i := 0 ; i < 2 ; i ++ {
serverLabel := ""
if i == 1 {
serverLabel = "Server "
}
for _ , permission := range perms {
printedLabel = false
for _ , cmd := range commands {
2017-12-16 05:26:22 +00:00
if ( i == 0 && containsString ( serverCommands , cmd ) ) || ( i == 1 && ! containsString ( serverCommands , cmd ) ) || cl . permissionRequired ( cmd ) != permission {
2017-12-15 01:39:18 +00:00
continue
2017-12-16 05:26:22 +00:00
}
if ! showAll && ! cl . canUse ( cmd , "" ) {
2017-12-15 01:39:18 +00:00
continue
}
2017-12-02 07:50:51 +00:00
2017-12-15 01:39:18 +00:00
if ! printedLabel {
cl . sendNotice ( serverLabel + permissionLabels [ permission ] + " Commands" )
printedLabel = true
}
2017-12-16 05:26:22 +00:00
usage = commandUsage [ cmd ]
2017-12-15 01:39:18 +00:00
cl . sendMessage ( cmd + " " + usage [ 0 ] )
for _ , ul := range usage [ 1 : ] {
cl . sendMessage ( " " + ul )
}
}
}
}
} else {
2017-12-16 05:26:22 +00:00
if usage , ok := commandUsage [ command ] ; ok {
cl . sendMessage ( command + " " + usage [ 0 ] )
2017-12-15 01:39:18 +00:00
for _ , ul := range usage [ 1 : ] {
cl . sendMessage ( " " + ul )
}
2017-12-16 05:26:22 +00:00
} else {
cl . sendError ( "Unknown command specified" )
2017-12-02 07:50:51 +00:00
}
}
}
2017-12-15 01:39:18 +00:00
func ( s * Server ) ban ( channel string , iphash string , accountid int64 , expires int64 , reason string ) error {
if channel == "" || expires < 0 {
log . Println ( "invalid args" )
return nil
}
b := DBBan { }
if iphash != "" {
b = DBBan { Channel : generateHash ( channel ) , Type : BAN_TYPE_ADDRESS , Target : iphash , Expires : expires }
err := db . AddBan ( b )
if err != nil {
return err
}
}
if accountid > 0 {
b = DBBan { Channel : generateHash ( channel ) , Type : BAN_TYPE_ACCOUNT , Target : fmt . Sprintf ( "%d" , accountid ) , Expires : expires }
err := db . AddBan ( b )
if err != nil {
return err
}
}
if b . Channel == "" {
log . Println ( "blank chan" )
return nil
}
ch := channel
rs := formatAction ( "Banned" , reason )
if channel == CHANNEL_SERVER {
ch = ""
rs = formatAction ( "Killed" , reason )
}
cls := s . getClients ( ch )
for _ , cl := range cls {
if cl == nil {
continue
}
if ( iphash != "" && cl . iphash == iphash ) || ( accountid > 0 && cl . account == accountid ) {
if channel == CHANNEL_SERVER {
s . killClient ( cl , rs )
} else {
s . partChannel ( channel , cl . identifier , rs )
}
}
}
return nil
}
2017-12-02 07:50:51 +00:00
func ( s * Server ) handleUserCommand ( client string , command string , params [ ] string ) {
cl := s . getClient ( client )
if cl == nil {
return
}
var err error
command = strings . ToUpper ( command )
2017-12-11 11:01:24 +00:00
ch := ""
if len ( params ) > 0 {
ch = params [ 0 ]
}
if ! cl . canUse ( command , ch ) {
cl . accessDenied ( cl . permissionRequired ( command ) )
return
}
2017-12-02 07:50:51 +00:00
switch command {
case COMMAND_HELP :
cmd := command
if len ( params ) > 0 {
cmd = params [ 0 ]
}
s . sendUsage ( cl , cmd )
return
case COMMAND_INFO :
2017-12-11 11:01:24 +00:00
// TODO: when channel is supplied, send whether it is registered and show a notice that it is dropping soon if no super admins have logged in in X days
cl . sendMessage ( "Server info: AnonIRCd https://github.com/sageru-6ch/anonircd" )
2017-12-02 07:50:51 +00:00
return
case COMMAND_REGISTER :
if len ( params ) == 0 {
s . sendUsage ( cl , command )
return
}
// TODO: Only alphanumeric username
2017-12-16 05:26:22 +00:00
// TODO: allow duplicate usernames, only return error on existing username and password
2017-12-02 07:50:51 +00:00
case COMMAND_IDENTIFY :
if len ( params ) == 0 || len ( params ) > 2 {
s . sendUsage ( cl , command )
return
}
username := cl . nick
password := params [ 0 ]
if len ( params ) == 2 {
username = params [ 0 ]
password = params [ 1 ]
}
2017-12-11 11:01:24 +00:00
authSuccess := cl . identify ( username , password )
2017-12-02 07:50:51 +00:00
if authSuccess {
cl . sendNotice ( "Identified successfully" )
2017-12-11 11:01:24 +00:00
if cl . globalPermission ( ) >= PERMISSION_VIP {
s . joinChannel ( CHANNEL_SERVER , cl . identifier )
}
for clch := range s . getChannels ( cl . identifier ) {
banned , br := cl . isBanned ( clch )
if banned {
reason := "Banned"
if br != "" {
reason += ": " + br
}
s . partChannel ( clch , cl . identifier , reason )
return
}
2017-12-02 07:50:51 +00:00
}
} else {
cl . sendNotice ( "Failed to identify, incorrect username/password" )
}
case COMMAND_USERNAME :
if cl . account == 0 {
cl . sendError ( "You must identify before using that command" )
}
2017-12-11 11:01:24 +00:00
if len ( params ) == 0 || len ( params ) < 4 {
2017-12-02 07:50:51 +00:00
s . sendUsage ( cl , command )
return
}
if params [ 2 ] != params [ 3 ] {
cl . sendError ( "Unable to change username, new usernames don't match" )
return
}
// TODO: Alphanumeric username
2017-12-11 11:01:24 +00:00
accid , err := db . Auth ( params [ 0 ] , params [ 1 ] )
2017-12-02 07:50:51 +00:00
if err != nil {
2017-12-11 11:01:24 +00:00
log . Panicf ( "%+v" , err )
2017-12-02 07:50:51 +00:00
}
if accid == 0 {
cl . sendError ( "Unable to change username, incorrect username/password supplied" )
return
}
2017-12-11 11:01:24 +00:00
err = db . SetUsername ( accid , params [ 2 ] , params [ 1 ] )
2017-12-02 07:50:51 +00:00
if err != nil {
2017-12-11 11:01:24 +00:00
log . Panicf ( "%+v" , err )
2017-12-02 07:50:51 +00:00
}
cl . sendMessage ( "Username changed successfully" )
case COMMAND_PASSWORD :
2017-12-11 11:01:24 +00:00
if len ( params ) == 0 || len ( params ) < 4 {
2017-12-02 07:50:51 +00:00
s . sendUsage ( cl , command )
return
}
if params [ 2 ] != params [ 3 ] {
cl . sendError ( "Unable to change password, new passwords don't match" )
return
}
2017-12-11 11:01:24 +00:00
accid , err := db . Auth ( params [ 0 ] , params [ 1 ] )
2017-12-02 07:50:51 +00:00
if err != nil {
2017-12-11 11:01:24 +00:00
log . Panicf ( "%+v" , err )
2017-12-02 07:50:51 +00:00
}
if accid == 0 {
cl . sendError ( "Unable to change password, incorrect username/password supplied" )
return
}
2017-12-11 11:01:24 +00:00
err = db . SetPassword ( accid , params [ 0 ] , params [ 2 ] )
2017-12-02 07:50:51 +00:00
if err != nil {
2017-12-11 11:01:24 +00:00
log . Panicf ( "%+v" , err )
2017-12-02 07:50:51 +00:00
}
cl . sendMessage ( "Password changed successfully" )
2017-12-16 05:26:22 +00:00
case COMMAND_REVEAL , COMMAND_AUDIT :
2017-12-02 07:50:51 +00:00
// TODO: &#chan shows moderator audit log, & alone shows server admin audit log
if len ( params ) == 0 {
s . sendUsage ( cl , command )
return
}
ch := s . getChannel ( params [ 0 ] )
if ch == nil {
cl . sendError ( "Unable to reveal, invalid channel specified" )
return
}
page := 1
2017-12-15 01:39:18 +00:00
all := false
2017-12-02 07:50:51 +00:00
if len ( params ) > 1 {
page , err = strconv . Atoi ( params [ 1 ] )
if err != nil || page < - 1 || page == 0 {
2017-12-15 01:39:18 +00:00
if strings . ToLower ( params [ 1 ] ) == "all" {
page = - 1
all = true
} else {
cl . sendError ( "Unable to reveal, invalid page specified" )
return
}
2017-12-02 07:50:51 +00:00
}
}
2017-12-11 11:01:24 +00:00
if len ( params ) > 2 {
2017-12-15 01:39:18 +00:00
if strings . ToLower ( params [ 2 ] ) == "all" {
all = true
2017-12-11 11:01:24 +00:00
}
}
2017-12-16 05:26:22 +00:00
if command == COMMAND_REVEAL {
s . revealChannelLog ( params [ 0 ] , cl . identifier , page , all )
} else {
// TODO: Audit
}
2017-12-02 07:50:51 +00:00
case COMMAND_KICK :
if len ( params ) < 2 {
s . sendUsage ( cl , command )
return
}
ch := s . getChannel ( params [ 0 ] )
if ch == nil {
cl . sendError ( "Unable to kick, invalid channel specified" )
return
}
rcl := s . revealClient ( params [ 0 ] , params [ 1 ] )
if rcl == nil {
cl . sendError ( "Unable to kick, client not found or no longer connected" )
return
}
reason := "Kicked"
if len ( params ) > 2 {
reason = fmt . Sprintf ( "%s: %s" , reason , strings . Join ( params [ 2 : ] , " " ) )
}
s . partChannel ( ch . identifier , rcl . identifier , reason )
cl . sendMessage ( fmt . Sprintf ( "Kicked %s %s" , params [ 0 ] , params [ 1 ] ) )
2017-12-15 01:39:18 +00:00
case COMMAND_BAN , COMMAND_KILL :
2017-12-02 07:50:51 +00:00
if len ( params ) < 3 {
s . sendUsage ( cl , command )
return
}
ch := s . getChannel ( params [ 0 ] )
if ch == nil {
2017-12-15 01:39:18 +00:00
cl . sendError ( fmt . Sprintf ( "Unable to %s, invalid channel specified" , strings . ToLower ( command ) ) )
2017-12-02 07:50:51 +00:00
return
}
rcl := s . revealClient ( params [ 0 ] , params [ 1 ] )
if rcl == nil {
2017-12-15 01:39:18 +00:00
cl . sendError ( fmt . Sprintf ( "Unable to %s, client not found or no longer connected" , strings . ToLower ( command ) ) )
2017-12-02 07:50:51 +00:00
return
}
2017-12-15 01:39:18 +00:00
expires := parseDuration ( params [ 2 ] )
if expires < 0 {
cl . sendError ( fmt . Sprintf ( "Unable to %s, invalid duration supplied" , strings . ToLower ( command ) ) )
return
} else if expires > 0 {
expires = time . Now ( ) . Unix ( ) + expires
}
2017-12-11 11:01:24 +00:00
2017-12-15 01:39:18 +00:00
reason := ""
2017-12-02 07:50:51 +00:00
if len ( params ) > 3 {
2017-12-15 01:39:18 +00:00
strings . Join ( params [ 3 : ] , " " )
2017-12-02 07:50:51 +00:00
}
2017-12-15 01:39:18 +00:00
bch := ch . identifier
if command == COMMAND_KILL {
bch = CHANNEL_SERVER
}
err := s . ban ( bch , rcl . iphash , rcl . account , expires , reason )
if err != nil {
cl . sendError ( fmt . Sprintf ( "Unable to %s, %v" , strings . ToLower ( command ) , err ) )
2017-12-02 07:50:51 +00:00
return
}
2017-12-15 01:39:18 +00:00
cl . sendMessage ( fmt . Sprintf ( "%sed %s %s" , strings . ToLower ( command ) , params [ 0 ] , params [ 1 ] ) )
2017-12-02 07:50:51 +00:00
case COMMAND_STATS :
cl . sendMessage ( fmt . Sprintf ( "%d clients in %d channels" , s . clientCount ( ) , s . channelCount ( ) ) )
case COMMAND_REHASH :
err := s . reload ( )
if err != nil {
cl . sendError ( err . Error ( ) )
} else {
cl . sendMessage ( "Reloaded configuration" )
}
case COMMAND_UPGRADE :
// TODO
}
}
2016-09-02 09:24:27 +00:00
func ( s * Server ) handlePrivmsg ( channel string , client string , message string ) {
2017-12-02 07:50:51 +00:00
cl := s . getClient ( client )
if cl == nil {
return
}
if strings . ToLower ( channel ) == "anonirc" {
params := strings . Split ( message , " " )
if len ( params ) > 0 && len ( params [ 0 ] ) > 0 {
var otherparams [ ] string
if len ( params ) > 1 {
otherparams = params [ 1 : ]
}
s . handleUserCommand ( client , params [ 0 ] , otherparams )
}
return
} else if channel == "" || ! validChannelPrefix ( channel ) {
return
} else if ! s . inChannel ( channel , client ) {
2016-09-06 06:49:05 +00:00
return // Not in channel
2016-09-02 05:54:54 +00:00
}
2017-04-15 21:31:16 +00:00
ch := s . getChannel ( channel )
if ch == nil {
return
}
2017-12-02 07:50:51 +00:00
s . updateClientCount ( channel , "" , "" )
2017-07-26 03:41:15 +00:00
2017-09-13 05:51:51 +00:00
ch . clients . Range ( func ( k , v interface { } ) bool {
2017-12-02 07:50:51 +00:00
chcl := s . getClient ( k . ( string ) )
if chcl != nil && chcl . identifier != client {
2017-12-15 01:39:18 +00:00
chcl . write ( & prefixAnonymous , irc . PRIVMSG , [ ] string { channel , message } )
2016-09-02 05:54:54 +00:00
}
2017-09-13 05:51:51 +00:00
return true
} )
2017-12-02 07:50:51 +00:00
ch . Log ( cl , "CHAT" , message )
}
2016-09-02 05:54:54 +00:00
func ( s * Server ) handleRead ( c * Client ) {
for {
2017-12-02 07:50:51 +00:00
if c . state == ENTITY_STATE_TERMINATING {
return
}
c . conn . SetReadDeadline ( time . Now ( ) . Add ( 300 * time . Second ) )
2017-04-15 21:31:16 +00:00
2017-09-13 05:51:51 +00:00
if _ , ok := s . clients . Load ( c . identifier ) ; ! ok {
2017-12-15 01:39:18 +00:00
s . killClient ( c , "" )
2017-04-15 21:31:16 +00:00
return
}
2017-04-15 22:45:53 +00:00
msg , err := c . reader . Decode ( )
2017-04-19 18:25:33 +00:00
if msg == nil || err != nil {
2017-12-02 07:50:51 +00:00
// Error decoding message, client probably disconnected
2017-12-15 01:39:18 +00:00
s . killClient ( c , "" )
2016-09-02 05:54:54 +00:00
return
}
2017-12-16 05:26:22 +00:00
if debugMode && ( verbose || len ( msg . Command ) < 4 || ( msg . Command [ 0 : 4 ] != irc . PING && msg . Command [ 0 : 4 ] != irc . PONG ) ) {
2017-12-02 07:50:51 +00:00
log . Printf ( "%s -> %s" , c . identifier , msg )
2016-09-02 05:54:54 +00:00
}
2017-12-02 07:50:51 +00:00
if msg . Command == irc . NICK && c . nick == "*" && len ( msg . Params ) > 0 && len ( msg . Params [ 0 ] ) > 0 && msg . Params [ 0 ] != "" && msg . Params [ 0 ] != "*" {
2016-09-02 05:54:54 +00:00
c . nick = strings . Trim ( msg . Params [ 0 ] , "\"" )
2016-09-16 01:25:52 +00:00
} else if msg . Command == irc . USER && c . user == "" && len ( msg . Params ) >= 3 && msg . Params [ 0 ] != "" && msg . Params [ 2 ] != "" {
2016-09-02 05:54:54 +00:00
c . user = strings . Trim ( msg . Params [ 0 ] , "\"" )
c . host = strings . Trim ( msg . Params [ 2 ] , "\"" )
2017-12-02 07:50:51 +00:00
c . writeMessage ( irc . RPL_WELCOME , [ ] string { "Welcome to AnonIRC " + c . getPrefix ( ) . String ( ) } )
c . writeMessage ( irc . RPL_YOURHOST , [ ] string { "Your host is AnonIRC, running version AnonIRCd https://github.com/sageru-6ch/anonircd" } )
c . writeMessage ( irc . RPL_CREATED , [ ] string { fmt . Sprintf ( "This server was created %s" , time . Unix ( s . created , 0 ) . UTC ( ) ) } )
c . writeMessage ( strings . Join ( [ ] string { irc . RPL_MYINFO , c . nick , "AnonIRC" , "AnonIRCd" , CLIENT_MODES , CHANNEL_MODES , CHANNEL_MODES_ARG } , " " ) , [ ] string { } )
2016-09-02 05:54:54 +00:00
motdsplit := strings . Split ( motd , "\n" )
for i , motdmsg := range motdsplit {
var motdcode string
2016-09-16 01:25:52 +00:00
if i == 0 {
2016-09-02 05:54:54 +00:00
motdcode = irc . RPL_MOTDSTART
2016-09-16 01:25:52 +00:00
} else if i < len ( motdsplit ) - 1 {
2016-09-02 05:54:54 +00:00
motdcode = irc . RPL_MOTD
} else {
motdcode = irc . RPL_ENDOFMOTD
}
2017-12-02 07:50:51 +00:00
c . writeMessage ( motdcode , [ ] string { " " + motdmsg } )
2016-09-02 05:54:54 +00:00
}
2017-12-11 11:01:24 +00:00
s . joinChannel ( CHANNEL_LOBBY , c . identifier )
if c . globalPermission ( ) >= PERMISSION_VIP {
s . joinChannel ( CHANNEL_SERVER , c . identifier )
2017-12-02 07:50:51 +00:00
}
} else if msg . Command == irc . PASS && c . user == "" && len ( msg . Params ) > 0 && len ( msg . Params [ 0 ] ) > 0 {
// TODO: Add auth and multiple failed attempts ban
authSuccess := false
psplit := strings . SplitN ( msg . Params [ 0 ] , ":" , 2 )
if len ( psplit ) == 2 {
2017-12-11 11:01:24 +00:00
authSuccess = c . identify ( psplit [ 0 ] , psplit [ 1 ] )
2017-12-02 07:50:51 +00:00
}
if ! authSuccess {
c . sendPasswordIncorrect ( )
2017-12-15 01:39:18 +00:00
s . killClient ( c , "" )
2017-12-02 07:50:51 +00:00
}
} else if msg . Command == irc . CAP && len ( msg . Params ) > 0 && len ( msg . Params [ 0 ] ) > 0 && msg . Params [ 0 ] == irc . CAP_LS {
c . writeMessage ( irc . CAP , [ ] string { irc . CAP_LS , "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 . writeMessage ( 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 . writeMessage ( irc . CAP , [ ] string { irc . CAP_LIST , strings . Join ( caps , " " ) } )
} else if msg . Command == irc . PING {
c . writeMessage ( irc . PONG + " AnonIRC" , [ ] string { msg . Trailing ( ) } )
} else if c . user == "" {
return // Client must send USER before issuing remaining commands
} else if msg . Command == irc . WHOIS && len ( msg . Params ) > 0 && len ( msg . Params [ 0 ] ) >= len ( prefixAnonymous . Name ) && strings . ToLower ( msg . Params [ 0 ] [ : len ( prefixAnonymous . Name ) ] ) == strings . ToLower ( prefixAnonymous . Name ) {
2017-06-08 07:39:01 +00:00
go func ( ) {
whoisindex := 1
2017-12-02 07:50:51 +00:00
if len ( msg . Params [ 0 ] ) > len ( prefixAnonymous . Name ) {
whoisindex , err = strconv . Atoi ( msg . Params [ 0 ] [ len ( prefixAnonymous . Name ) : ] )
2017-06-08 07:39:01 +00:00
if err != nil || whoisindex <= 1 {
return
}
}
2017-12-02 07:50:51 +00:00
whoisnick := prefixAnonymous . Name
2017-06-08 07:39:01 +00:00
if whoisindex > 1 {
whoisnick += strconv . Itoa ( whoisindex )
}
easteregg := s . readOdyssey ( whoisindex )
if easteregg == "" {
easteregg = "I am the owner of my actions, heir of my actions, actions are the womb (from which I have sprung), actions are my relations, actions are my protection. Whatever actions I do, good or bad, of these I shall become the heir."
}
2017-12-02 07:50:51 +00:00
c . writeMessage ( irc . RPL_AWAY , [ ] string { whoisnick , easteregg } )
c . writeMessage ( irc . RPL_ENDOFWHOIS , [ ] string { whoisnick , "End of /WHOIS list." } )
2017-06-08 07:39:01 +00:00
} ( )
2017-12-02 07:50:51 +00:00
} else if msg . Command == irc . ISON {
c . writeMessage ( irc . RPL_ISON , [ ] string { "" } )
2017-06-08 01:53:08 +00:00
} else if msg . Command == irc . AWAY {
if len ( msg . Params ) > 0 {
2017-12-02 07:50:51 +00:00
c . writeMessage ( irc . RPL_NOWAWAY , [ ] string { "You have been marked as being away" } )
2017-06-08 01:53:08 +00:00
} else {
2017-12-02 07:50:51 +00:00
c . writeMessage ( irc . RPL_UNAWAY , [ ] string { "You are no longer marked as being away" } )
2017-06-08 01:53:08 +00:00
}
2016-09-16 01:25:52 +00:00
} else if msg . Command == irc . LIST {
2016-09-07 00:38:11 +00:00
chans := make ( map [ string ] int )
2017-09-13 05:51:51 +00:00
s . channels . Range ( func ( k , v interface { } ) bool {
key := k . ( string )
ch := v . ( * Channel )
2017-04-15 21:31:16 +00:00
2017-12-11 11:01:24 +00:00
if key [ 0 ] == '&' && c . globalPermission ( ) < PERMISSION_VIP {
return true
}
if ch == nil || ch . hasMode ( "p" ) || ch . hasMode ( "s" ) {
return true
2016-09-06 07:04:36 +00:00
}
2017-09-13 05:51:51 +00:00
2017-12-11 11:01:24 +00:00
chans [ key ] = s . clientsInChannel ( key , c . identifier )
2017-09-13 05:51:51 +00:00
return true
} )
2016-09-07 00:38:11 +00:00
2017-12-02 07:50:51 +00:00
c . writeMessage ( irc . RPL_LISTSTART , [ ] string { "Channel" , "Users Name" } )
2016-09-07 00:38:11 +00:00
for _ , pl := range sortMapByValues ( chans ) {
2017-04-15 21:31:16 +00:00
ch := s . getChannel ( pl . Key )
2017-12-02 07:50:51 +00:00
c . writeMessage ( irc . RPL_LIST , [ ] string { pl . Key , strconv . Itoa ( pl . Value ) , "[" + ch . printModes ( ch . getModes ( ) , nil ) + "] " + ch . topic } )
2016-09-06 07:04:36 +00:00
}
2017-12-02 07:50:51 +00:00
c . writeMessage ( irc . RPL_LISTEND , [ ] string { "End of /LIST" } )
} else if msg . Command == irc . JOIN && len ( msg . Params ) > 0 && len ( msg . Params [ 0 ] ) > 0 {
2016-09-02 09:24:27 +00:00
for _ , channel := range strings . Split ( msg . Params [ 0 ] , "," ) {
s . joinChannel ( channel , c . identifier )
}
2017-12-02 07:50:51 +00:00
} else if msg . Command == irc . NAMES && len ( msg . Params ) > 0 && len ( msg . Params [ 0 ] ) > 0 {
2016-09-02 09:24:27 +00:00
for _ , channel := range strings . Split ( msg . Params [ 0 ] , "," ) {
s . sendNames ( channel , c . identifier )
}
2017-12-02 07:50:51 +00:00
} else if msg . Command == irc . WHO && len ( msg . Params ) > 0 && len ( msg . Params [ 0 ] ) > 0 {
2016-09-08 04:19:32 +00:00
var ccount int
2016-09-02 09:24:27 +00:00
for _ , channel := range strings . Split ( msg . Params [ 0 ] , "," ) {
if s . inChannel ( channel , c . identifier ) {
2017-12-02 07:50:51 +00:00
ccount = s . clientsInChannel ( channel , c . identifier )
2016-09-06 06:49:05 +00:00
for i := 0 ; i < ccount ; i ++ {
2016-09-02 09:24:27 +00:00
var prfx * irc . Prefix
2016-09-06 06:49:05 +00:00
if i == 0 {
2016-09-02 09:24:27 +00:00
prfx = c . getPrefix ( )
} else {
prfx = s . getAnonymousPrefix ( i )
}
2017-12-02 07:50:51 +00:00
c . writeMessage ( irc . RPL_WHOREPLY , [ ] string { channel , prfx . User , prfx . Host , "AnonIRC" , prfx . Name , "H" , "0 " + prefixAnonymous . Name } )
2016-09-02 05:54:54 +00:00
}
2017-12-02 07:50:51 +00:00
c . writeMessage ( irc . RPL_ENDOFWHO , [ ] string { channel , "End of /WHO list." } )
2016-09-02 05:54:54 +00:00
}
}
2016-09-16 01:25:52 +00:00
} else if msg . Command == irc . MODE {
2017-12-16 05:26:22 +00:00
s . handleMode ( c , msg . Params )
2016-09-16 01:25:52 +00:00
} else if msg . Command == irc . TOPIC && len ( msg . Params ) > 0 && len ( msg . Params [ 0 ] ) > 0 {
2017-09-14 18:59:54 +00:00
if len ( msg . Params ) == 1 {
s . sendTopic ( msg . Params [ 0 ] , c . identifier , false )
} else {
s . handleTopic ( msg . Params [ 0 ] , c . identifier , strings . Join ( msg . Params [ 1 : ] , " " ) )
}
2016-09-16 01:25:52 +00:00
} else if msg . Command == irc . PRIVMSG && len ( msg . Params ) > 0 && len ( msg . Params [ 0 ] ) > 0 {
2016-09-02 09:24:27 +00:00
s . handlePrivmsg ( msg . Params [ 0 ] , c . identifier , msg . Trailing ( ) )
2017-12-02 07:50:51 +00:00
} else if msg . Command == irc . PART && len ( msg . Params ) > 0 && len ( msg . Params [ 0 ] ) > 0 {
2016-09-02 09:24:27 +00:00
for _ , channel := range strings . Split ( msg . Params [ 0 ] , "," ) {
2016-09-06 06:49:05 +00:00
s . partChannel ( channel , c . identifier , "" )
2016-09-02 09:24:27 +00:00
}
2016-09-16 01:25:52 +00:00
} else if msg . Command == irc . QUIT {
2017-12-15 01:39:18 +00:00
s . killClient ( c , "" )
2017-12-02 07:50:51 +00:00
} else {
s . handleUserCommand ( c . identifier , msg . Command , msg . Params )
2016-09-02 05:54:54 +00:00
}
}
}
2017-09-09 02:22:12 +00:00
func ( s * Server ) handleWrite ( c * Client ) {
2017-12-02 07:50:51 +00:00
for {
select {
case msg := <- c . writebuffer :
if c . state == ENTITY_STATE_TERMINATING {
continue
}
2017-09-09 02:22:12 +00:00
2017-12-02 07:50:51 +00:00
c . wg . Add ( 1 )
addnick := false
if _ , err := strconv . Atoi ( msg . Command ) ; err == nil {
addnick = true
} else if msg . Command == irc . CAP {
addnick = true
}
2017-09-09 02:22:12 +00:00
2017-12-02 07:50:51 +00:00
if addnick {
msg . Params = append ( [ ] string { c . nick } , msg . Params ... )
}
2017-09-09 02:22:12 +00:00
2017-12-16 05:26:22 +00:00
if debugMode && ( verbose || len ( msg . Command ) < 4 || ( msg . Command [ 0 : 4 ] != irc . PING && msg . Command [ 0 : 4 ] != irc . PONG ) ) {
2017-12-02 07:50:51 +00:00
log . Printf ( "%s <- %s" , c . identifier , msg )
}
c . writer . Encode ( msg )
c . wg . Done ( )
case <- c . terminate :
close ( c . writebuffer )
return
2017-09-09 02:22:12 +00:00
}
}
}
2016-09-06 06:49:05 +00:00
func ( s * Server ) handleConnection ( conn net . Conn , ssl bool ) {
2016-09-02 05:54:54 +00:00
defer conn . Close ( )
2016-09-04 09:22:33 +00:00
var identifier string
2017-04-15 21:31:16 +00:00
2016-09-04 09:22:33 +00:00
for {
identifier = randomIdentifier ( )
2017-09-13 05:51:51 +00:00
if _ , ok := s . clients . Load ( identifier ) ; ! ok {
2016-09-04 09:22:33 +00:00
break
}
}
2017-09-13 05:51:51 +00:00
client := NewClient ( identifier , conn , ssl )
2017-12-11 11:01:24 +00:00
banned := true
reason := ""
if client != nil {
banned , reason = client . isBanned ( "" )
}
if banned {
// TODO: Send banned message
_ = reason
2017-12-02 07:50:51 +00:00
return // Banned
}
2017-09-13 05:51:51 +00:00
s . clients . Store ( client . identifier , client )
2016-09-02 07:49:36 +00:00
2017-09-09 02:22:12 +00:00
go s . handleWrite ( client )
2017-09-13 05:51:51 +00:00
s . handleRead ( client ) // Block until the connection is closed
2017-04-29 21:36:33 +00:00
2017-12-15 01:39:18 +00:00
s . killClient ( client , "" )
2017-04-29 21:36:33 +00:00
}
2017-12-15 01:39:18 +00:00
func ( s * Server ) killClient ( c * Client , reason string ) {
2017-12-02 07:50:51 +00:00
if c == nil || c . state == ENTITY_STATE_TERMINATING {
2017-04-29 21:36:33 +00:00
return
}
c . state = ENTITY_STATE_TERMINATING
2017-12-02 07:50:51 +00:00
select {
case c . terminate <- true :
if _ , ok := s . clients . Load ( c . identifier ) ; ok {
2017-12-15 01:39:18 +00:00
s . partAllChannels ( c . identifier , reason )
2017-12-02 07:50:51 +00:00
}
c . wg . Wait ( )
default :
2017-04-29 21:36:33 +00:00
}
2016-09-02 05:54:54 +00:00
}
2016-09-02 07:49:36 +00:00
func ( s * Server ) listenPlain ( ) {
for {
2016-09-16 06:12:25 +00:00
listen , err := net . Listen ( "tcp" , ":6667" )
2016-09-02 07:49:36 +00:00
if err != nil {
2017-06-08 07:39:01 +00:00
log . Printf ( "Failed to listen: %v" , err )
2016-09-16 06:12:25 +00:00
time . Sleep ( 1 * time . Minute )
2016-09-02 07:49:36 +00:00
continue
}
2016-09-16 06:12:25 +00:00
log . Println ( "Listening on 6667" )
accept :
for {
select {
2017-09-27 21:21:18 +00:00
case <- s . restartplain :
2016-09-16 06:12:25 +00:00
break accept
default :
conn , err := listen . Accept ( )
if err != nil {
log . Println ( "Error accepting connection:" , err )
continue
}
go s . handleConnection ( conn , true )
}
}
listen . Close ( )
2016-09-02 05:54:54 +00:00
}
}
2016-09-02 07:49:36 +00:00
func ( s * Server ) listenSSL ( ) {
2016-09-16 06:12:25 +00:00
for {
if s . config . SSLCert == "" {
time . Sleep ( 1 * time . Minute )
return // SSL is disabled
}
2016-09-02 05:54:54 +00:00
2016-09-16 06:12:25 +00:00
cert , err := tls . LoadX509KeyPair ( s . config . SSLCert , s . config . SSLKey )
if err != nil {
2017-06-08 07:39:01 +00:00
log . Printf ( "Failed to load SSL certificate: %v" , err )
2016-09-16 06:12:25 +00:00
time . Sleep ( 1 * time . Minute )
continue
}
2016-09-02 05:54:54 +00:00
2016-09-16 06:12:25 +00:00
listen , err := tls . Listen ( "tcp" , ":6697" , & tls . Config { Certificates : [ ] tls . Certificate { cert } } )
2016-09-02 05:54:54 +00:00
if err != nil {
2017-06-08 07:39:01 +00:00
log . Printf ( "Failed to listen: %v" , err )
2016-09-16 06:12:25 +00:00
time . Sleep ( 1 * time . Minute )
2016-09-02 05:54:54 +00:00
continue
}
2016-09-16 06:12:25 +00:00
log . Println ( "Listening on +6697" )
accept :
for {
select {
2017-09-27 21:21:18 +00:00
case <- s . restartssl :
2016-09-16 06:12:25 +00:00
break accept
default :
conn , err := listen . Accept ( )
if err != nil {
log . Println ( "Error accepting connection:" , err )
continue
}
go s . handleConnection ( conn , true )
}
}
listen . Close ( )
2016-09-02 05:54:54 +00:00
}
}
2016-09-02 07:49:36 +00:00
func ( s * Server ) pingClients ( ) {
for {
2017-09-13 05:51:51 +00:00
s . clients . Range ( func ( k , v interface { } ) bool {
cl := v . ( * Client )
2017-04-15 21:31:16 +00:00
if cl != nil {
2017-12-15 01:39:18 +00:00
cl . write ( nil , irc . PING , [ ] string { fmt . Sprintf ( "anonirc%d%d" , int32 ( time . Now ( ) . Unix ( ) ) , rand . Intn ( 1000 ) ) } )
2017-04-15 21:31:16 +00:00
}
2017-09-13 05:51:51 +00:00
return true
} )
2016-09-08 04:19:32 +00:00
time . Sleep ( 90 * time . Second )
2016-09-02 07:49:36 +00:00
}
}
2017-12-02 07:50:51 +00:00
func ( s * Server ) connectDatabase ( ) {
2017-12-11 11:01:24 +00:00
err := db . Connect ( s . config . DBDriver , s . config . DBSource )
2017-12-02 07:50:51 +00:00
if err != nil {
2017-12-11 11:01:24 +00:00
log . Panicf ( "%+v" , err )
2017-12-02 07:50:51 +00:00
}
}
func ( s * Server ) closeDatabase ( ) {
2017-12-11 11:01:24 +00:00
err := db . Close ( )
2017-12-02 07:50:51 +00:00
if err != nil {
2017-12-11 11:01:24 +00:00
log . Panicf ( "%+v" , err )
2017-12-02 07:50:51 +00:00
}
}
func ( s * Server ) loadConfig ( ) error {
if s . configfile == "" {
return errors . New ( "configuration file must be specified: anonircd -c /home/user/anonircd/anonircd.conf" )
}
if _ , err := os . Stat ( s . configfile ) ; err != nil {
return errors . New ( "unable to find configuration file " + s . configfile )
}
oldconfig := & * s . config
if _ , err := toml . DecodeFile ( s . configfile , & s . config ) ; err != nil {
if oldconfig != nil {
s . config = oldconfig
2016-09-16 06:12:25 +00:00
}
2017-12-02 07:50:51 +00:00
return errors . New ( fmt . Sprintf ( "Failed to read configuration file %s: %v" , s . configfile , err ) )
}
if s . config . DBDriver == "" || s . config . DBSource == "" {
if oldconfig != nil {
s . config = oldconfig
}
return errors . New ( fmt . Sprintf ( "DBDriver and DBSource must be configured in %s\nExample:\n\nDBDriver=\"sqlite3\"\nDBSource=\"/home/user/anonircd/anonircd.db\"" , s . configfile ) )
2016-09-16 06:12:25 +00:00
}
2017-12-02 07:50:51 +00:00
return nil
2016-09-16 06:12:25 +00:00
}
2017-12-02 07:50:51 +00:00
func ( s * Server ) reload ( ) error {
log . Println ( "Reloading configuration..." )
err := s . loadConfig ( )
if err != nil {
log . Println ( "Failed to reload configuration" )
return errors . Wrap ( err , "failed to reload configuration" )
}
log . Println ( "Reloaded configuration" )
2016-09-16 06:12:25 +00:00
s . restartplain <- true
s . restartssl <- true
2017-12-02 07:50:51 +00:00
return nil
2016-09-16 06:12:25 +00:00
}
2017-06-08 07:39:01 +00:00
func ( s * Server ) readOdyssey ( line int ) string {
s . odysseymutex . Lock ( )
defer s . odysseymutex . Unlock ( )
scanner := bufio . NewScanner ( s . odyssey )
currentline := 1
for scanner . Scan ( ) {
if currentline == line {
s . odyssey . Seek ( 0 , 0 )
return scanner . Text ( )
}
currentline ++
}
if err := scanner . Err ( ) ; err != nil {
log . Printf ( "Failed to read ODYSSEY: %v" , err )
return ""
}
s . odyssey . Seek ( 0 , 0 )
return ""
}
2016-09-02 07:49:36 +00:00
func ( s * Server ) listen ( ) {
go s . listenPlain ( )
go s . listenSSL ( )
s . pingClients ( )
}