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 (
2016-11-15 18:05:33 +01:00
"errors"
2012-04-18 07:11:35 +02:00
"fmt"
2016-06-30 11:28:34 +02:00
"log"
2012-04-07 20:44:59 +02:00
"net"
2016-10-16 12:35:50 +02:00
"runtime/debug"
2016-06-30 07:35:34 +02:00
"strconv"
2017-01-20 14:51:36 +01:00
"strings"
2012-12-12 08:12:35 +01:00
"time"
2016-06-17 14:17:42 +02:00
"github.com/DanielOaks/girc-go/ircmsg"
2016-06-30 11:28:34 +02:00
"github.com/DanielOaks/go-ident"
2012-04-07 20:44:59 +02:00
)
2014-03-13 01:52:25 +01:00
const (
2016-07-02 11:12:00 +02:00
IDLE_TIMEOUT = time . Minute + time . Second * 30 // how long before a client is considered idle
QUIT_TIMEOUT = time . Minute // how long after idle before a client is kicked
2016-11-04 12:38:47 +01:00
IdentTimeoutSeconds = 5
2016-06-30 07:35:34 +02:00
)
var (
TIMEOUT_STATED_SECONDS = strconv . Itoa ( int ( ( IDLE_TIMEOUT + QUIT_TIMEOUT ) . Seconds ( ) ) )
2016-11-15 18:05:33 +01:00
ErrNickAlreadySet = errors . New ( "Nickname is already set" )
2014-03-13 01:52:25 +01:00
)
2016-10-23 03:48:57 +02:00
// Client is an IRC client.
2012-04-07 20:44:59 +02:00
type Client struct {
2016-10-11 15:51:46 +02:00
account * ClientAccount
atime time . Time
authorized bool
awayMessage string
capabilities CapabilitySet
capState CapState
2016-10-16 05:54:09 +02:00
capVersion CapVersion
2016-10-11 15:51:46 +02:00
certfp string
channels ChannelSet
2016-10-23 02:47:11 +02:00
class * OperClass
2016-10-11 15:51:46 +02:00
ctime time . Time
flags map [ UserMode ] bool
isDestroyed bool
isQuitting bool
hasQuit bool
2016-10-16 13:28:59 +02:00
hops int
2016-10-11 15:51:46 +02:00
hostname string
2016-10-23 03:28:31 +02:00
rawHostname string
vhost string
2016-10-11 15:51:46 +02:00
idleTimer * time . Timer
2016-10-16 12:14:56 +02:00
monitoring map [ string ] bool
2016-10-11 15:51:46 +02:00
nick string
nickCasefolded string
nickMaskString string // cache for nickmask string since it's used with lots of replies
nickMaskCasefolded string
2016-10-23 02:47:11 +02:00
operName string
2016-10-11 15:51:46 +02:00
quitTimer * time . Timer
2016-11-29 12:06:01 +01:00
quitMessageSent bool
2016-10-11 15:51:46 +02:00
realname string
registered bool
saslInProgress bool
saslMechanism string
saslValue string
server * Server
socket * Socket
username string
2016-10-23 03:01:05 +02:00
whoisLine string
2012-12-17 04:13:53 +01:00
}
2016-10-16 12:14:56 +02:00
// NewClient returns a client with all the appropriate info setup.
2016-06-28 17:09:07 +02:00
func NewClient ( server * Server , conn net . Conn , isTLS bool ) * Client {
2014-02-14 03:59:45 +01:00
now := time . Now ( )
2016-06-15 13:21:45 +02:00
socket := NewSocket ( conn )
2012-12-09 21:51:50 +01:00
client := & Client {
2016-09-19 14:30:29 +02:00
atime : now ,
authorized : server . password == nil ,
capabilities : make ( CapabilitySet ) ,
2016-10-16 05:54:09 +02:00
capState : CapNone ,
capVersion : Cap301 ,
2016-09-19 14:30:29 +02:00
channels : make ( ChannelSet ) ,
ctime : now ,
flags : make ( map [ UserMode ] bool ) ,
2016-10-16 12:14:56 +02:00
monitoring : make ( map [ string ] bool ) ,
2016-09-19 14:30:29 +02:00
server : server ,
socket : & socket ,
account : & NoAccount ,
2016-10-11 15:51:46 +02:00
nick : "*" , // * is used until actual nick is given
nickCasefolded : "*" ,
2016-09-19 14:30:29 +02:00
nickMaskString : "*" , // * is used until actual nick is given
2012-12-09 21:51:50 +01:00
}
2016-06-28 17:09:07 +02:00
if isTLS {
client . flags [ TLS ] = true
2016-09-07 13:32:58 +02:00
// error is not useful to us here anyways so we can ignore it
client . certfp , _ = client . socket . CertFP ( )
2016-06-28 17:09:07 +02:00
}
2016-06-30 11:28:34 +02:00
if server . checkIdent {
_ , serverPortString , err := net . SplitHostPort ( conn . LocalAddr ( ) . String ( ) )
serverPort , _ := strconv . Atoi ( serverPortString )
if err != nil {
log . Fatal ( err )
}
clientHost , clientPortString , err := net . SplitHostPort ( conn . RemoteAddr ( ) . String ( ) )
clientPort , _ := strconv . Atoi ( clientPortString )
if err != nil {
log . Fatal ( err )
}
client . Notice ( "*** Looking up your username" )
2016-07-02 11:12:00 +02:00
resp , err := ident . Query ( clientHost , serverPort , clientPort , IdentTimeoutSeconds )
2016-06-30 11:28:34 +02:00
if err == nil {
username := resp . Identifier
2016-10-11 15:51:46 +02:00
_ , err := CasefoldName ( username ) // ensure it's a valid username
if err == nil {
2016-06-30 11:28:34 +02:00
client . Notice ( "*** Found your username" )
2016-10-11 15:51:46 +02:00
client . username = username
2016-06-30 11:28:34 +02:00
// we don't need to updateNickMask here since nickMask is not used for anything yet
} else {
client . Notice ( "*** Got a malformed username, ignoring" )
}
} else {
client . Notice ( "*** Could not find your username" )
}
}
2014-04-15 17:49:52 +02:00
client . Touch ( )
2014-02-24 07:21:39 +01:00
go client . run ( )
2012-12-13 08:27:17 +01:00
2012-04-08 08:32:08 +02:00
return client
2012-04-07 20:44:59 +02:00
}
2014-02-24 07:21:39 +01:00
//
// command goroutine
//
2017-01-13 15:22:42 +01:00
func ( client * Client ) maxlens ( ) ( int , int ) {
maxlenTags := 512
maxlenRest := 512
if client . capabilities [ MessageTags ] {
maxlenTags = 4096
}
if client . capabilities [ MaxLine ] {
2017-01-14 06:28:50 +01:00
if client . server . limits . LineLen . Tags > maxlenTags {
maxlenTags = client . server . limits . LineLen . Tags
2017-01-13 15:22:42 +01:00
}
2017-01-14 06:28:50 +01:00
maxlenRest = client . server . limits . LineLen . Rest
2017-01-13 15:22:42 +01:00
}
return maxlenTags , maxlenRest
}
2014-02-24 07:21:39 +01:00
func ( client * Client ) run ( ) {
2014-04-15 17:49:52 +02:00
var err error
2016-06-17 14:17:42 +02:00
var isExiting bool
2014-04-15 17:49:52 +02:00
var line string
2016-06-17 14:17:42 +02:00
var msg ircmsg . IrcMessage
2014-04-15 17:49:52 +02:00
2016-10-15 08:29:34 +02:00
// Set the hostname for this client
2016-10-23 03:28:31 +02:00
client . rawHostname = AddrLookupHostname ( client . socket . conn . RemoteAddr ( ) )
2014-04-15 17:49:52 +02:00
2016-06-17 14:17:42 +02:00
//TODO(dan): Make this a socketreactor from ircbnc
for {
line , err = client . socket . Read ( )
if err != nil {
client . Quit ( "connection closed" )
break
}
2017-01-13 15:22:42 +01:00
maxlenTags , maxlenRest := client . maxlens ( )
2016-11-29 09:38:04 +01:00
2017-01-13 15:22:42 +01:00
msg , err = ircmsg . ParseLineMaxLen ( line , maxlenTags , maxlenRest )
2017-01-18 22:56:33 +01:00
if err == ircmsg . ErrorLineIsEmpty {
continue
} else if err != nil {
2016-06-20 14:53:45 +02:00
client . Quit ( "received malformed line" )
2016-06-17 14:17:42 +02:00
break
2014-02-24 07:21:39 +01:00
}
2014-04-15 17:49:52 +02:00
2016-06-19 02:01:30 +02:00
cmd , exists := Commands [ msg . Command ]
if ! exists {
2016-11-04 12:38:47 +01:00
if len ( msg . Command ) > 0 {
client . Send ( nil , client . server . name , ERR_UNKNOWNCOMMAND , client . nick , msg . Command , "Unknown command" )
} else {
client . Send ( nil , client . server . name , ERR_UNKNOWNCOMMAND , client . nick , "lastcmd" , "No command given" )
}
2016-06-20 14:53:45 +02:00
continue
2016-06-19 02:01:30 +02:00
}
isExiting = cmd . Run ( client . server , client , msg )
2016-06-22 14:04:13 +02:00
if isExiting || client . isQuitting {
2016-06-17 14:17:42 +02:00
break
}
2014-02-24 07:21:39 +01:00
}
2016-06-17 14:17:42 +02:00
// ensure client connection gets closed
2016-06-19 02:01:30 +02:00
client . destroy ( )
2014-04-15 17:49:52 +02:00
}
2016-06-17 14:17:42 +02:00
//
2014-04-15 17:49:52 +02:00
// quit timer goroutine
2016-06-17 14:17:42 +02:00
//
2014-04-15 17:49:52 +02:00
2014-03-01 04:21:33 +01:00
func ( client * Client ) connectionTimeout ( ) {
2016-06-30 07:35:34 +02:00
client . Quit ( fmt . Sprintf ( "Ping timeout: %s seconds" , TIMEOUT_STATED_SECONDS ) )
2016-06-22 14:04:13 +02:00
client . isQuitting = true
2014-03-01 04:21:33 +01:00
}
2014-02-18 22:25:21 +01:00
//
// idle timer goroutine
//
func ( client * Client ) connectionIdle ( ) {
client . server . idle <- client
}
//
// server goroutine
//
2016-10-23 03:48:57 +02:00
// Active marks the client as 'active' (i.e. the user should be there).
2014-02-18 22:25:21 +01:00
func ( client * Client ) Active ( ) {
2014-02-14 03:39:33 +01:00
client . atime = time . Now ( )
2014-02-18 22:25:21 +01:00
}
2014-02-14 03:39:33 +01:00
2016-10-23 03:48:57 +02:00
// Touch marks the client as alive.
2014-02-18 22:25:21 +01:00
func ( client * Client ) Touch ( ) {
2014-02-09 21:13:09 +01:00
if client . quitTimer != nil {
client . quitTimer . Stop ( )
}
2014-02-14 03:39:33 +01:00
2014-02-09 21:13:09 +01:00
if client . idleTimer == nil {
2014-02-18 20:22:56 +01:00
client . idleTimer = time . AfterFunc ( IDLE_TIMEOUT , client . connectionIdle )
2014-02-09 21:13:09 +01:00
} else {
client . idleTimer . Reset ( IDLE_TIMEOUT )
}
}
2016-10-23 03:48:57 +02:00
// Idle resets the timeout handlers and sends the client a PING.
2014-02-09 21:13:09 +01:00
func ( client * Client ) Idle ( ) {
2016-10-11 15:51:46 +02:00
client . Send ( nil , "" , "PING" , client . nick )
2014-02-18 20:22:56 +01:00
2014-02-09 21:13:09 +01:00
if client . quitTimer == nil {
2014-02-18 02:58:22 +01:00
client . quitTimer = time . AfterFunc ( QUIT_TIMEOUT , client . connectionTimeout )
2014-02-09 21:13:09 +01:00
} else {
client . quitTimer . Reset ( QUIT_TIMEOUT )
}
2014-02-18 20:22:56 +01:00
}
2014-02-14 03:39:33 +01:00
2016-10-16 12:35:50 +02:00
// Register sets the client details as appropriate when entering the network.
2014-02-18 22:25:21 +01:00
func ( client * Client ) Register ( ) {
2014-03-13 01:52:25 +01:00
if client . registered {
return
}
client . registered = true
2014-02-18 22:25:21 +01:00
client . Touch ( )
2016-10-16 12:14:56 +02:00
2016-10-16 12:35:50 +02:00
client . updateNickMask ( )
2016-10-16 12:14:56 +02:00
client . alertMonitors ( )
2012-12-13 08:27:17 +01:00
}
2016-10-23 03:48:57 +02:00
// IdleTime returns how long this client's been idle.
2014-02-18 00:25:32 +01:00
func ( client * Client ) IdleTime ( ) time . Duration {
return time . Since ( client . atime )
}
2016-10-23 03:48:57 +02:00
// SignonTime returns this client's signon time as a unix timestamp.
2014-02-18 04:56:06 +01:00
func ( client * Client ) SignonTime ( ) int64 {
return client . ctime . Unix ( )
}
2016-10-23 03:48:57 +02:00
// IdleSeconds returns the number of seconds this client's been idle.
2014-02-18 04:08:57 +01:00
func ( client * Client ) IdleSeconds ( ) uint64 {
return uint64 ( client . IdleTime ( ) . Seconds ( ) )
}
2016-10-23 03:48:57 +02:00
// HasNick returns true if the client's nickname is set (used in registration).
2014-02-09 02:10:04 +01:00
func ( client * Client ) HasNick ( ) bool {
2016-10-11 15:51:46 +02:00
return client . nick != "" && client . nick != "*"
2012-12-17 04:13:53 +01:00
}
2016-10-23 03:48:57 +02:00
// HasNick returns true if the client's username is set (used in registration).
2014-02-09 02:10:04 +01:00
func ( client * Client ) HasUsername ( ) bool {
2016-10-11 15:51:46 +02:00
return client . username != "" && client . username != "*"
2014-02-09 02:10:04 +01:00
}
2016-10-23 03:13:08 +02:00
// HasCapabs returns true if client has the given (role) capabilities.
func ( client * Client ) HasCapabs ( capabs ... string ) bool {
if client . class == nil {
return false
}
for _ , capab := range capabs {
if ! client . class . Capabilities [ capab ] {
return false
}
}
return true
}
2014-02-09 17:53:06 +01:00
// <mode>
func ( c * Client ) ModeString ( ) ( str string ) {
2016-09-07 13:50:42 +02:00
str = "+"
2014-02-17 22:22:35 +01:00
for flag := range c . flags {
str += flag . String ( )
2014-02-09 19:07:40 +01:00
}
2014-02-09 17:53:06 +01:00
return
2012-04-18 05:24:26 +02:00
}
2012-04-18 06:13:12 +02:00
2016-06-17 14:17:42 +02:00
// Friends refers to clients that share a channel with this client.
2016-10-13 10:08:08 +02:00
func ( client * Client ) Friends ( Capabilities ... Capability ) ClientSet {
2014-02-19 00:28:20 +01:00
friends := make ( ClientSet )
2016-10-26 16:44:36 +02:00
// make sure that I have the right caps
hasCaps := true
for _ , Cap := range Capabilities {
if ! client . capabilities [ Cap ] {
hasCaps = false
break
}
}
if hasCaps {
friends . Add ( client )
}
2014-02-19 00:28:20 +01:00
for channel := range client . channels {
2017-01-10 17:09:08 +01:00
channel . membersMutex . RLock ( )
defer channel . membersMutex . RUnlock ( )
2014-02-19 00:28:20 +01:00
for member := range channel . members {
2016-10-13 10:08:08 +02:00
// make sure they have all the required caps
for _ , Cap := range Capabilities {
if ! member . capabilities [ Cap ] {
continue
}
}
2014-02-19 00:28:20 +01:00
friends . Add ( member )
}
2014-02-17 02:23:47 +01:00
}
2014-02-19 00:28:20 +01:00
return friends
2014-02-17 02:23:47 +01:00
}
2016-10-16 12:35:50 +02:00
// updateNick updates the casefolded nickname.
func ( client * Client ) updateNick ( ) {
2016-10-11 15:51:46 +02:00
casefoldedName , err := CasefoldName ( client . nick )
if err != nil {
2016-10-16 12:35:50 +02:00
log . Println ( fmt . Sprintf ( "ERROR: Nick [%s] couldn't be casefolded... this should never happen. Printing stacktrace." , client . nick ) )
debug . PrintStack ( )
2016-10-11 15:51:46 +02:00
}
client . nickCasefolded = casefoldedName
2016-10-16 12:35:50 +02:00
}
// updateNickMask updates the casefolded nickname and nickmask.
func ( client * Client ) updateNickMask ( ) {
client . updateNick ( )
2016-10-11 15:51:46 +02:00
2016-10-23 03:28:31 +02:00
if len ( client . vhost ) > 0 {
client . hostname = client . vhost
} else {
client . hostname = client . rawHostname
}
2016-10-11 15:51:46 +02:00
client . nickMaskString = fmt . Sprintf ( "%s!%s@%s" , client . nick , client . username , client . hostname )
nickMaskCasefolded , err := Casefold ( client . nickMaskString )
if err != nil {
2016-10-16 12:35:50 +02:00
log . Println ( fmt . Sprintf ( "ERROR: Nickmask [%s] couldn't be casefolded... this should never happen. Printing stacktrace." , client . nickMaskString ) )
debug . PrintStack ( )
2016-10-11 15:51:46 +02:00
}
client . nickMaskCasefolded = nickMaskCasefolded
2016-06-19 07:37:29 +02:00
}
2017-01-11 13:38:16 +01:00
// AllNickmasks returns all the possible nickmasks for the client.
func ( client * Client ) AllNickmasks ( ) [ ] string {
var masks [ ] string
var mask string
var err error
if len ( client . vhost ) > 0 {
mask , err = Casefold ( fmt . Sprintf ( "%s!%s@%s" , client . nick , client . username , client . vhost ) )
if err == nil {
masks = append ( masks , mask )
}
}
mask , err = Casefold ( fmt . Sprintf ( "%s!%s@%s" , client . nick , client . username , client . rawHostname ) )
if err == nil {
masks = append ( masks , mask )
}
mask2 , err := Casefold ( fmt . Sprintf ( "%s!%s@%s" , client . nick , client . username , IPString ( client . socket . conn . RemoteAddr ( ) ) ) )
if err == nil && mask2 != mask {
masks = append ( masks , mask2 )
}
return masks
}
2016-10-23 03:48:57 +02:00
// SetNickname sets the very first nickname for the client.
2016-11-15 18:05:33 +01:00
func ( client * Client ) SetNickname ( nickname string ) error {
2014-03-17 20:11:35 +01:00
if client . HasNick ( ) {
2017-03-06 06:50:23 +01:00
client . server . logger . Log ( LogError , "nick" , client . nick , fmt . Sprintf ( "%s nickname already set, something is wrong with server consistency" , client . nickMaskString ) )
2016-11-15 18:05:33 +01:00
return ErrNickAlreadySet
2014-03-17 20:11:35 +01:00
}
2016-11-15 18:05:33 +01:00
2016-11-29 13:33:10 +01:00
err := client . server . clients . Add ( client , nickname )
if err == nil {
client . nick = nickname
client . updateNick ( )
}
return err
2014-02-19 00:36:58 +01:00
}
2016-10-23 03:48:57 +02:00
// ChangeNickname changes the existing nickname of the client.
2016-11-15 18:05:33 +01:00
func ( client * Client ) ChangeNickname ( nickname string ) error {
2016-06-19 07:37:29 +02:00
origNickMask := client . nickMaskString
2016-11-16 03:02:22 +01:00
err := client . server . clients . Replace ( client . nick , nickname , client )
if err == nil {
client . server . whoWas . Append ( client )
client . nick = nickname
2017-01-13 17:32:15 +01:00
client . updateNickMask ( )
2016-11-16 03:02:22 +01:00
for friend := range client . Friends ( ) {
friend . Send ( nil , origNickMask , "NICK" , nickname )
}
2014-02-22 20:40:32 +01:00
}
2016-11-16 03:02:22 +01:00
return err
2014-02-22 20:40:32 +01:00
}
2016-06-19 02:01:30 +02:00
func ( client * Client ) Quit ( message string ) {
2016-11-29 12:06:01 +01:00
if ! client . quitMessageSent {
client . Send ( nil , client . nickMaskString , "QUIT" , message )
2017-01-18 22:56:33 +01:00
client . Send ( nil , "" , "ERROR" , message )
2016-11-29 12:06:01 +01:00
client . quitMessageSent = true
}
2016-06-17 14:17:42 +02:00
}
2016-10-23 03:48:57 +02:00
// destroy gets rid of a client, removes them from server lists etc.
2016-06-17 14:17:42 +02:00
func ( client * Client ) destroy ( ) {
if client . isDestroyed {
2014-02-18 22:25:21 +01:00
return
}
2014-02-20 03:46:46 +01:00
2016-11-29 12:06:01 +01:00
// send quit/error message to client if they haven't been sent already
client . Quit ( "Connection closed" )
2016-06-17 14:17:42 +02:00
client . isDestroyed = true
2014-03-06 22:55:25 +01:00
client . server . whoWas . Append ( client )
2014-02-19 00:28:20 +01:00
friends := client . Friends ( )
friends . Remove ( client )
2016-06-17 14:17:42 +02:00
2016-10-23 15:05:00 +02:00
// remove from connection limits
ipaddr := net . ParseIP ( IPString ( client . socket . conn . RemoteAddr ( ) ) )
// this check shouldn't be required but eh
if ipaddr != nil {
client . server . connectionLimitsMutex . Lock ( )
client . server . connectionLimits . RemoveClient ( ipaddr )
client . server . connectionLimitsMutex . Unlock ( )
}
2016-10-23 02:47:11 +02:00
// remove from opers list
_ , exists := client . server . currentOpers [ client ]
if exists {
delete ( client . server . currentOpers , client )
}
2016-10-16 12:14:56 +02:00
// alert monitors
for _ , mClient := range client . server . monitoring [ client . nickCasefolded ] {
mClient . Send ( nil , client . server . name , RPL_MONOFFLINE , mClient . nick , client . nick )
}
// remove my monitors
client . clearMonitorList ( )
2016-06-17 14:17:42 +02:00
// clean up channels
for channel := range client . channels {
channel . Quit ( client )
}
// clean up server
client . server . clients . Remove ( client )
// clean up self
if client . idleTimer != nil {
client . idleTimer . Stop ( )
}
if client . quitTimer != nil {
client . quitTimer . Stop ( )
}
client . socket . Close ( )
2016-11-29 12:06:01 +01:00
// send quit messages to friends
for friend := range friends {
//TODO(dan): store quit message in user, if exists use that instead here
friend . Send ( nil , client . nickMaskString , "QUIT" , "Exited" )
}
2016-06-19 02:01:30 +02:00
}
2014-02-18 22:25:21 +01:00
2017-01-14 06:28:50 +01:00
// SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
// Adds account-tag to the line as well.
2017-01-14 10:52:47 +01:00
func ( client * Client ) SendSplitMsgFromClient ( msgid string , from * Client , tags * map [ string ] ircmsg . TagValue , command , target string , message SplitMessage ) {
2017-01-14 06:28:50 +01:00
if client . capabilities [ MaxLine ] {
2017-01-17 23:05:31 +01:00
client . SendFromClient ( msgid , from , tags , command , target , message . ForMaxLine )
2017-01-14 06:28:50 +01:00
} else {
for _ , str := range message . For512 {
2017-01-17 23:05:31 +01:00
client . SendFromClient ( msgid , from , tags , command , target , str )
2017-01-14 06:28:50 +01:00
}
}
}
2016-09-12 03:25:31 +02:00
// SendFromClient sends an IRC line coming from a specific client.
// Adds account-tag to the line as well.
2017-01-14 12:48:57 +01:00
func ( client * Client ) SendFromClient ( msgid string , from * Client , tags * map [ string ] ircmsg . TagValue , command string , params ... string ) error {
2016-09-12 03:25:31 +02:00
// attach account-tag
if client . capabilities [ AccountTag ] && from . account != & NoAccount {
if tags == nil {
tags = ircmsg . MakeTags ( "account" , from . account . Name )
} else {
( * tags ) [ "account" ] = ircmsg . MakeTagValue ( from . account . Name )
}
}
2017-01-14 10:52:47 +01:00
// attach message-id
if len ( msgid ) > 0 && client . capabilities [ MessageIDs ] {
if tags == nil {
tags = ircmsg . MakeTags ( "draft/msgid" , msgid )
} else {
( * tags ) [ "draft/msgid" ] = ircmsg . MakeTagValue ( msgid )
}
}
2016-09-12 03:25:31 +02:00
2017-01-14 12:48:57 +01:00
return client . Send ( tags , from . nickMaskString , command , params ... )
2016-09-12 03:25:31 +02:00
}
2017-01-20 15:07:10 +01:00
var (
2017-01-23 00:03:49 +01:00
// these are all the output commands that MUST have their last param be a trailing.
// this is needed because silly clients like to treat trailing as separate from the
// other params in messages.
2017-01-20 15:07:10 +01:00
commandsThatMustUseTrailing = map [ string ] bool {
"PRIVMSG" : true ,
"NOTICE" : true ,
2017-01-23 00:03:49 +01:00
RPL_WHOISCHANNELS : true ,
2017-03-06 06:50:23 +01:00
RPL_USERHOST : true ,
2017-01-20 15:07:10 +01:00
}
)
2016-06-19 02:01:30 +02:00
// Send sends an IRC line to the client.
func ( client * Client ) Send ( tags * map [ string ] ircmsg . TagValue , prefix string , command string , params ... string ) error {
2016-08-13 14:04:21 +02:00
// attach server-time
if client . capabilities [ ServerTime ] {
2017-02-07 01:37:32 +01:00
t := time . Now ( ) . UTC ( ) . Format ( "2006-01-02T15:04:05.999Z" )
2016-08-13 14:04:21 +02:00
if tags == nil {
2017-02-07 01:37:32 +01:00
tags = ircmsg . MakeTags ( "time" , t )
2016-08-13 14:04:21 +02:00
} else {
2017-02-07 01:37:32 +01:00
( * tags ) [ "time" ] = ircmsg . MakeTagValue ( t )
2016-08-13 14:04:21 +02:00
}
}
2017-01-20 14:51:36 +01:00
// force trailing
var usedSpaceHack bool
2017-01-20 15:07:10 +01:00
if commandsThatMustUseTrailing [ strings . ToUpper ( command ) ] && len ( params ) > 0 {
2017-01-20 14:51:36 +01:00
lastParam := params [ len ( params ) - 1 ]
if ! strings . Contains ( lastParam , " " ) {
params [ len ( params ) - 1 ] = lastParam + " "
usedSpaceHack = true
}
}
2016-08-13 14:04:21 +02:00
// send out the message
2016-09-19 15:00:19 +02:00
message := ircmsg . MakeMessage ( tags , prefix , command , params ... )
2017-01-13 15:22:42 +01:00
maxlenTags , maxlenRest := client . maxlens ( )
line , err := message . LineMaxLen ( maxlenTags , maxlenRest )
2016-06-19 02:01:30 +02:00
if err != nil {
2016-09-19 15:00:19 +02:00
// try not to fail quietly - especially useful when running tests, as a note to dig deeper
2016-11-01 14:56:21 +01:00
// log.Println("Error assembling message:")
// spew.Dump(message)
2016-11-04 12:38:47 +01:00
// debug.PrintStack()
2016-10-11 15:51:46 +02:00
message = ircmsg . MakeMessage ( nil , client . server . name , ERR_UNKNOWNERROR , "*" , "Error assembling message for sending" )
2016-09-19 15:00:19 +02:00
line , _ := message . Line ( )
client . socket . Write ( line )
2016-06-19 02:01:30 +02:00
return err
2014-02-17 02:23:47 +01:00
}
2017-01-20 14:51:36 +01:00
// strip space hack if we used it
if usedSpaceHack {
line = line [ : len ( line ) - 3 ] + "\r\n"
}
2016-06-19 02:01:30 +02:00
client . socket . Write ( line )
return nil
}
// Notice sends the client a notice from the server.
func ( client * Client ) Notice ( text string ) {
2016-10-11 15:51:46 +02:00
client . Send ( nil , client . server . name , "NOTICE" , client . nick , text )
2014-02-17 02:23:47 +01:00
}