2016-06-15 13:50:56 +02:00
// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2014-2015 Edmund Huber
// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
2012-04-07 20:44:59 +02:00
package irc
import (
2014-02-12 01:35:32 +01:00
"bufio"
2016-04-13 12:45:09 +02:00
"crypto/tls"
2016-09-05 10:45:42 +02:00
"encoding/base64"
2016-11-06 04:47:13 +01:00
"errors"
2014-02-09 02:10:04 +01:00
"fmt"
2012-04-07 20:44:59 +02:00
"log"
2017-01-14 10:52:47 +01:00
"math/rand"
2012-04-07 20:44:59 +02:00
"net"
2015-05-04 07:47:26 +02:00
"net/http"
2014-02-12 01:35:32 +01:00
"os"
2014-02-25 20:11:34 +01:00
"os/signal"
2016-06-17 14:17:42 +02:00
"strconv"
2014-02-18 03:10:52 +01:00
"strings"
2016-10-22 12:54:04 +02:00
"sync"
2014-03-02 20:36:00 +01:00
"syscall"
2012-04-18 07:21:41 +02:00
"time"
2014-03-13 01:52:25 +01:00
2016-06-17 14:17:42 +02:00
"github.com/DanielOaks/girc-go/ircmsg"
2016-08-19 15:21:52 +02:00
"github.com/tidwall/buntdb"
2016-06-17 14:17:42 +02:00
)
2014-03-08 23:20:36 +01:00
2016-10-23 15:05:00 +02:00
var (
// cached because this may be used lots
2017-01-12 08:40:01 +01:00
tooManyClientsMsg = ircmsg . MakeMessage ( nil , "" , "ERROR" , "Too many clients from your network" )
2016-10-23 15:05:00 +02:00
tooManyClientsBytes , _ = tooManyClientsMsg . Line ( )
2016-11-04 03:42:58 +01:00
bannedFromServerMsg = ircmsg . MakeMessage ( nil , "" , "ERROR" , "You are banned from this server (%s)" )
bannedFromServerBytes , _ = bannedFromServerMsg . Line ( )
2016-11-06 04:47:13 +01:00
2017-03-06 00:27:08 +01:00
errDbOutOfDate = errors . New ( "Database schema is old" )
2016-10-23 15:05:00 +02:00
)
2017-01-13 15:22:42 +01:00
// Limits holds the maximum limits for various things such as topic lengths.
2016-09-12 04:22:50 +02:00
type Limits struct {
2016-10-16 12:14:56 +02:00
AwayLen int
ChannelLen int
KickLen int
MonitorEntries int
NickLen int
TopicLen int
2016-10-23 16:50:18 +02:00
ChanListModes int
2017-01-13 15:22:42 +01:00
LineLen LineLenLimits
}
// LineLenLimits holds the maximum limits for IRC lines.
type LineLenLimits struct {
Tags int
Rest int
2016-09-12 04:22:50 +02:00
}
2016-10-22 12:54:04 +02:00
// ListenerInterface represents an interface for a listener.
type ListenerInterface struct {
Listener net . Listener
Events chan ListenerEvent
}
const (
// DestroyListener instructs the listener to destroy itself.
DestroyListener ListenerEventType = iota
// UpdateListener instructs the listener to update itself (grab new certs, etc).
UpdateListener = iota
)
// ListenerEventType is the type of event this is.
type ListenerEventType int
// ListenerEvent is an event that's passed to the listener.
type ListenerEvent struct {
Type ListenerEventType
NewConfig * tls . Config
}
// Server is the main Oragono server.
2012-04-07 20:44:59 +02:00
type Server struct {
2017-03-06 04:05:33 +01:00
accountAuthenticationEnabled bool
2017-03-06 00:43:52 +01:00
accountRegistration * AccountRegistration
accounts map [ string ] * ClientAccount
channels ChannelNameMap
checkIdent bool
clients * ClientLookupSet
commands chan Command
configFilename string
connectionLimits * ConnectionLimits
connectionLimitsMutex sync . Mutex // used when affecting the connection limiter, to make sure rehashing doesn't make things go out-of-whack
2017-03-06 04:05:33 +01:00
connectionThrottle * ConnectionThrottle
connectionThrottleMutex sync . Mutex // used when affecting the connection limiter, to make sure rehashing doesn't make things go out-of-whack
2017-03-06 00:43:52 +01:00
ctime time . Time
currentOpers map [ * Client ] bool
dlines * DLineManager
idle chan * Client
isupport * ISupportList
klines * KLineManager
limits Limits
listenerEventActMutex sync . Mutex
listeners map [ string ] ListenerInterface
listenerUpdateMutex sync . Mutex
2017-03-06 04:05:33 +01:00
logger * Logger
2017-03-06 00:43:52 +01:00
monitoring map [ string ] [ ] Client
motdLines [ ] string
name string
nameCasefolded string
networkName string
newConns chan clientConn
operators map [ string ] Oper
operclasses map [ string ] OperClass
password [ ] byte
passwords * PasswordManager
rehashMutex sync . Mutex
rehashSignal chan os . Signal
restAPI * RestAPIConfig
signals chan os . Signal
store * buntdb . DB
whoWas * WhoWasList
2012-04-18 03:16:57 +02:00
}
2014-03-13 01:52:25 +01:00
var (
2016-10-23 03:48:57 +02:00
// ServerExitSignals are the signals the server will exit on.
ServerExitSignals = [ ] os . Signal {
2016-08-14 06:13:01 +02:00
syscall . SIGINT ,
syscall . SIGTERM ,
syscall . SIGQUIT ,
}
2014-03-13 01:52:25 +01:00
)
2016-06-28 17:09:07 +02:00
type clientConn struct {
Conn net . Conn
IsTLS bool
}
2016-10-19 13:38:31 +02:00
// NewServer returns a new Oragono server.
2017-03-06 06:50:23 +01:00
func NewServer ( configFilename string , config * Config , logger * Logger ) ( * Server , error ) {
2016-10-11 15:51:46 +02:00
casefoldedName , err := Casefold ( config . Server . Name )
if err != nil {
2017-03-06 06:50:23 +01:00
return nil , fmt . Errorf ( "Server name isn't valid [%s]: %s" , config . Server . Name , err . Error ( ) )
2016-10-11 15:51:46 +02:00
}
2016-10-23 10:59:13 +02:00
// startup check that we have HELP entries for every command
for name := range Commands {
_ , exists := Help [ strings . ToLower ( name ) ]
if ! exists {
2017-03-06 06:50:23 +01:00
return nil , fmt . Errorf ( "Help entry does not exist for command %s" , name )
2016-10-23 10:59:13 +02:00
}
}
2017-03-06 00:43:52 +01:00
if config . Accounts . AuthenticationEnabled {
2016-10-22 14:18:41 +02:00
SupportedCapabilities [ SASL ] = true
}
2017-01-13 15:22:42 +01:00
if config . Limits . LineLen . Tags > 512 || config . Limits . LineLen . Rest > 512 {
2016-11-29 09:38:04 +01:00
SupportedCapabilities [ MaxLine ] = true
2017-01-13 15:22:42 +01:00
CapValues [ MaxLine ] = fmt . Sprintf ( "%d,%d" , config . Limits . LineLen . Tags , config . Limits . LineLen . Rest )
2016-11-29 09:38:04 +01:00
}
2016-10-23 02:47:11 +02:00
operClasses , err := config . OperatorClasses ( )
if err != nil {
2017-03-06 06:50:23 +01:00
return nil , fmt . Errorf ( "Error loading oper classes: %s" , err . Error ( ) )
2016-10-23 02:47:11 +02:00
}
opers , err := config . Operators ( operClasses )
if err != nil {
2017-03-06 06:50:23 +01:00
return nil , fmt . Errorf ( "Error loading operators: %s" , err . Error ( ) )
2016-10-23 02:47:11 +02:00
}
2016-10-23 15:05:00 +02:00
connectionLimits , err := NewConnectionLimits ( config . Server . ConnectionLimits )
if err != nil {
2017-03-06 06:50:23 +01:00
return nil , fmt . Errorf ( "Error loading connection limits: %s" , err . Error ( ) )
2016-10-23 15:05:00 +02:00
}
2017-01-12 08:40:01 +01:00
connectionThrottle , err := NewConnectionThrottle ( config . Server . ConnectionThrottle )
if err != nil {
2017-03-06 06:50:23 +01:00
return nil , fmt . Errorf ( "Error loading connection throttler: %s" , err . Error ( ) )
2017-01-12 08:40:01 +01:00
}
2016-10-23 15:05:00 +02:00
2012-12-09 21:51:50 +01:00
server := & Server {
2017-03-06 00:43:52 +01:00
accounts : make ( map [ string ] * ClientAccount ) ,
accountAuthenticationEnabled : config . Accounts . AuthenticationEnabled ,
channels : make ( ChannelNameMap ) ,
clients : NewClientLookupSet ( ) ,
commands : make ( chan Command ) ,
configFilename : configFilename ,
connectionLimits : connectionLimits ,
connectionThrottle : connectionThrottle ,
ctime : time . Now ( ) ,
currentOpers : make ( map [ * Client ] bool ) ,
idle : make ( chan * Client ) ,
2016-09-12 04:22:50 +02:00
limits : Limits {
2016-10-16 12:14:56 +02:00
AwayLen : int ( config . Limits . AwayLen ) ,
ChannelLen : int ( config . Limits . ChannelLen ) ,
KickLen : int ( config . Limits . KickLen ) ,
MonitorEntries : int ( config . Limits . MonitorEntries ) ,
NickLen : int ( config . Limits . NickLen ) ,
TopicLen : int ( config . Limits . TopicLen ) ,
2016-10-23 16:50:18 +02:00
ChanListModes : int ( config . Limits . ChanListModes ) ,
2017-01-13 15:22:42 +01:00
LineLen : LineLenLimits {
Tags : config . Limits . LineLen . Tags ,
Rest : config . Limits . LineLen . Rest ,
} ,
2016-09-12 04:22:50 +02:00
} ,
2016-10-22 12:54:04 +02:00
listeners : make ( map [ string ] ListenerInterface ) ,
2017-03-06 06:50:23 +01:00
logger : logger ,
2016-10-16 12:14:56 +02:00
monitoring : make ( map [ string ] [ ] Client ) ,
2016-10-16 04:54:15 +02:00
name : config . Server . Name ,
nameCasefolded : casefoldedName ,
2016-10-19 13:38:31 +02:00
networkName : config . Network . Name ,
2016-10-16 04:54:15 +02:00
newConns : make ( chan clientConn ) ,
2016-10-23 02:47:11 +02:00
operclasses : * operClasses ,
operators : opers ,
2016-10-23 03:48:57 +02:00
signals : make ( chan os . Signal , len ( ServerExitSignals ) ) ,
2016-10-22 13:20:08 +02:00
rehashSignal : make ( chan os . Signal , 1 ) ,
2016-11-06 02:05:29 +01:00
restAPI : & config . Server . RestAPI ,
2016-10-16 04:54:15 +02:00
whoWas : NewWhoWasList ( config . Limits . WhowasEntries ) ,
checkIdent : config . Server . CheckIdent ,
2012-12-15 23:34:20 +01:00
}
2014-02-09 19:07:40 +01:00
2016-08-19 15:21:52 +02:00
// open data store
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogDebug , "startup" , "Opening datastore" )
2016-08-19 15:21:52 +02:00
db , err := buntdb . Open ( config . Datastore . Path )
if err != nil {
2017-03-06 06:50:23 +01:00
return nil , fmt . Errorf ( "Failed to open datastore: %s" , err . Error ( ) )
2016-08-19 15:21:52 +02:00
}
2016-11-06 04:47:13 +01:00
server . store = db
// check db version
err = server . store . View ( func ( tx * buntdb . Tx ) error {
version , _ := tx . Get ( keySchemaVersion )
if version != latestDbSchema {
2017-03-06 06:50:23 +01:00
logger . Log ( LogError , "startup" , "server" , fmt . Sprintf ( "Database must be updated. Expected schema v%s, got v%s." , latestDbSchema , version ) )
2016-11-06 04:47:13 +01:00
return errDbOutOfDate
}
return nil
} )
if err != nil {
// close the db
db . Close ( )
2017-03-06 06:50:23 +01:00
return nil , errDbOutOfDate
2016-11-06 04:47:13 +01:00
}
2016-08-19 15:21:52 +02:00
2017-01-11 13:38:16 +01:00
// load *lines
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogDebug , "startup" , "Loading D/Klines" )
2016-11-04 03:42:58 +01:00
server . loadDLines ( )
2017-01-11 13:38:16 +01:00
server . loadKLines ( )
2016-11-04 03:42:58 +01:00
2016-09-05 10:45:42 +02:00
// load password manager
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogDebug , "startup" , "Loading passwords" )
2016-09-05 10:45:42 +02:00
err = server . store . View ( func ( tx * buntdb . Tx ) error {
saltString , err := tx . Get ( keySalt )
if err != nil {
return fmt . Errorf ( "Could not retrieve salt string: %s" , err . Error ( ) )
}
salt , err := base64 . StdEncoding . DecodeString ( saltString )
if err != nil {
return err
}
pwm := NewPasswordManager ( salt )
server . passwords = & pwm
return nil
} )
if err != nil {
2017-03-06 06:50:23 +01:00
return nil , fmt . Errorf ( "Could not load salt: %s" , err . Error ( ) )
2016-09-05 10:45:42 +02:00
}
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogDebug , "startup" , "Loading MOTD" )
2016-04-13 07:49:30 +02:00
if config . Server . MOTD != "" {
file , err := os . Open ( config . Server . MOTD )
if err == nil {
defer file . Close ( )
reader := bufio . NewReader ( file )
for {
line , err := reader . ReadString ( '\n' )
if err != nil {
break
}
line = strings . TrimRight ( line , "\r\n" )
2016-06-19 13:59:18 +02:00
// "- " is the required prefix for MOTD, we just add it here to make
// bursting it out to clients easier
line = fmt . Sprintf ( "- %s" , line )
2016-04-13 07:49:30 +02:00
server . motdLines = append ( server . motdLines , line )
}
}
}
2014-03-06 20:00:48 +01:00
if config . Server . Password != "" {
server . password = config . Server . PasswordBytes ( )
}
2014-03-01 23:34:51 +01:00
for _ , addr := range config . Server . Listen {
2016-10-22 12:54:04 +02:00
server . createListener ( addr , config . TLSListeners ( ) )
2014-02-26 00:57:35 +01:00
}
2015-05-04 07:47:26 +02:00
if config . Server . Wslisten != "" {
2016-04-28 12:41:26 +02:00
server . wslisten ( config . Server . Wslisten , config . Server . TLSListeners )
2015-05-04 07:47:26 +02:00
}
2016-09-04 11:25:33 +02:00
// registration
2017-03-06 00:43:52 +01:00
accountReg := NewAccountRegistration ( config . Accounts . Registration )
2016-09-04 11:25:33 +02:00
server . accountRegistration = & accountReg
2016-08-14 06:13:01 +02:00
// Attempt to clean up when receiving these signals.
2016-10-23 03:48:57 +02:00
signal . Notify ( server . signals , ServerExitSignals ... )
2016-10-22 13:20:08 +02:00
signal . Notify ( server . rehashSignal , syscall . SIGHUP )
2014-03-02 20:51:29 +01:00
2016-10-19 13:38:31 +02:00
server . setISupport ( )
2016-11-06 02:05:29 +01:00
// start API if enabled
if server . restAPI . Enabled {
2017-03-06 06:50:23 +01:00
logger . Log ( LogInfo , "startup" , "server" , fmt . Sprintf ( "%s rest API started on %s." , server . name , server . restAPI . Listen ) )
2016-11-06 02:05:29 +01:00
server . startRestAPI ( )
}
2017-03-06 06:50:23 +01:00
return server , nil
2016-10-19 13:38:31 +02:00
}
// setISupport sets up our RPL_ISUPPORT reply.
func ( server * Server ) setISupport ( ) {
2016-10-26 16:51:55 +02:00
maxTargetsString := strconv . Itoa ( maxTargets )
2016-04-12 07:38:42 +02:00
// add RPL_ISUPPORT tokens
server . isupport = NewISupportList ( )
2016-09-12 04:40:09 +02:00
server . isupport . Add ( "AWAYLEN" , strconv . Itoa ( server . limits . AwayLen ) )
2017-01-13 17:32:15 +01:00
server . isupport . Add ( "CASEMAPPING" , casemappingName )
2016-11-01 14:56:21 +01:00
server . isupport . Add ( "CHANMODES" , strings . Join ( [ ] string { ChannelModes { BanMask , ExceptMask , InviteMask } . String ( ) , "" , ChannelModes { UserLimit , Key } . String ( ) , ChannelModes { InviteOnly , Moderated , NoOutside , OpOnlyTopic , ChanRoleplaying , Secret } . String ( ) } , "," ) )
2016-10-16 12:14:56 +02:00
server . isupport . Add ( "CHANNELLEN" , strconv . Itoa ( server . limits . ChannelLen ) )
2016-04-12 07:38:42 +02:00
server . isupport . Add ( "CHANTYPES" , "#" )
server . isupport . Add ( "EXCEPTS" , "" )
server . isupport . Add ( "INVEX" , "" )
2016-09-12 04:40:09 +02:00
server . isupport . Add ( "KICKLEN" , strconv . Itoa ( server . limits . KickLen ) )
2016-10-23 16:50:18 +02:00
server . isupport . Add ( "MAXLIST" , fmt . Sprintf ( "beI:%s" , strconv . Itoa ( server . limits . ChanListModes ) ) )
2016-10-26 16:51:55 +02:00
server . isupport . Add ( "MAXTARGETS" , maxTargetsString )
2016-10-23 17:01:27 +02:00
server . isupport . Add ( "MODES" , "" )
2016-10-16 12:14:56 +02:00
server . isupport . Add ( "MONITOR" , strconv . Itoa ( server . limits . MonitorEntries ) )
2016-10-19 13:38:31 +02:00
server . isupport . Add ( "NETWORK" , server . networkName )
2016-10-16 12:14:56 +02:00
server . isupport . Add ( "NICKLEN" , strconv . Itoa ( server . limits . NickLen ) )
2016-04-14 01:35:36 +02:00
server . isupport . Add ( "PREFIX" , "(qaohv)~&@%+" )
2016-11-01 14:56:21 +01:00
server . isupport . Add ( "RPCHAN" , "E" )
server . isupport . Add ( "RPUSER" , "E" )
2016-10-22 16:45:51 +02:00
server . isupport . Add ( "STATUSMSG" , "~&@%+" )
2017-03-06 00:14:15 +01:00
server . isupport . Add ( "TARGMAX" , fmt . Sprintf ( "NAMES:1,LIST:1,KICK:1,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:" , maxTargetsString , maxTargetsString , maxTargetsString ) )
2016-09-12 04:40:09 +02:00
server . isupport . Add ( "TOPICLEN" , strconv . Itoa ( server . limits . TopicLen ) )
2016-09-04 11:25:33 +02:00
// account registration
if server . accountRegistration . Enabled {
// 'none' isn't shown in the REGCALLBACKS vars
2016-09-04 13:15:28 +02:00
var enabledCallbacks [ ] string
for _ , name := range server . accountRegistration . EnabledCallbacks {
2016-09-04 12:08:53 +02:00
if name != "*" {
2016-09-04 13:15:28 +02:00
enabledCallbacks = append ( enabledCallbacks , name )
2016-09-04 11:25:33 +02:00
}
}
server . isupport . Add ( "REGCOMMANDS" , "CREATE,VERIFY" )
2016-09-04 13:15:28 +02:00
server . isupport . Add ( "REGCALLBACKS" , strings . Join ( enabledCallbacks , "," ) )
2016-09-04 11:25:33 +02:00
server . isupport . Add ( "REGCREDTYPES" , "passphrase,certfp" )
}
2016-04-12 07:38:42 +02:00
server . isupport . RegenerateCachedReply ( )
2014-02-26 00:57:35 +01:00
}
2014-03-08 03:14:02 +01:00
func loadChannelList ( channel * Channel , list string , maskMode ChannelMode ) {
if list == "" {
return
}
2016-10-11 15:51:46 +02:00
channel . lists [ maskMode ] . AddAll ( strings . Split ( list , " " ) )
2014-03-08 03:14:02 +01:00
}
2017-03-06 00:27:08 +01:00
// Shutdown shuts down the server.
2014-03-02 20:36:00 +01:00
func ( server * Server ) Shutdown ( ) {
2016-09-05 11:39:16 +02:00
//TODO(dan): Make sure we disallow new nicks
2017-01-10 16:06:02 +01:00
server . clients . ByNickMutex . RLock ( )
2016-10-19 13:38:31 +02:00
for _ , client := range server . clients . ByNick {
2016-06-19 13:59:18 +02:00
client . Notice ( "Server is shutting down" )
2014-03-02 20:36:00 +01:00
}
2017-01-10 16:06:02 +01:00
server . clients . ByNickMutex . RUnlock ( )
2016-08-14 06:13:01 +02:00
2016-09-05 11:39:16 +02:00
if err := server . store . Close ( ) ; err != nil {
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogError , "shutdown" , fmt . Sprintln ( "Could not close datastore:" , err ) )
2016-08-14 06:13:01 +02:00
}
2014-03-02 20:36:00 +01:00
}
2016-10-23 03:48:57 +02:00
// Run starts the server.
2014-02-24 04:13:45 +01:00
func ( server * Server ) Run ( ) {
2016-09-05 14:14:20 +02:00
// defer closing db/store
defer server . store . Close ( )
2014-02-25 20:11:34 +01:00
done := false
for ! done {
2014-02-17 07:38:43 +01:00
select {
2014-02-25 20:11:34 +01:00
case <- server . signals :
2014-03-02 20:36:00 +01:00
server . Shutdown ( )
2014-02-25 20:11:34 +01:00
done = true
2016-10-22 13:20:08 +02:00
case <- server . rehashSignal :
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogInfo , "rehash" , "Rehashing due to SIGHUP" )
2016-10-22 13:20:08 +02:00
err := server . rehash ( )
if err != nil {
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogError , "rehash" , fmt . Sprintln ( "Failed to rehash:" , err . Error ( ) ) )
2016-10-22 13:20:08 +02:00
}
2014-02-20 22:03:33 +01:00
case conn := <- server . newConns :
2016-10-23 15:05:00 +02:00
// check connection limits
ipaddr := net . ParseIP ( IPString ( conn . Conn . RemoteAddr ( ) ) )
if ipaddr != nil {
2016-11-04 03:42:58 +01:00
// check DLINEs
isBanned , info := server . dlines . CheckIP ( ipaddr )
if isBanned {
banMessage := fmt . Sprintf ( bannedFromServerBytes , info . Reason )
if info . Time != nil {
2016-11-04 12:38:47 +01:00
banMessage += fmt . Sprintf ( " [%s]" , info . Time . Duration . String ( ) )
2016-11-04 03:42:58 +01:00
}
conn . Conn . Write ( [ ] byte ( banMessage ) )
conn . Conn . Close ( )
continue
}
// check connection limits
2016-10-23 15:05:00 +02:00
server . connectionLimitsMutex . Lock ( )
err := server . connectionLimits . AddClient ( ipaddr , false )
server . connectionLimitsMutex . Unlock ( )
2016-11-04 03:42:58 +01:00
if err != nil {
// too many connections from one client, tell the client and close the connection
// this might not show up properly on some clients, but our objective here is just to close it out before it has a load impact on us
conn . Conn . Write ( [ ] byte ( tooManyClientsBytes ) )
conn . Conn . Close ( )
2016-10-23 15:05:00 +02:00
continue
}
2016-11-04 03:42:58 +01:00
2017-01-12 08:40:01 +01:00
// check connection throttle
server . connectionThrottleMutex . Lock ( )
err = server . connectionThrottle . AddClient ( ipaddr )
server . connectionThrottleMutex . Unlock ( )
if err != nil {
// too many connections too quickly from client, tell them and close the connection
length := & IPRestrictTime {
Duration : server . connectionThrottle . BanDuration ,
Expires : time . Now ( ) . Add ( server . connectionThrottle . BanDuration ) ,
}
server . dlines . AddIP ( ipaddr , length , server . connectionThrottle . BanMessage , "Exceeded automated connection throttle" )
// reset ban on connectionThrottle
server . connectionThrottle . ResetFor ( ipaddr )
// this might not show up properly on some clients, but our objective here is just to close it out before it has a load impact on us
conn . Conn . Write ( [ ] byte ( server . connectionThrottle . BanMessageBytes ) )
conn . Conn . Close ( )
continue
}
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogDebug , "localconnect-ip" , fmt . Sprintf ( "Client connecting from %v" , ipaddr ) )
2016-11-04 03:42:58 +01:00
go NewClient ( server , conn . Conn , conn . IsTLS )
continue
2016-10-23 15:05:00 +02:00
}
2014-02-24 07:21:39 +01:00
2014-02-18 20:22:56 +01:00
case client := <- server . idle :
client . Idle ( )
2014-02-15 03:28:36 +01:00
}
2014-02-14 03:39:33 +01:00
}
}
2014-02-20 04:30:49 +01:00
//
2016-10-22 12:54:04 +02:00
// IRC protocol listeners
2014-02-20 04:30:49 +01:00
//
2016-10-23 03:48:57 +02:00
// createListener starts the given listeners.
func ( server * Server ) createListener ( addr string , tlsMap map [ string ] * tls . Config ) {
2016-10-11 15:51:46 +02:00
config , listenTLS := tlsMap [ addr ]
2016-04-13 12:45:09 +02:00
2016-10-23 03:48:57 +02:00
_ , alreadyExists := server . listeners [ addr ]
2016-10-22 12:54:04 +02:00
if alreadyExists {
2016-10-23 03:48:57 +02:00
log . Fatal ( server , "listener already exists:" , addr )
2016-10-22 12:54:04 +02:00
}
// make listener event channel
listenerEventChannel := make ( chan ListenerEvent , 1 )
// make listener
2014-03-01 23:34:51 +01:00
listener , err := net . Listen ( "tcp" , addr )
2012-04-07 20:44:59 +02:00
if err != nil {
2016-10-23 03:48:57 +02:00
log . Fatal ( server , "listen error: " , err )
2012-04-07 20:44:59 +02:00
}
2016-04-28 12:12:23 +02:00
tlsString := "plaintext"
if listenTLS {
2016-09-05 05:53:39 +02:00
config . ClientAuth = tls . RequestClientCert
2016-04-13 12:45:09 +02:00
listener = tls . NewListener ( listener , config )
2016-04-28 12:12:23 +02:00
tlsString = "TLS"
2016-04-13 12:45:09 +02:00
}
2016-10-22 12:54:04 +02:00
// throw our details to the server so we can be modified/killed later
li := ListenerInterface {
Events : listenerEventChannel ,
Listener : listener ,
}
2016-10-23 03:48:57 +02:00
server . listeners [ addr ] = li
2016-10-22 12:54:04 +02:00
// start listening
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogInfo , "listeners" , fmt . Sprintf ( "listening on %s using %s." , addr , tlsString ) )
2012-12-09 07:54:58 +01:00
2016-10-22 12:54:04 +02:00
// setup accept goroutine
2014-03-14 01:19:39 +01:00
go func ( ) {
for {
conn , err := listener . Accept ( )
2014-02-17 07:30:01 +01:00
2016-10-22 12:54:04 +02:00
if err == nil {
newConn := clientConn {
Conn : conn ,
IsTLS : listenTLS ,
}
2016-10-23 03:48:57 +02:00
server . newConns <- newConn
2016-06-28 17:09:07 +02:00
}
2016-10-22 12:54:04 +02:00
select {
2016-10-23 03:48:57 +02:00
case event := <- server . listeners [ addr ] . Events :
2016-10-22 13:01:19 +02:00
// this is used to confirm that whoever passed us this event has closed the existing listener correctly (in an attempt to get us to notice the event).
// this is required to keep REHASH from having a very small race possibility of killing the primary listener
2016-10-23 03:48:57 +02:00
server . listenerEventActMutex . Lock ( )
server . listenerEventActMutex . Unlock ( )
2016-10-22 13:01:19 +02:00
2016-10-22 12:54:04 +02:00
if event . Type == DestroyListener {
// listener should already be closed, this is just for safety
listener . Close ( )
return
} else if event . Type == UpdateListener {
// close old listener
listener . Close ( )
// make new listener
listener , err = net . Listen ( "tcp" , addr )
if err != nil {
2016-10-23 03:48:57 +02:00
log . Fatal ( server , "listen error: " , err )
2016-10-22 12:54:04 +02:00
}
tlsString := "plaintext"
if event . NewConfig != nil {
config = event . NewConfig
config . ClientAuth = tls . RequestClientCert
listener = tls . NewListener ( listener , config )
tlsString = "TLS"
}
// update server ListenerInterface
li . Listener = listener
2016-10-23 03:48:57 +02:00
server . listenerUpdateMutex . Lock ( )
server . listeners [ addr ] = li
server . listenerUpdateMutex . Unlock ( )
2016-10-22 12:54:04 +02:00
// print notice
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogInfo , "listeners" , fmt . Sprintf ( "updated listener %s using %s." , addr , tlsString ) )
2016-10-22 12:54:04 +02:00
}
default :
// no events waiting for us, fall-through and continue
}
2014-03-14 01:19:39 +01:00
}
} ( )
2012-12-09 07:54:58 +01:00
}
2015-05-04 07:47:26 +02:00
//
// websocket listen goroutine
//
2016-10-23 03:48:57 +02:00
func ( server * Server ) wslisten ( addr string , tlsMap map [ string ] * TLSListenConfig ) {
2015-05-04 07:47:26 +02:00
http . HandleFunc ( "/" , func ( w http . ResponseWriter , r * http . Request ) {
if r . Method != "GET" {
2017-03-06 06:50:23 +01:00
server . logger . Log ( LogError , "ws" , addr , fmt . Sprintf ( "%s method not allowed" , r . Method ) )
2015-05-04 07:47:26 +02:00
return
}
2015-06-07 00:11:59 +02:00
// We don't have any subprotocols, so if someone attempts to `new
// WebSocket(server, "subprotocol")` they'll break here, instead of
// getting the default, ambiguous, response from gorilla.
if v , ok := r . Header [ "Sec-Websocket-Protocol" ] ; ok {
http . Error ( w , fmt . Sprintf ( "WebSocket subprocotols (e.g. %s) not supported" , v ) , 400 )
}
2015-05-04 07:47:26 +02:00
ws , err := upgrader . Upgrade ( w , r , nil )
if err != nil {
2017-03-06 06:50:23 +01:00
server . logger . Log ( LogError , "ws" , addr , fmt . Sprintf ( "%s websocket upgrade error: %s" , server . name , err ) )
2015-05-04 07:47:26 +02:00
return
}
2016-06-28 17:09:07 +02:00
newConn := clientConn {
Conn : WSContainer { ws } ,
IsTLS : false , //TODO(dan): track TLS or not here properly
}
2016-10-23 03:48:57 +02:00
server . newConns <- newConn
2015-05-04 07:47:26 +02:00
} )
go func ( ) {
2016-04-28 12:41:26 +02:00
config , listenTLS := tlsMap [ addr ]
tlsString := "plaintext"
var err error
if listenTLS {
tlsString = "TLS"
}
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogInfo , "listeners" , fmt . Sprintf ( "websocket listening on %s using %s." , addr , tlsString ) )
2016-04-28 12:41:26 +02:00
if listenTLS {
err = http . ListenAndServeTLS ( addr , config . Cert , config . Key , nil )
} else {
err = http . ListenAndServe ( addr , nil )
}
2015-05-04 07:47:26 +02:00
if err != nil {
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogError , "listeners" , fmt . Sprintf ( "listenAndServe error [%s]: %s" , tlsString , err ) )
2015-05-04 07:47:26 +02:00
}
} ( )
}
2017-01-14 10:52:47 +01:00
// generateMessageID returns a network-unique message ID.
func ( server * Server ) generateMessageID ( ) string {
return fmt . Sprintf ( "%s-%s" , strconv . FormatInt ( time . Now ( ) . UTC ( ) . UnixNano ( ) , 10 ) , strconv . FormatInt ( rand . Int63 ( ) , 10 ) )
}
2014-02-15 03:28:36 +01:00
//
2012-12-09 07:54:58 +01:00
// server functionality
2014-02-15 03:28:36 +01:00
//
2012-12-09 07:54:58 +01:00
2016-10-23 03:48:57 +02:00
func ( server * Server ) tryRegister ( c * Client ) {
2014-03-13 01:52:25 +01:00
if c . registered || ! c . HasNick ( ) || ! c . HasUsername ( ) ||
( c . capState == CapNegotiating ) {
return
2012-12-15 23:34:20 +01:00
}
2017-01-11 13:38:16 +01:00
// check KLINEs
isBanned , info := server . klines . CheckMasks ( c . AllNickmasks ( ) ... )
if isBanned {
reason := info . Reason
if info . Time != nil {
reason += fmt . Sprintf ( " [%s]" , info . Time . Duration . String ( ) )
}
c . Send ( nil , "" , "ERROR" , fmt . Sprintf ( "You are banned from this server (%s)" , reason ) )
c . quitMessageSent = true
c . destroy ( )
return
}
// continue registration
2017-03-06 13:11:10 +01:00
server . logger . Log ( LogDebug , "localconnect" , fmt . Sprintf ( "Client registered [%s]" , c . nick ) )
2014-03-13 01:52:25 +01:00
c . Register ( )
2016-06-19 13:59:18 +02:00
// send welcome text
//NOTE(dan): we specifically use the NICK here instead of the nickmask
// see http://modern.ircdocs.horse/#rplwelcome-001 for details on why we avoid using the nickmask
2016-10-23 03:48:57 +02:00
c . Send ( nil , server . name , RPL_WELCOME , c . nick , fmt . Sprintf ( "Welcome to the Internet Relay Network %s" , c . nick ) )
c . Send ( nil , server . name , RPL_YOURHOST , c . nick , fmt . Sprintf ( "Your host is %s, running version %s" , server . name , Ver ) )
c . Send ( nil , server . name , RPL_CREATED , c . nick , fmt . Sprintf ( "This server was created %s" , server . ctime . Format ( time . RFC1123 ) ) )
2016-06-19 13:59:18 +02:00
//TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter
2016-10-23 03:48:57 +02:00
c . Send ( nil , server . name , RPL_MYINFO , c . nick , server . name , Ver , supportedUserModesString , supportedChannelModesString )
2016-04-12 07:38:42 +02:00
c . RplISupport ( )
2016-10-23 03:48:57 +02:00
server . MOTD ( c )
2016-10-11 15:51:46 +02:00
c . Send ( nil , c . nickMaskString , RPL_UMODEIS , c . nick , c . ModeString ( ) )
2012-12-15 23:34:20 +01:00
}
2017-03-06 00:27:08 +01:00
// MOTD serves the Message of the Day.
2014-02-12 00:33:02 +01:00
func ( server * Server ) MOTD ( client * Client ) {
2016-04-13 07:49:30 +02:00
if len ( server . motdLines ) < 1 {
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , ERR_NOMOTD , client . nick , "MOTD File is missing" )
2014-02-12 01:35:32 +01:00
return
}
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , RPL_MOTDSTART , client . nick , fmt . Sprintf ( "- %s Message of the day - " , server . name ) )
2016-04-13 07:49:30 +02:00
for _ , line := range server . motdLines {
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , RPL_MOTD , client . nick , line )
2014-02-12 01:35:32 +01:00
}
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , RPL_ENDOFMOTD , client . nick , "End of MOTD command" )
2014-02-12 00:33:02 +01:00
}
2012-12-15 23:34:20 +01:00
//
2014-03-01 04:21:33 +01:00
// registration commands
2012-12-15 23:34:20 +01:00
//
2016-06-17 14:17:42 +02:00
// PASS <password>
func passHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-06-19 13:59:18 +02:00
if client . registered {
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , ERR_ALREADYREGISTRED , client . nick , "You may not reregister" )
2016-06-17 14:17:42 +02:00
return false
}
2016-06-20 14:53:45 +02:00
// if no password exists, skip checking
if len ( server . password ) == 0 {
client . authorized = true
return false
}
2016-06-17 14:17:42 +02:00
// check the provided password
2016-06-19 13:59:18 +02:00
password := [ ] byte ( msg . Params [ 0 ] )
2016-06-17 14:17:42 +02:00
if ComparePassword ( server . password , password ) != nil {
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , ERR_PASSWDMISMATCH , client . nick , "Password incorrect" )
client . Send ( nil , server . name , "ERROR" , "Password incorrect" )
2016-06-17 14:17:42 +02:00
return true
2014-02-15 03:28:36 +01:00
}
2014-03-01 04:21:33 +01:00
client . authorized = true
2016-06-17 14:17:42 +02:00
return false
2012-12-15 23:34:20 +01:00
}
2016-06-17 14:17:42 +02:00
// USER <username> * 0 <realname>
func userHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-06-19 13:59:18 +02:00
if client . registered {
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , ERR_ALREADYREGISTRED , client . nick , "You may not reregister" )
2016-06-17 14:17:42 +02:00
return false
}
2014-03-01 04:21:33 +01:00
if ! client . authorized {
2016-06-19 13:59:18 +02:00
client . Quit ( "Bad password" )
2016-06-17 14:17:42 +02:00
return true
}
if client . username != "" && client . realname != "" {
return false
2014-03-01 04:21:33 +01:00
}
2014-03-07 02:44:37 +01:00
2016-06-30 15:55:44 +02:00
// confirm that username is valid
//
2016-10-11 15:51:46 +02:00
_ , err := CasefoldName ( msg . Params [ 0 ] )
if err != nil {
2016-06-30 15:55:44 +02:00
client . Send ( nil , "" , "ERROR" , "Malformed username" )
return true
}
2016-06-20 14:53:45 +02:00
if ! client . HasUsername ( ) {
2016-10-11 15:51:46 +02:00
client . username = "~" + msg . Params [ 0 ]
2016-10-16 12:35:50 +02:00
// don't bother updating nickmask here, it's not valid anyway
2016-06-17 14:17:42 +02:00
}
2016-08-14 03:59:33 +02:00
if client . realname == "" {
2016-06-19 13:59:18 +02:00
client . realname = msg . Params [ 3 ]
2016-06-17 14:17:42 +02:00
}
2014-02-17 07:20:42 +01:00
server . tryRegister ( client )
2016-06-19 13:59:18 +02:00
return false
2014-02-15 03:28:36 +01:00
}
2016-06-17 14:17:42 +02:00
// QUIT [<reason>]
func quitHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
reason := "Quit"
if len ( msg . Params ) > 0 {
reason += ": " + msg . Params [ 0 ]
}
2016-06-19 13:59:18 +02:00
client . Quit ( reason )
2016-06-17 14:17:42 +02:00
return true
2014-02-18 22:25:21 +01:00
}
2014-02-15 03:28:36 +01:00
//
// normal commands
//
2016-06-17 14:17:42 +02:00
// PING <server1> [<server2>]
func pingHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , "PONG" , msg . Params ... )
2016-06-22 14:04:13 +02:00
return false
2014-02-15 03:28:36 +01:00
}
2016-06-17 14:17:42 +02:00
// PONG <server> [ <server2> ]
func pongHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-06-22 14:04:13 +02:00
// client gets touched when they send this command, so we don't need to do anything
return false
2012-12-15 23:34:20 +01:00
}
2016-06-17 14:17:42 +02:00
// JOIN <channel>{,<channel>} [<key>{,<key>}]
// JOIN 0
func joinHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
// handle JOIN 0
if msg . Params [ 0 ] == "0" {
2014-02-17 07:20:42 +01:00
for channel := range client . channels {
2016-10-11 15:51:46 +02:00
channel . Part ( client , client . nickCasefolded )
2012-12-15 23:34:20 +01:00
}
2016-06-17 14:17:42 +02:00
return false
2012-12-17 04:13:53 +01:00
}
2016-06-17 14:17:42 +02:00
// handle regular JOINs
channels := strings . Split ( msg . Params [ 0 ] , "," )
var keys [ ] string
if len ( msg . Params ) > 1 {
keys = strings . Split ( msg . Params [ 1 ] , "," )
}
2016-10-11 15:51:46 +02:00
for i , name := range channels {
casefoldedName , err := CasefoldChannel ( name )
if err != nil {
2016-11-04 12:38:47 +01:00
if len ( name ) > 0 {
client . Send ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , name , "No such channel" )
}
2014-02-22 02:19:02 +01:00
continue
}
2016-10-11 15:51:46 +02:00
channel := server . channels . Get ( casefoldedName )
2014-02-22 02:19:02 +01:00
if channel == nil {
2016-10-19 13:38:31 +02:00
if len ( casefoldedName ) > server . limits . ChannelLen {
client . Send ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , name , "No such channel" )
continue
}
2016-06-19 13:59:18 +02:00
channel = NewChannel ( server , name , true )
2014-02-22 02:19:02 +01:00
}
2016-06-17 14:17:42 +02:00
var key string
if len ( keys ) > i {
key = keys [ i ]
}
2014-02-19 04:31:59 +01:00
channel . Join ( client , key )
2012-12-09 07:54:58 +01:00
}
2016-06-19 13:59:18 +02:00
return false
2012-12-09 07:54:58 +01:00
}
2016-06-17 14:17:42 +02:00
// PART <channel>{,<channel>} [<reason>]
func partHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
channels := strings . Split ( msg . Params [ 0 ] , "," )
2016-06-19 13:59:18 +02:00
var reason string //TODO(dan): if this isn't supplied here, make sure the param doesn't exist in the PART message sent to other users
2016-06-17 14:17:42 +02:00
if len ( msg . Params ) > 1 {
reason = msg . Params [ 1 ]
}
for _ , chname := range channels {
2016-10-11 15:51:46 +02:00
casefoldedChannelName , err := CasefoldChannel ( chname )
channel := server . channels . Get ( casefoldedChannelName )
2012-12-15 23:34:20 +01:00
2016-10-11 15:51:46 +02:00
if err != nil || channel == nil {
2016-11-04 12:38:47 +01:00
if len ( chname ) > 0 {
client . Send ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , chname , "No such channel" )
}
2012-12-15 23:34:20 +01:00
continue
}
2016-06-19 13:59:18 +02:00
channel . Part ( client , reason )
2012-12-09 07:54:58 +01:00
}
2016-06-19 13:59:18 +02:00
return false
2012-12-09 07:54:58 +01:00
}
2016-06-17 14:17:42 +02:00
// TOPIC <channel> [<topic>]
func topicHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-10-11 15:51:46 +02:00
name , err := CasefoldChannel ( msg . Params [ 0 ] )
channel := server . channels . Get ( name )
if err != nil || channel == nil {
2016-11-04 12:38:47 +01:00
if len ( msg . Params [ 0 ] ) > 0 {
client . Send ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , msg . Params [ 0 ] , "No such channel" )
}
2016-06-19 13:59:18 +02:00
return false
2012-12-15 23:34:20 +01:00
}
2016-06-17 14:17:42 +02:00
if len ( msg . Params ) > 1 {
channel . SetTopic ( client , msg . Params [ 1 ] )
2014-02-17 07:20:42 +01:00
} else {
channel . GetTopic ( client )
}
2016-06-19 13:59:18 +02:00
return false
2012-12-15 23:34:20 +01:00
}
2017-01-14 06:28:50 +01:00
func wordWrap ( text string , lineWidth int ) [ ] string {
2017-01-17 23:05:31 +01:00
var lines [ ] string
var cacheLine , cacheWord string
for _ , char := range text {
if ( char == ' ' || char == '-' ) && len ( cacheLine ) + len ( cacheWord ) + 1 < lineWidth {
cacheLine += cacheWord + string ( char )
cacheWord = ""
} else if len ( cacheLine ) + len ( cacheWord ) + 1 >= lineWidth {
if len ( cacheLine ) < ( lineWidth / 2 ) {
// there must be a really long word or something, just split on word boundary
cacheLine += cacheWord + string ( char )
cacheWord = ""
}
lines = append ( lines , cacheLine )
cacheLine = ""
2017-01-14 06:28:50 +01:00
} else {
2017-01-17 23:05:31 +01:00
cacheWord += string ( char )
2017-01-14 06:28:50 +01:00
}
}
2017-01-17 23:05:31 +01:00
if len ( cacheWord ) > 0 {
cacheLine += cacheWord
}
if len ( cacheLine ) > 0 {
lines = append ( lines , cacheLine )
}
2017-01-14 06:28:50 +01:00
2017-01-17 23:05:31 +01:00
return lines
2017-01-14 06:28:50 +01:00
}
// SplitMessage represents a message that's been split for sending.
type SplitMessage struct {
For512 [ ] string
ForMaxLine string
}
2017-01-14 12:48:57 +01:00
func ( server * Server ) splitMessage ( original string , origIs512 bool ) SplitMessage {
2017-01-14 06:28:50 +01:00
var newSplit SplitMessage
newSplit . ForMaxLine = original
2017-01-17 23:05:31 +01:00
if ! origIs512 {
2017-01-14 06:28:50 +01:00
newSplit . For512 = wordWrap ( original , 400 )
} else {
newSplit . For512 = [ ] string { original }
}
return newSplit
}
2016-06-17 14:17:42 +02:00
// PRIVMSG <target>{,<target>} <message>
func privmsgHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-10-16 04:54:15 +02:00
clientOnlyTags := GetClientOnlyTags ( msg . Tags )
2016-06-17 14:17:42 +02:00
targets := strings . Split ( msg . Params [ 0 ] , "," )
message := msg . Params [ 1 ]
2012-12-15 23:34:20 +01:00
2017-01-14 06:28:50 +01:00
// split privmsg
2017-01-14 12:48:57 +01:00
splitMsg := server . splitMessage ( message , ! client . capabilities [ MaxLine ] )
2017-01-14 06:28:50 +01:00
2016-10-26 16:51:55 +02:00
for i , targetString := range targets {
// max of four targets per privmsg
if i > maxTargets - 1 {
break
}
2016-10-22 16:45:51 +02:00
prefixes , targetString := SplitChannelMembershipPrefixes ( targetString )
lowestPrefix := GetLowestChannelModePrefix ( prefixes )
2016-11-04 12:38:47 +01:00
// eh, no need to notify them
if len ( targetString ) < 1 {
continue
}
2016-10-11 15:51:46 +02:00
target , err := CasefoldChannel ( targetString )
2016-10-16 04:59:36 +02:00
if err == nil {
2016-06-17 14:17:42 +02:00
channel := server . channels . Get ( target )
if channel == nil {
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , targetString , "No such channel" )
2016-06-17 14:17:42 +02:00
continue
}
2017-01-14 10:52:47 +01:00
msgid := server . generateMessageID ( )
channel . SplitPrivMsg ( msgid , lowestPrefix , clientOnlyTags , client , splitMsg )
2016-06-17 14:17:42 +02:00
} else {
2016-10-11 15:51:46 +02:00
target , err = CasefoldName ( targetString )
2016-06-17 14:17:42 +02:00
user := server . clients . Get ( target )
2016-10-11 15:51:46 +02:00
if err != nil || user == nil {
2016-11-04 12:38:47 +01:00
if len ( target ) > 0 {
client . Send ( nil , server . name , ERR_NOSUCHNICK , target , "No such nick" )
}
2016-06-19 13:59:18 +02:00
continue
2016-06-17 14:17:42 +02:00
}
2016-10-16 04:54:15 +02:00
if ! user . capabilities [ MessageTags ] {
clientOnlyTags = nil
}
2017-01-14 10:52:47 +01:00
msgid := server . generateMessageID ( )
user . SendSplitMsgFromClient ( msgid , client , clientOnlyTags , "PRIVMSG" , user . nick , splitMsg )
2016-10-22 14:29:01 +02:00
if client . capabilities [ EchoMessage ] {
2017-01-14 10:52:47 +01:00
client . SendSplitMsgFromClient ( msgid , client , clientOnlyTags , "PRIVMSG" , user . nick , splitMsg )
2016-10-22 14:29:01 +02:00
}
2016-06-17 14:17:42 +02:00
if user . flags [ Away ] {
2016-06-19 13:59:18 +02:00
//TODO(dan): possibly implement cooldown of away notifications to users
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , RPL_AWAY , user . nick , user . awayMessage )
2016-06-17 14:17:42 +02:00
}
}
2014-02-12 00:44:58 +01:00
}
2016-06-19 13:59:18 +02:00
return false
2012-04-07 20:44:59 +02:00
}
2012-12-13 08:27:17 +01:00
2017-01-14 06:52:32 +01:00
// TAGMSG <target>{,<target>}
func tagmsgHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
clientOnlyTags := GetClientOnlyTags ( msg . Tags )
// no client-only tags, so we can drop it
if clientOnlyTags == nil {
return false
}
targets := strings . Split ( msg . Params [ 0 ] , "," )
for i , targetString := range targets {
// max of four targets per privmsg
if i > maxTargets - 1 {
break
}
prefixes , targetString := SplitChannelMembershipPrefixes ( targetString )
lowestPrefix := GetLowestChannelModePrefix ( prefixes )
// eh, no need to notify them
if len ( targetString ) < 1 {
continue
}
target , err := CasefoldChannel ( targetString )
if err == nil {
channel := server . channels . Get ( target )
if channel == nil {
client . Send ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , targetString , "No such channel" )
continue
}
2017-01-14 10:52:47 +01:00
msgid := server . generateMessageID ( )
channel . TagMsg ( msgid , lowestPrefix , clientOnlyTags , client )
2017-01-14 06:52:32 +01:00
} else {
target , err = CasefoldName ( targetString )
user := server . clients . Get ( target )
if err != nil || user == nil {
if len ( target ) > 0 {
client . Send ( nil , server . name , ERR_NOSUCHNICK , target , "No such nick" )
}
continue
}
2017-01-14 10:52:47 +01:00
msgid := server . generateMessageID ( )
2017-01-14 06:52:32 +01:00
// end user can't receive tagmsgs
if ! user . capabilities [ MessageTags ] {
continue
}
2017-01-14 10:52:47 +01:00
user . SendFromClient ( msgid , client , clientOnlyTags , "TAGMSG" , user . nick )
2017-01-14 06:52:32 +01:00
if client . capabilities [ EchoMessage ] {
2017-01-14 10:52:47 +01:00
client . SendFromClient ( msgid , client , clientOnlyTags , "TAGMSG" , user . nick )
2017-01-14 06:52:32 +01:00
}
if user . flags [ Away ] {
//TODO(dan): possibly implement cooldown of away notifications to users
client . Send ( nil , server . name , RPL_AWAY , user . nick , user . awayMessage )
}
}
}
return false
}
2017-03-06 00:27:08 +01:00
// WhoisChannelsNames returns the common channel names between two users.
2016-04-14 14:33:38 +02:00
func ( client * Client ) WhoisChannelsNames ( target * Client ) [ ] string {
isMultiPrefix := target . capabilities [ MultiPrefix ]
var chstrs [ ] string
2014-02-20 07:20:34 +01:00
index := 0
for channel := range client . channels {
2017-01-10 17:09:08 +01:00
channel . membersMutex . RLock ( )
defer channel . membersMutex . RUnlock ( )
2016-04-14 14:33:38 +02:00
// channel is secret and the target can't see it
if ! target . flags [ Operator ] && channel . flags [ Secret ] && ! channel . members . Has ( target ) {
continue
}
2016-10-11 15:51:46 +02:00
chstrs = append ( chstrs , channel . members [ client ] . Prefixes ( isMultiPrefix ) + channel . name )
2016-04-14 14:33:38 +02:00
index ++
2014-02-20 07:20:34 +01:00
}
return chstrs
}
2016-06-17 14:17:42 +02:00
// WHOIS [ <target> ] <mask> *( "," <mask> )
func whoisHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-06-19 13:59:18 +02:00
var masksString string
2016-06-20 14:53:45 +02:00
//var target string
2014-02-09 02:43:59 +01:00
2016-06-17 14:17:42 +02:00
if len ( msg . Params ) > 1 {
2016-06-20 14:53:45 +02:00
//target = msg.Params[0]
2016-06-19 13:59:18 +02:00
masksString = msg . Params [ 1 ]
2016-06-17 14:17:42 +02:00
} else {
2016-06-19 13:59:18 +02:00
masksString = msg . Params [ 0 ]
2016-06-17 14:17:42 +02:00
}
2014-02-09 02:43:59 +01:00
2016-11-04 12:38:47 +01:00
if len ( strings . TrimSpace ( masksString ) ) < 1 {
client . Send ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , "No masks given" )
return false
}
2016-06-19 13:59:18 +02:00
if client . flags [ Operator ] {
masks := strings . Split ( masksString , "," )
for _ , mask := range masks {
2016-10-11 15:51:46 +02:00
casefoldedMask , err := Casefold ( mask )
if err != nil {
client . Send ( nil , client . server . name , ERR_NOSUCHNICK , mask , "No such nick" )
continue
}
matches := server . clients . FindAll ( casefoldedMask )
2016-06-19 13:59:18 +02:00
if len ( matches ) == 0 {
2016-10-11 15:51:46 +02:00
client . Send ( nil , client . server . name , ERR_NOSUCHNICK , mask , "No such nick" )
2016-06-19 13:59:18 +02:00
continue
}
for mclient := range matches {
2016-09-05 06:23:57 +02:00
client . getWhoisOf ( mclient )
2016-06-19 13:59:18 +02:00
}
2014-02-18 04:08:57 +01:00
}
2016-06-19 13:59:18 +02:00
} else {
2016-10-23 17:01:27 +02:00
// only get the first request
casefoldedMask , err := Casefold ( strings . Split ( masksString , "," ) [ 0 ] )
2016-10-11 15:51:46 +02:00
mclient := server . clients . Get ( casefoldedMask )
if err != nil || mclient == nil {
client . Send ( nil , client . server . name , ERR_NOSUCHNICK , masksString , "No such nick" )
2016-06-19 13:59:18 +02:00
// fall through, ENDOFWHOIS is always sent
} else {
client . getWhoisOf ( mclient )
2014-02-09 02:43:59 +01:00
}
}
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , RPL_ENDOFWHOIS , client . nick , masksString , "End of /WHOIS list" )
2016-06-19 13:59:18 +02:00
return false
}
func ( client * Client ) getWhoisOf ( target * Client ) {
2016-10-11 15:51:46 +02:00
client . Send ( nil , client . server . name , RPL_WHOISUSER , client . nick , target . nick , target . username , target . hostname , "*" , target . realname )
2017-01-13 02:05:58 +01:00
whoischannels := client . WhoisChannelsNames ( target )
if whoischannels != nil {
2017-01-23 00:03:49 +01:00
client . Send ( nil , client . server . name , RPL_WHOISCHANNELS , client . nick , target . nick , strings . Join ( whoischannels , " " ) )
2016-09-05 06:23:57 +02:00
}
2016-10-23 03:01:05 +02:00
if target . class != nil {
client . Send ( nil , client . server . name , RPL_WHOISOPERATOR , client . nick , target . nick , target . whoisLine )
2016-06-19 13:59:18 +02:00
}
2016-09-05 06:23:57 +02:00
if target . certfp != "" && ( client . flags [ Operator ] || client == target ) {
2016-10-11 15:51:46 +02:00
client . Send ( nil , client . server . name , RPL_WHOISCERTFP , client . nick , target . nick , fmt . Sprintf ( "has client certificate fingerprint %s" , target . certfp ) )
2016-06-19 13:59:18 +02:00
}
2016-10-11 15:51:46 +02:00
client . Send ( nil , client . server . name , RPL_WHOISIDLE , client . nick , target . nick , strconv . FormatUint ( target . IdleSeconds ( ) , 10 ) , strconv . FormatInt ( target . SignonTime ( ) , 10 ) , "seconds idle, signon time" )
2016-06-19 13:59:18 +02:00
}
2017-03-06 00:27:08 +01:00
// RplWhoReply returns the WHO reply between one user and another channel/user.
2016-06-19 13:59:18 +02:00
// <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ]
// :<hopcount> <real name>
func ( target * Client ) RplWhoReply ( channel * Channel , client * Client ) {
channelName := "*"
flags := ""
if client . flags [ Away ] {
flags = "G"
} else {
flags = "H"
}
if client . flags [ Operator ] {
flags += "*"
}
if channel != nil {
2017-01-10 17:09:08 +01:00
channel . membersMutex . RLock ( )
defer channel . membersMutex . RUnlock ( )
2016-06-19 13:59:18 +02:00
flags += channel . members [ client ] . Prefixes ( target . capabilities [ MultiPrefix ] )
2016-10-11 15:51:46 +02:00
channelName = channel . name
2016-06-19 13:59:18 +02:00
}
2016-10-16 13:28:59 +02:00
target . Send ( nil , target . server . name , RPL_WHOREPLY , target . nick , channelName , client . username , client . hostname , client . server . name , client . nick , flags , strconv . Itoa ( client . hops ) + " " + client . realname )
2014-02-09 02:43:59 +01:00
}
2014-02-09 03:14:39 +01:00
2014-03-06 20:56:32 +01:00
func whoChannel ( client * Client , channel * Channel , friends ClientSet ) {
2017-01-10 17:09:08 +01:00
channel . membersMutex . RLock ( )
defer channel . membersMutex . RUnlock ( )
2014-02-09 03:49:52 +01:00
for member := range channel . members {
2014-03-06 20:56:32 +01:00
if ! client . flags [ Invisible ] || friends [ client ] {
2014-02-20 07:20:34 +01:00
client . RplWhoReply ( channel , member )
2014-02-18 06:30:14 +01:00
}
2014-02-09 03:49:52 +01:00
}
}
2016-06-17 14:17:42 +02:00
// WHO [ <mask> [ "o" ] ]
func whoHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2014-03-06 20:56:32 +01:00
friends := client . Friends ( )
2016-06-17 14:17:42 +02:00
2016-10-11 15:51:46 +02:00
var mask string
2016-06-17 14:17:42 +02:00
if len ( msg . Params ) > 0 {
2016-10-11 15:51:46 +02:00
casefoldedMask , err := Casefold ( msg . Params [ 0 ] )
if err != nil {
client . Send ( nil , server . name , ERR_UNKNOWNERROR , "WHO" , "Mask isn't valid" )
return false
}
mask = casefoldedMask
2016-06-17 14:17:42 +02:00
}
//TODO(dan): is this used and would I put this param in the Modern doc?
// if not, can we remove it?
2016-06-20 14:53:45 +02:00
//var operatorOnly bool
//if len(msg.Params) > 1 && msg.Params[1] == "o" {
// operatorOnly = true
//}
2014-02-09 03:49:52 +01:00
2014-02-09 07:42:14 +01:00
if mask == "" {
2014-02-09 03:49:52 +01:00
for _ , channel := range server . channels {
2014-03-06 20:56:32 +01:00
whoChannel ( client , channel , friends )
2014-02-09 03:49:52 +01:00
}
2016-10-11 15:51:46 +02:00
} else if mask [ 0 ] == '#' {
2014-03-06 20:56:32 +01:00
// TODO implement wildcard matching
2016-06-19 13:59:18 +02:00
//TODO(dan): ^ only for opers
2014-02-26 05:17:26 +01:00
channel := server . channels . Get ( mask )
2014-02-09 03:49:52 +01:00
if channel != nil {
2014-03-06 20:56:32 +01:00
whoChannel ( client , channel , friends )
2014-02-09 03:49:52 +01:00
}
} else {
2014-03-06 20:56:32 +01:00
for mclient := range server . clients . FindAll ( mask ) {
2014-02-20 07:20:34 +01:00
client . RplWhoReply ( nil , mclient )
2014-02-09 03:49:52 +01:00
}
}
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , RPL_ENDOFWHO , client . nick , mask , "End of WHO list" )
2016-06-19 13:59:18 +02:00
return false
2014-02-09 03:49:52 +01:00
}
2014-02-09 19:07:40 +01:00
2016-06-17 14:17:42 +02:00
// OPER <name> <password>
func operHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-10-11 15:51:46 +02:00
name , err := CasefoldName ( msg . Params [ 0 ] )
if err != nil {
client . Send ( nil , server . name , ERR_PASSWDMISMATCH , client . nick , "Password incorrect" )
return true
}
2016-10-23 02:47:11 +02:00
hash := server . operators [ name ] . Pass
2016-06-19 13:59:18 +02:00
password := [ ] byte ( msg . Params [ 1 ] )
2014-02-09 19:07:40 +01:00
2016-10-11 15:51:46 +02:00
err = ComparePassword ( hash , password )
2016-06-17 14:17:42 +02:00
if ( hash == nil ) || ( err != nil ) {
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , ERR_PASSWDMISMATCH , client . nick , "Password incorrect" )
2016-06-17 14:17:42 +02:00
return true
2014-02-24 07:21:39 +01:00
}
2014-02-09 19:07:40 +01:00
2014-02-24 07:21:39 +01:00
client . flags [ Operator ] = true
2016-10-23 02:47:11 +02:00
client . operName = name
client . class = server . operators [ name ] . Class
server . currentOpers [ client ] = true
2016-10-23 03:01:05 +02:00
client . whoisLine = server . operators [ name ] . WhoisLine
2016-10-23 02:47:11 +02:00
2016-10-23 03:28:31 +02:00
// push new vhost if one is set
if len ( server . operators [ name ] . Vhost ) > 0 {
for fClient := range client . Friends ( ChgHost ) {
2017-01-14 12:48:57 +01:00
fClient . SendFromClient ( "" , client , nil , "CHGHOST" , client . username , server . operators [ name ] . Vhost )
2016-10-23 03:28:31 +02:00
}
2017-01-14 12:48:57 +01:00
// CHGHOST requires prefix nickmask to have original hostname, so do that before updating nickmask
client . vhost = server . operators [ name ] . Vhost
2016-10-23 03:28:31 +02:00
client . updateNickMask ( )
}
2016-10-23 02:47:11 +02:00
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , RPL_YOUREOPER , client . nick , "You are now an IRC operator" )
2016-06-19 13:59:18 +02:00
//TODO(dan): Should this be sent automagically as part of setting the flag/mode?
modech := ModeChanges { & ModeChange {
2014-04-15 17:49:52 +02:00
mode : Operator ,
op : Add ,
2016-06-19 13:59:18 +02:00
} }
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , "MODE" , client . nick , modech . String ( ) )
2016-06-19 13:59:18 +02:00
return false
2014-02-09 19:07:40 +01:00
}
2014-02-10 20:14:34 +01:00
2016-10-22 13:20:08 +02:00
// rehash reloads the config and applies the changes from the config file.
func ( server * Server ) rehash ( ) error {
2016-10-22 12:54:04 +02:00
// only let one REHASH go on at a time
server . rehashMutex . Lock ( )
2016-10-19 13:38:31 +02:00
config , err := LoadConfig ( server . configFilename )
if err != nil {
2017-01-12 08:40:01 +01:00
return fmt . Errorf ( "Error rehashing config file config: %s" , err . Error ( ) )
2016-10-19 13:38:31 +02:00
}
2016-11-29 09:38:04 +01:00
// line lengths cannot be changed after launching the server
2017-01-14 06:28:50 +01:00
if server . limits . LineLen . Tags != config . Limits . LineLen . Tags || server . limits . LineLen . Rest != config . Limits . LineLen . Rest {
2016-11-29 09:38:04 +01:00
return fmt . Errorf ( "Maximum line length (linelen) cannot be changed after launching the server, rehash aborted" )
}
2016-10-23 15:05:00 +02:00
// confirm connectionLimits are fine
connectionLimits , err := NewConnectionLimits ( config . Server . ConnectionLimits )
if err != nil {
2017-01-12 08:40:01 +01:00
return fmt . Errorf ( "Error rehashing config file connection-limits: %s" , err . Error ( ) )
}
// confirm connectionThrottler is fine
connectionThrottle , err := NewConnectionThrottle ( config . Server . ConnectionThrottle )
if err != nil {
return fmt . Errorf ( "Error rehashing config file connection-throttle: %s" , err . Error ( ) )
2016-10-23 15:05:00 +02:00
}
2016-10-23 02:47:11 +02:00
// confirm operator stuff all exists and is fine
operclasses , err := config . OperatorClasses ( )
if err != nil {
2017-01-12 08:40:01 +01:00
return fmt . Errorf ( "Error rehashing config file operclasses: %s" , err . Error ( ) )
2016-10-23 02:47:11 +02:00
}
opers , err := config . Operators ( operclasses )
if err != nil {
2017-01-12 08:40:01 +01:00
return fmt . Errorf ( "Error rehashing config file opers: %s" , err . Error ( ) )
2016-10-23 02:47:11 +02:00
}
for client := range server . currentOpers {
_ , exists := opers [ client . operName ]
if ! exists {
return fmt . Errorf ( "Oper [%s] no longer exists (used by client [%s])" , client . operName , client . nickMaskString )
}
}
2016-10-23 15:05:00 +02:00
// apply new connectionlimits
server . connectionLimitsMutex . Lock ( )
server . connectionLimits = connectionLimits
2017-01-12 08:40:01 +01:00
server . connectionThrottleMutex . Lock ( )
server . connectionThrottle = connectionThrottle
2016-10-23 15:05:00 +02:00
2017-01-10 16:06:02 +01:00
server . clients . ByNickMutex . RLock ( )
2016-10-23 15:05:00 +02:00
for _ , client := range server . clients . ByNick {
ipaddr := net . ParseIP ( IPString ( client . socket . conn . RemoteAddr ( ) ) )
if ipaddr != nil {
server . connectionLimits . AddClient ( ipaddr , true )
}
}
2017-01-10 16:06:02 +01:00
server . clients . ByNickMutex . RUnlock ( )
2016-10-23 15:05:00 +02:00
server . connectionLimitsMutex . Unlock ( )
2016-10-22 14:18:41 +02:00
// setup new and removed caps
addedCaps := make ( CapabilitySet )
removedCaps := make ( CapabilitySet )
// SASL
2017-03-06 00:43:52 +01:00
if config . Accounts . AuthenticationEnabled && ! server . accountAuthenticationEnabled {
2016-10-22 14:18:41 +02:00
// enabling SASL
SupportedCapabilities [ SASL ] = true
addedCaps [ SASL ] = true
}
2017-03-06 00:43:52 +01:00
if ! config . Accounts . AuthenticationEnabled && server . accountAuthenticationEnabled {
2016-10-22 14:18:41 +02:00
// disabling SASL
SupportedCapabilities [ SASL ] = false
removedCaps [ SASL ] = true
}
2017-03-06 00:43:52 +01:00
server . accountAuthenticationEnabled = config . Accounts . AuthenticationEnabled
2016-10-22 14:18:41 +02:00
// burst new and removed caps
var capBurstClients ClientSet
added := make ( map [ CapVersion ] string )
var removed string
if len ( addedCaps ) > 0 || len ( removedCaps ) > 0 {
capBurstClients = server . clients . AllWithCaps ( CapNotify )
added [ Cap301 ] = addedCaps . String ( Cap301 )
added [ Cap302 ] = addedCaps . String ( Cap302 )
// removed never has values
removed = removedCaps . String ( Cap301 )
}
for sClient := range capBurstClients {
if len ( addedCaps ) > 0 {
sClient . Send ( nil , server . name , "CAP" , sClient . nick , "NEW" , added [ sClient . capVersion ] )
}
if len ( removedCaps ) > 0 {
sClient . Send ( nil , server . name , "CAP" , sClient . nick , "DEL" , removed )
}
}
2016-10-19 13:38:31 +02:00
// set server options
server . limits = Limits {
AwayLen : int ( config . Limits . AwayLen ) ,
ChannelLen : int ( config . Limits . ChannelLen ) ,
KickLen : int ( config . Limits . KickLen ) ,
MonitorEntries : int ( config . Limits . MonitorEntries ) ,
NickLen : int ( config . Limits . NickLen ) ,
TopicLen : int ( config . Limits . TopicLen ) ,
2016-10-23 16:50:18 +02:00
ChanListModes : int ( config . Limits . ChanListModes ) ,
2016-10-19 13:38:31 +02:00
}
2016-10-23 02:47:11 +02:00
server . operclasses = * operclasses
server . operators = opers
2016-10-19 13:38:31 +02:00
server . checkIdent = config . Server . CheckIdent
// registration
2017-03-06 00:43:52 +01:00
accountReg := NewAccountRegistration ( config . Accounts . Registration )
2016-10-19 13:38:31 +02:00
server . accountRegistration = & accountReg
// set RPL_ISUPPORT
oldISupportList := server . isupport
server . setISupport ( )
newISupportReplies := oldISupportList . GetDifference ( server . isupport )
// push new info to all of our clients
2017-01-10 16:06:02 +01:00
server . clients . ByNickMutex . RLock ( )
2016-10-19 13:38:31 +02:00
for _ , sClient := range server . clients . ByNick {
for _ , tokenline := range newISupportReplies {
// ugly trickery ahead
2016-10-22 13:20:08 +02:00
sClient . Send ( nil , server . name , RPL_ISUPPORT , append ( [ ] string { sClient . nick } , tokenline ... ) ... )
2016-10-19 13:38:31 +02:00
}
}
2017-01-10 16:06:02 +01:00
server . clients . ByNickMutex . RUnlock ( )
2016-10-19 13:38:31 +02:00
2016-10-22 12:54:04 +02:00
// destroy old listeners
tlsListeners := config . TLSListeners ( )
for addr := range server . listeners {
var exists bool
for _ , newaddr := range config . Server . Listen {
if newaddr == addr {
exists = true
break
}
}
2016-10-22 13:01:19 +02:00
server . listenerEventActMutex . Lock ( )
2016-10-22 12:54:04 +02:00
if exists {
// update old listener
server . listeners [ addr ] . Events <- ListenerEvent {
Type : UpdateListener ,
NewConfig : tlsListeners [ addr ] ,
}
} else {
// destroy nonexistent listener
server . listeners [ addr ] . Events <- ListenerEvent {
Type : DestroyListener ,
}
}
// force listener to apply the event right away
server . listeners [ addr ] . Listener . Close ( )
2016-10-22 13:01:19 +02:00
server . listenerEventActMutex . Unlock ( )
2016-10-22 12:54:04 +02:00
}
for _ , newaddr := range config . Server . Listen {
_ , exists := server . listeners [ newaddr ]
if ! exists {
// make new listener
server . createListener ( newaddr , tlsListeners )
}
}
server . rehashMutex . Unlock ( )
2016-10-22 13:20:08 +02:00
return nil
}
// REHASH
func rehashHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogInfo , "rehash" , fmt . Sprintf ( "REHASH command used by %s" , client . nick ) )
2016-10-22 13:20:08 +02:00
err := server . rehash ( )
2016-10-22 12:54:04 +02:00
2016-10-22 13:20:08 +02:00
if err == nil {
client . Send ( nil , server . name , RPL_REHASHING , client . nick , "ircd.yaml" , "Rehashing" )
} else {
2017-03-06 11:15:28 +01:00
server . logger . Log ( LogError , "rehash" , fmt . Sprintln ( "Failed to rehash:" , err . Error ( ) ) )
2016-10-22 13:20:08 +02:00
client . Send ( nil , server . name , ERR_UNKNOWNERROR , client . nick , "REHASH" , err . Error ( ) )
}
2016-10-19 13:38:31 +02:00
return false
}
2016-06-17 14:17:42 +02:00
// AWAY [<message>]
func awayHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
var isAway bool
var text string
if len ( msg . Params ) > 0 {
2016-06-19 13:59:18 +02:00
isAway = true
text = msg . Params [ 0 ]
2016-09-12 04:40:09 +02:00
if len ( text ) > server . limits . AwayLen {
text = text [ : server . limits . AwayLen ]
}
2016-06-17 14:17:42 +02:00
}
if isAway {
2014-02-17 22:22:35 +01:00
client . flags [ Away ] = true
} else {
delete ( client . flags , Away )
}
2016-06-17 14:17:42 +02:00
client . awayMessage = text
2014-02-12 00:44:58 +01:00
2014-04-15 17:49:52 +02:00
var op ModeOp
2014-02-17 22:22:35 +01:00
if client . flags [ Away ] {
2014-04-15 17:49:52 +02:00
op = Add
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , RPL_NOWAWAY , client . nick , "You have been marked as being away" )
2014-02-12 00:44:58 +01:00
} else {
2014-04-15 17:49:52 +02:00
op = Remove
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , RPL_UNAWAY , client . nick , "You are no longer marked as being away" )
2014-02-12 00:44:58 +01:00
}
2016-06-19 13:59:18 +02:00
//TODO(dan): Should this be sent automagically as part of setting the flag/mode?
modech := ModeChanges { & ModeChange {
2014-04-15 17:49:52 +02:00
mode : Away ,
op : op ,
2016-06-19 13:59:18 +02:00
} }
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , "MODE" , client . nick , client . nick , modech . String ( ) )
2016-09-12 03:56:20 +02:00
// dispatch away-notify
2016-10-13 10:08:08 +02:00
for friend := range client . Friends ( AwayNotify ) {
2016-09-12 03:56:20 +02:00
if client . flags [ Away ] {
2017-01-14 12:48:57 +01:00
friend . SendFromClient ( "" , client , nil , "AWAY" , client . awayMessage )
2016-09-12 03:56:20 +02:00
} else {
2017-01-14 12:48:57 +01:00
friend . SendFromClient ( "" , client , nil , "AWAY" )
2016-09-12 03:56:20 +02:00
}
}
2016-06-19 13:59:18 +02:00
return false
2014-02-12 00:44:58 +01:00
}
2014-02-12 00:58:54 +01:00
2016-06-17 14:17:42 +02:00
// ISON <nick>{ <nick>}
func isonHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-06-19 13:59:18 +02:00
var nicks = msg . Params
2014-02-12 00:58:54 +01:00
2016-10-11 15:51:46 +02:00
var err error
var casefoldedNick string
2014-02-12 00:58:54 +01:00
ison := make ( [ ] string , 0 )
2016-06-17 14:17:42 +02:00
for _ , nick := range nicks {
2016-10-11 15:51:46 +02:00
casefoldedNick , err = CasefoldName ( nick )
if err != nil {
continue
}
if iclient := server . clients . Get ( casefoldedNick ) ; iclient != nil {
ison = append ( ison , iclient . nick )
2014-02-12 00:58:54 +01:00
}
}
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , RPL_ISON , client . nick , strings . Join ( nicks , " " ) )
2016-06-19 13:59:18 +02:00
return false
2014-02-12 00:58:54 +01:00
}
2014-02-12 01:35:32 +01:00
2016-06-17 14:17:42 +02:00
// MOTD [<target>]
func motdHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
//TODO(dan): hook this up when we have multiple servers I guess???
2016-06-20 14:53:45 +02:00
//var target string
//if len(msg.Params) > 0 {
// target = msg.Params[0]
//}
2016-06-17 14:17:42 +02:00
2016-06-19 13:59:18 +02:00
server . MOTD ( client )
return false
2014-02-12 01:35:32 +01:00
}
2014-02-12 02:11:59 +01:00
2016-06-17 14:17:42 +02:00
// NOTICE <target>{,<target>} <message>
func noticeHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-10-16 04:54:15 +02:00
clientOnlyTags := GetClientOnlyTags ( msg . Tags )
2016-06-19 13:59:18 +02:00
targets := strings . Split ( msg . Params [ 0 ] , "," )
message := msg . Params [ 1 ]
2016-06-17 14:17:42 +02:00
2017-01-14 06:28:50 +01:00
// split privmsg
2017-01-14 12:48:57 +01:00
splitMsg := server . splitMessage ( message , ! client . capabilities [ MaxLine ] )
2017-01-14 06:28:50 +01:00
2016-10-26 16:51:55 +02:00
for i , targetString := range targets {
// max of four targets per privmsg
if i > maxTargets - 1 {
break
}
2016-10-22 16:45:51 +02:00
prefixes , targetString := SplitChannelMembershipPrefixes ( targetString )
lowestPrefix := GetLowestChannelModePrefix ( prefixes )
2016-10-11 15:51:46 +02:00
target , cerr := CasefoldChannel ( targetString )
if cerr == nil {
2016-06-19 13:59:18 +02:00
channel := server . channels . Get ( target )
if channel == nil {
// errors silently ignored with NOTICE as per RFC
continue
}
2017-01-14 10:52:47 +01:00
msgid := server . generateMessageID ( )
channel . SplitNotice ( msgid , lowestPrefix , clientOnlyTags , client , splitMsg )
2016-06-19 13:59:18 +02:00
} else {
2016-10-11 15:51:46 +02:00
target , err := CasefoldName ( targetString )
if err != nil {
continue
}
2016-06-19 13:59:18 +02:00
user := server . clients . Get ( target )
if user == nil {
// errors silently ignored with NOTICE as per RFC
continue
}
2016-10-16 04:54:15 +02:00
if ! user . capabilities [ MessageTags ] {
clientOnlyTags = nil
}
2017-01-14 10:52:47 +01:00
msgid := server . generateMessageID ( )
user . SendSplitMsgFromClient ( msgid , client , clientOnlyTags , "NOTICE" , user . nick , splitMsg )
2016-10-22 14:29:01 +02:00
if client . capabilities [ EchoMessage ] {
2017-01-14 10:52:47 +01:00
client . SendSplitMsgFromClient ( msgid , client , clientOnlyTags , "NOTICE" , user . nick , splitMsg )
2016-10-22 14:29:01 +02:00
}
2014-02-12 02:11:59 +01:00
}
}
2016-06-19 13:59:18 +02:00
return false
2014-02-17 02:23:47 +01:00
}
2014-02-17 08:29:11 +01:00
2016-06-17 14:17:42 +02:00
// KICK <channel>{,<channel>} <user>{,<user>} [<comment>]
func kickHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-06-19 13:59:18 +02:00
channels := strings . Split ( msg . Params [ 0 ] , "," )
users := strings . Split ( msg . Params [ 1 ] , "," )
2016-06-17 14:17:42 +02:00
if ( len ( channels ) != len ( users ) ) && ( len ( users ) != 1 ) {
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , ERR_NEEDMOREPARAMS , client . nick , "KICK" , "Not enough parameters" )
2016-06-17 14:17:42 +02:00
return false
}
2016-12-01 09:10:38 +01:00
var kicks [ ] [ ] string
2016-06-17 14:17:42 +02:00
for index , channel := range channels {
if len ( users ) == 1 {
2016-12-01 09:10:38 +01:00
kicks = append ( kicks , [ ] string { channel , users [ 0 ] } )
2016-06-17 14:17:42 +02:00
} else {
2016-12-01 09:10:38 +01:00
kicks = append ( kicks , [ ] string { channel , users [ index ] } )
2016-06-17 14:17:42 +02:00
}
}
var comment string
if len ( msg . Params ) > 2 {
comment = msg . Params [ 2 ]
}
2016-12-01 09:10:38 +01:00
for _ , info := range kicks {
chname := info [ 0 ]
nickname := info [ 1 ]
2016-10-11 15:51:46 +02:00
casefoldedChname , err := CasefoldChannel ( chname )
channel := server . channels . Get ( casefoldedChname )
if err != nil || channel == nil {
client . Send ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , chname , "No such channel" )
2014-02-17 08:29:11 +01:00
continue
}
2016-10-11 15:51:46 +02:00
casefoldedNickname , err := CasefoldName ( nickname )
target := server . clients . Get ( casefoldedNickname )
if err != nil || target == nil {
client . Send ( nil , server . name , ERR_NOSUCHNICK , nickname , "No such nick" )
2014-02-17 08:29:11 +01:00
continue
}
2016-04-14 01:35:36 +02:00
// make sure client has privs to kick the given user
2016-06-30 07:43:27 +02:00
//TODO(dan): split this into a separate function that checks if users have privs
// over other users, useful for things like -aoh as well
2017-01-11 03:16:51 +01:00
channel . membersMutex . Lock ( )
2017-01-10 17:09:08 +01:00
2016-04-14 01:35:36 +02:00
var hasPrivs bool
for _ , mode := range ChannelPrivModes {
if channel . members [ client ] [ mode ] {
hasPrivs = true
// admins cannot kick other admins
if mode == ChannelAdmin && channel . members [ target ] [ ChannelAdmin ] {
hasPrivs = false
}
break
} else if channel . members [ target ] [ mode ] {
break
}
}
if hasPrivs {
2016-06-17 14:17:42 +02:00
if comment == "" {
2016-06-30 07:43:27 +02:00
comment = nickname
2016-06-17 14:17:42 +02:00
}
2017-01-11 03:16:51 +01:00
channel . kickNoMutex ( client , target , comment )
2016-04-14 01:35:36 +02:00
} else {
2016-10-11 15:51:46 +02:00
client . Send ( nil , client . server . name , ERR_CHANOPRIVSNEEDED , chname , "You're not a channel operator" )
2016-04-14 01:35:36 +02:00
}
2017-01-11 03:16:51 +01:00
channel . membersMutex . Unlock ( )
2014-02-17 08:29:11 +01:00
}
2016-06-19 13:59:18 +02:00
return false
2014-02-17 08:29:11 +01:00
}
2014-02-17 08:51:27 +01:00
2016-06-17 14:17:42 +02:00
// LIST [<channel>{,<channel>} [<server>]]
func listHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-06-19 13:59:18 +02:00
var channels [ ] string
if len ( msg . Params ) > 0 {
channels = strings . Split ( msg . Params [ 0 ] , "," )
2016-06-17 14:17:42 +02:00
}
2016-06-19 13:59:18 +02:00
var target string
if len ( msg . Params ) > 1 {
target = msg . Params [ 1 ]
2016-06-17 14:17:42 +02:00
}
2014-02-17 08:51:27 +01:00
2016-06-17 14:17:42 +02:00
//TODO(dan): target server when we have multiple servers
//TODO(dan): we should continue just fine if it's this current server though
if target != "" {
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , ERR_NOSUCHSERVER , client . nick , target , "No such server" )
2016-06-19 13:59:18 +02:00
return false
2014-02-17 08:51:27 +01:00
}
2016-06-17 14:17:42 +02:00
if len ( channels ) == 0 {
2014-02-17 08:51:27 +01:00
for _ , channel := range server . channels {
2016-04-14 14:33:38 +02:00
if ! client . flags [ Operator ] && channel . flags [ Secret ] {
2014-02-17 08:51:27 +01:00
continue
}
2014-02-20 07:20:34 +01:00
client . RplList ( channel )
2014-02-17 08:51:27 +01:00
}
} else {
2016-10-23 17:01:27 +02:00
// limit regular users to only listing one channel
if ! client . flags [ Operator ] {
channels = channels [ : 1 ]
}
2016-06-17 14:17:42 +02:00
for _ , chname := range channels {
2016-10-11 15:51:46 +02:00
casefoldedChname , err := CasefoldChannel ( chname )
channel := server . channels . Get ( casefoldedChname )
if err != nil || channel == nil || ( ! client . flags [ Operator ] && channel . flags [ Secret ] ) {
2016-11-04 12:38:47 +01:00
if len ( chname ) > 0 {
client . Send ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , chname , "No such channel" )
}
2014-02-17 08:51:27 +01:00
continue
}
2014-02-20 07:20:34 +01:00
client . RplList ( channel )
2014-02-17 08:51:27 +01:00
}
}
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , RPL_LISTEND , client . nick , "End of LIST" )
2016-06-19 13:59:18 +02:00
return false
}
func ( target * Client ) RplList ( channel * Channel ) {
2017-01-10 17:09:08 +01:00
channel . membersMutex . RLock ( )
defer channel . membersMutex . RUnlock ( )
2016-06-19 13:59:18 +02:00
// get the correct number of channel members
var memberCount int
if target . flags [ Operator ] || channel . members . Has ( target ) {
memberCount = len ( channel . members )
} else {
for member := range channel . members {
if ! member . flags [ Invisible ] {
2017-03-06 00:27:08 +01:00
memberCount ++
2016-06-19 13:59:18 +02:00
}
}
}
2017-01-22 04:01:44 +01:00
target . Send ( nil , target . server . name , RPL_LIST , target . nick , channel . name , strconv . Itoa ( memberCount ) , channel . topic )
2014-02-17 08:51:27 +01:00
}
2014-02-18 03:10:52 +01:00
2016-06-17 14:17:42 +02:00
// NAMES [<channel>{,<channel>}]
func namesHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-06-19 13:59:18 +02:00
var channels [ ] string
if len ( msg . Params ) > 0 {
channels = strings . Split ( msg . Params [ 0 ] , "," )
2016-06-17 14:17:42 +02:00
}
2016-06-20 14:53:45 +02:00
//var target string
//if len(msg.Params) > 1 {
// target = msg.Params[1]
//}
2016-06-17 14:17:42 +02:00
if len ( channels ) == 0 {
2014-02-18 06:02:03 +01:00
for _ , channel := range server . channels {
channel . Names ( client )
}
2016-06-17 14:17:42 +02:00
return false
2014-02-18 06:02:03 +01:00
}
2016-10-23 17:01:27 +02:00
// limit regular users to only listing one channel
if ! client . flags [ Operator ] {
channels = channels [ : 1 ]
}
2016-06-17 14:17:42 +02:00
for _ , chname := range channels {
2016-10-11 15:51:46 +02:00
casefoldedChname , err := CasefoldChannel ( chname )
channel := server . channels . Get ( casefoldedChname )
if err != nil || channel == nil {
2016-11-04 12:38:47 +01:00
if len ( chname ) > 0 {
client . Send ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , chname , "No such channel" )
}
2014-02-18 06:02:03 +01:00
continue
}
channel . Names ( client )
}
2016-06-19 13:59:18 +02:00
return false
2014-02-18 06:02:03 +01:00
}
2014-02-23 19:04:31 +01:00
2016-06-17 14:17:42 +02:00
// VERSION [<server>]
func versionHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-06-19 13:59:18 +02:00
var target string
if len ( msg . Params ) > 0 {
target = msg . Params [ 0 ]
2016-06-17 14:17:42 +02:00
}
2016-10-11 15:51:46 +02:00
casefoldedTarget , err := Casefold ( target )
2016-11-01 14:56:21 +01:00
if target != "" && ( err != nil || casefoldedTarget != server . nameCasefolded ) {
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , ERR_NOSUCHSERVER , client . nick , target , "No such server" )
2016-06-19 13:59:18 +02:00
return false
2014-02-25 07:04:11 +01:00
}
2016-10-13 09:36:44 +02:00
client . Send ( nil , server . name , RPL_VERSION , client . nick , Ver , server . name )
2016-04-14 01:55:22 +02:00
client . RplISupport ( )
2016-06-19 13:59:18 +02:00
return false
2014-02-25 07:04:11 +01:00
}
2014-02-25 16:28:09 +01:00
2016-06-17 14:17:42 +02:00
// INVITE <nickname> <channel>
func inviteHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-06-19 13:59:18 +02:00
nickname := msg . Params [ 0 ]
channelName := msg . Params [ 1 ]
2014-02-25 16:28:09 +01:00
2016-10-11 15:51:46 +02:00
casefoldedNickname , err := CasefoldName ( nickname )
target := server . clients . Get ( casefoldedNickname )
if err != nil || target == nil {
client . Send ( nil , server . name , ERR_NOSUCHNICK , client . nick , nickname , "No such nick" )
2016-06-19 13:59:18 +02:00
return false
2014-02-25 16:28:09 +01:00
}
2016-10-11 15:51:46 +02:00
casefoldedChannelName , err := CasefoldChannel ( channelName )
channel := server . channels . Get ( casefoldedChannelName )
if err != nil || channel == nil {
client . Send ( nil , server . name , RPL_INVITING , client . nick , target . nick , channelName )
target . Send ( nil , client . nickMaskString , "INVITE" , target . nick , channel . name )
2016-06-19 13:59:18 +02:00
return true
2014-02-25 16:28:09 +01:00
}
channel . Invite ( target , client )
2016-06-19 13:59:18 +02:00
return false
2014-02-25 16:28:09 +01:00
}
2014-02-25 16:45:40 +01:00
2016-06-17 14:17:42 +02:00
// TIME [<server>]
func timeHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-06-19 13:59:18 +02:00
var target string
2016-06-17 14:17:42 +02:00
if len ( msg . Params ) > 0 {
2016-06-19 13:59:18 +02:00
target = msg . Params [ 0 ]
2016-06-17 14:17:42 +02:00
}
2016-10-11 15:51:46 +02:00
casefoldedTarget , err := Casefold ( target )
if ( target != "" ) && err != nil || ( casefoldedTarget != server . nameCasefolded ) {
client . Send ( nil , server . name , ERR_NOSUCHSERVER , client . nick , target , "No such server" )
2016-06-19 13:59:18 +02:00
return false
2014-02-25 18:10:16 +01:00
}
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , RPL_TIME , client . nick , server . name , time . Now ( ) . Format ( time . RFC1123 ) )
2016-06-19 13:59:18 +02:00
return false
2014-02-25 18:10:16 +01:00
}
2016-06-17 14:17:42 +02:00
// KILL <nickname> <comment>
func killHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-06-19 13:59:18 +02:00
nickname := msg . Params [ 0 ]
2016-09-14 12:57:33 +02:00
comment := "<no reason supplied>"
if len ( msg . Params ) > 1 {
comment = msg . Params [ 1 ]
}
2016-06-17 14:17:42 +02:00
2016-10-11 15:51:46 +02:00
casefoldedNickname , err := CasefoldName ( nickname )
target := server . clients . Get ( casefoldedNickname )
if err != nil || target == nil {
client . Send ( nil , client . server . name , ERR_NOSUCHNICK , nickname , "No such nick" )
2016-06-19 13:59:18 +02:00
return false
2014-02-25 18:10:16 +01:00
}
2016-10-11 15:51:46 +02:00
quitMsg := fmt . Sprintf ( "Killed (%s (%s))" , client . nick , comment )
2016-06-19 13:59:18 +02:00
target . Quit ( quitMsg )
target . destroy ( )
return false
2014-02-25 16:45:40 +01:00
}
2014-03-06 20:56:32 +01:00
2016-06-17 14:17:42 +02:00
// WHOWAS <nickname> [<count> [<server>]]
func whowasHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2016-06-19 13:59:18 +02:00
nicknames := strings . Split ( msg . Params [ 0 ] , "," )
2016-06-17 14:17:42 +02:00
2016-06-19 13:59:18 +02:00
var count int64
2016-06-17 14:17:42 +02:00
if len ( msg . Params ) > 1 {
count , _ = strconv . ParseInt ( msg . Params [ 1 ] , 10 , 64 )
}
2016-06-20 14:53:45 +02:00
//var target string
//if len(msg.Params) > 2 {
// target = msg.Params[2]
//}
2016-06-17 14:17:42 +02:00
for _ , nickname := range nicknames {
2016-10-11 15:51:46 +02:00
results := server . whoWas . Find ( nickname , count )
2014-03-06 22:55:25 +01:00
if len ( results ) == 0 {
2016-11-04 12:38:47 +01:00
if len ( nickname ) > 0 {
client . Send ( nil , server . name , ERR_WASNOSUCHNICK , client . nick , nickname , "There was no such nickname" )
}
2014-03-06 22:55:25 +01:00
} else {
for _ , whoWas := range results {
2016-10-11 15:51:46 +02:00
client . Send ( nil , server . name , RPL_WHOWASUSER , client . nick , whoWas . nickname , whoWas . username , whoWas . hostname , "*" , whoWas . realname )
2014-03-06 22:55:25 +01:00
}
}
2016-11-04 12:38:47 +01:00
if len ( nickname ) > 0 {
client . Send ( nil , server . name , RPL_ENDOFWHOWAS , client . nick , nickname , "End of WHOWAS" )
}
2014-03-06 21:14:21 +01:00
}
2016-06-19 13:59:18 +02:00
return false
2014-03-06 21:14:21 +01:00
}
2017-01-15 01:48:47 +01:00
2017-01-17 13:49:14 +01:00
// LUSERS [<mask> [<server>]]
2017-01-15 01:48:47 +01:00
func lusersHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2017-01-17 12:09:51 +01:00
//TODO(vegax87) Fix network statistics and additional parameters
2017-01-17 13:52:19 +01:00
var totalcount , invisiblecount , opercount int
2017-01-17 13:49:14 +01:00
2017-01-15 01:48:47 +01:00
server . clients . ByNickMutex . RLock ( )
defer server . clients . ByNickMutex . RUnlock ( )
2017-01-17 13:49:14 +01:00
2017-01-15 01:48:47 +01:00
for _ , onlineusers := range server . clients . ByNick {
2017-01-17 13:49:14 +01:00
totalcount ++
2017-01-15 01:48:47 +01:00
if onlineusers . flags [ Invisible ] {
2017-01-17 13:49:14 +01:00
invisiblecount ++
2017-01-15 01:48:47 +01:00
}
if onlineusers . flags [ Operator ] {
2017-01-17 13:49:14 +01:00
opercount ++
2017-01-15 01:48:47 +01:00
}
}
client . Send ( nil , server . name , RPL_LUSERCLIENT , client . nick , fmt . Sprintf ( "There are %d users and %d invisible on %d server(s)" , totalcount , invisiblecount , 1 ) )
2017-01-23 18:49:42 +01:00
client . Send ( nil , server . name , RPL_LUSEROP , client . nick , fmt . Sprintf ( "%d IRC Operators online" , opercount ) )
2017-01-17 13:52:19 +01:00
client . Send ( nil , server . name , RPL_LUSERCHANNELS , client . nick , fmt . Sprintf ( "%d channels formed" , len ( server . channels ) ) )
2017-01-15 01:48:47 +01:00
client . Send ( nil , server . name , RPL_LUSERME , client . nick , fmt . Sprintf ( "I have %d clients and %d servers" , totalcount , 1 ) )
return false
}
2017-01-23 17:44:35 +01:00
2017-01-23 18:49:42 +01:00
// USERHOST <nickname> [<nickname> <nickname> ...]
2017-01-23 17:44:35 +01:00
func userhostHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
2017-03-06 00:14:15 +01:00
returnedNicks := make ( map [ string ] bool )
2017-01-23 17:44:35 +01:00
2017-03-06 00:14:15 +01:00
for i , nickname := range msg . Params {
if i >= 10 {
break
}
2017-01-23 17:44:35 +01:00
2017-03-06 00:14:15 +01:00
casefoldedNickname , err := CasefoldName ( nickname )
target := server . clients . Get ( casefoldedNickname )
if err != nil || target == nil {
client . Send ( nil , client . server . name , ERR_NOSUCHNICK , nickname , "No such nick" )
return false
}
if returnedNicks [ casefoldedNickname ] {
continue
}
// to prevent returning multiple results for a single nick
returnedNicks [ casefoldedNickname ] = true
var isOper , isAway string
if target . flags [ Operator ] {
isOper = "*"
}
if target . flags [ Away ] {
isAway = "-"
} else {
isAway = "+"
}
client . Send ( nil , client . server . name , RPL_USERHOST , client . nick , fmt . Sprintf ( "%s%s=%s%s@%s" , target . nick , isOper , isAway , target . username , target . hostname ) )
2017-01-23 17:44:35 +01:00
}
2017-03-06 00:14:15 +01:00
2017-01-23 17:44:35 +01:00
return false
}