mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-10 22:19:31 +01:00
add NICKSERV SESSIONS command to list sessions
This commit is contained in:
parent
b11bf503e7
commit
da656c07c8
@ -94,7 +94,14 @@ type Client struct {
|
|||||||
type Session struct {
|
type Session struct {
|
||||||
client *Client
|
client *Client
|
||||||
|
|
||||||
|
ctime time.Time
|
||||||
|
atime time.Time
|
||||||
|
|
||||||
socket *Socket
|
socket *Socket
|
||||||
|
realIP net.IP
|
||||||
|
proxiedIP net.IP
|
||||||
|
rawHostname string
|
||||||
|
|
||||||
idletimer IdleTimer
|
idletimer IdleTimer
|
||||||
fakelag Fakelag
|
fakelag Fakelag
|
||||||
|
|
||||||
@ -104,9 +111,6 @@ type Session struct {
|
|||||||
maxlenRest uint32
|
maxlenRest uint32
|
||||||
capState caps.State
|
capState caps.State
|
||||||
capVersion caps.Version
|
capVersion caps.Version
|
||||||
|
|
||||||
// TODO track per-connection real IP, proxied IP, and hostname here,
|
|
||||||
// so we can list attached sessions and their details
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sets the session quit message, if there isn't one already
|
// sets the session quit message, if there isn't one already
|
||||||
@ -187,6 +191,8 @@ func RunNewClient(server *Server, conn clientConn) {
|
|||||||
socket: socket,
|
socket: socket,
|
||||||
capVersion: caps.Cap301,
|
capVersion: caps.Cap301,
|
||||||
capState: caps.NoneState,
|
capState: caps.NoneState,
|
||||||
|
ctime: now,
|
||||||
|
atime: now,
|
||||||
}
|
}
|
||||||
session.SetMaxlenRest()
|
session.SetMaxlenRest()
|
||||||
client.sessions = []*Session{session}
|
client.sessions = []*Session{session}
|
||||||
@ -197,20 +203,29 @@ func RunNewClient(server *Server, conn clientConn) {
|
|||||||
client.certfp, _ = socket.CertFP()
|
client.certfp, _ = socket.CertFP()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remoteAddr := conn.Conn.RemoteAddr()
|
||||||
if conn.IsTor {
|
if conn.IsTor {
|
||||||
client.SetMode(modes.TLS, true)
|
client.SetMode(modes.TLS, true)
|
||||||
client.realIP = utils.IPv4LoopbackAddress
|
session.realIP = utils.AddrToIP(remoteAddr)
|
||||||
client.rawHostname = config.Server.TorListeners.Vhost
|
// cover up details of the tor proxying infrastructure (not a user privacy concern,
|
||||||
|
// but a hardening measure):
|
||||||
|
session.proxiedIP = utils.IPv4LoopbackAddress
|
||||||
|
session.rawHostname = config.Server.TorListeners.Vhost
|
||||||
} else {
|
} else {
|
||||||
remoteAddr := conn.Conn.RemoteAddr()
|
session.realIP = utils.AddrToIP(remoteAddr)
|
||||||
client.realIP = utils.AddrToIP(remoteAddr)
|
// set the hostname for this client (may be overridden later by PROXY or WEBIRC)
|
||||||
// Set the hostname for this client
|
session.rawHostname = utils.LookupHostname(session.realIP.String())
|
||||||
// (may be overridden by a later PROXY command from stunnel)
|
if utils.AddrIsLocal(remoteAddr) {
|
||||||
client.rawHostname = utils.LookupHostname(client.realIP.String())
|
// treat local connections as secure (may be overridden later by WEBIRC)
|
||||||
|
client.SetMode(modes.TLS, true)
|
||||||
|
}
|
||||||
if config.Server.CheckIdent && !utils.AddrIsUnix(remoteAddr) {
|
if config.Server.CheckIdent && !utils.AddrIsUnix(remoteAddr) {
|
||||||
client.doIdentLookup(conn.Conn)
|
client.doIdentLookup(conn.Conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
client.realIP = session.realIP
|
||||||
|
client.rawHostname = session.rawHostname
|
||||||
|
client.proxiedIP = session.proxiedIP
|
||||||
|
|
||||||
client.run(session)
|
client.run(session)
|
||||||
}
|
}
|
||||||
@ -389,10 +404,13 @@ func (session *Session) playReattachMessages() {
|
|||||||
//
|
//
|
||||||
|
|
||||||
// Active updates when the client was last 'active' (i.e. the user should be sitting in front of their client).
|
// Active updates when the client was last 'active' (i.e. the user should be sitting in front of their client).
|
||||||
func (client *Client) Active() {
|
func (client *Client) Active(session *Session) {
|
||||||
|
// TODO normalize all times to utc?
|
||||||
|
now := time.Now()
|
||||||
client.stateMutex.Lock()
|
client.stateMutex.Lock()
|
||||||
defer client.stateMutex.Unlock()
|
defer client.stateMutex.Unlock()
|
||||||
client.atime = time.Now()
|
session.atime = now
|
||||||
|
client.atime = now
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ping sends the client a PING message.
|
// Ping sends the client a PING message.
|
||||||
|
@ -58,8 +58,8 @@ func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ir
|
|||||||
session.idletimer.Touch()
|
session.idletimer.Touch()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cmd.leaveClientIdle {
|
if client.registered && !cmd.leaveClientIdle {
|
||||||
client.Active()
|
client.Active(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
return exiting
|
return exiting
|
||||||
|
@ -73,7 +73,9 @@ func (client *Client) ApplyProxiedIP(session *Session, proxiedIP string, tls boo
|
|||||||
|
|
||||||
client.stateMutex.Lock()
|
client.stateMutex.Lock()
|
||||||
defer client.stateMutex.Unlock()
|
defer client.stateMutex.Unlock()
|
||||||
|
session.proxiedIP = parsedProxiedIP
|
||||||
client.proxiedIP = parsedProxiedIP
|
client.proxiedIP = parsedProxiedIP
|
||||||
|
session.rawHostname = rawHostname
|
||||||
client.rawHostname = rawHostname
|
client.rawHostname = rawHostname
|
||||||
// nickmask will be updated when the client completes registration
|
// nickmask will be updated when the client completes registration
|
||||||
// set tls info
|
// set tls info
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/oragono/oragono/irc/isupport"
|
"github.com/oragono/oragono/irc/isupport"
|
||||||
@ -70,6 +71,37 @@ func (client *Client) Sessions() (sessions []*Session) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SessionData struct {
|
||||||
|
ctime time.Time
|
||||||
|
atime time.Time
|
||||||
|
ip net.IP
|
||||||
|
hostname string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *Client) AllSessionData(currentSession *Session) (data []SessionData, currentIndex int) {
|
||||||
|
currentIndex = -1
|
||||||
|
client.stateMutex.RLock()
|
||||||
|
defer client.stateMutex.RUnlock()
|
||||||
|
|
||||||
|
data = make([]SessionData, len(client.sessions))
|
||||||
|
for i, session := range client.sessions {
|
||||||
|
if session == currentSession {
|
||||||
|
currentIndex = i
|
||||||
|
}
|
||||||
|
data[i] = SessionData{
|
||||||
|
atime: session.atime,
|
||||||
|
ctime: session.ctime,
|
||||||
|
hostname: session.rawHostname,
|
||||||
|
}
|
||||||
|
if session.proxiedIP != nil {
|
||||||
|
data[i].ip = session.proxiedIP
|
||||||
|
} else {
|
||||||
|
data[i].ip = session.realIP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (client *Client) AddSession(session *Session) (success bool) {
|
func (client *Client) AddSession(session *Session) (success bool) {
|
||||||
client.stateMutex.Lock()
|
client.stateMutex.Lock()
|
||||||
defer client.stateMutex.Unlock()
|
defer client.stateMutex.Unlock()
|
||||||
|
@ -7,6 +7,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/goshuirc/irc-go/ircfmt"
|
"github.com/goshuirc/irc-go/ircfmt"
|
||||||
|
|
||||||
|
"github.com/oragono/oragono/irc/modes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// "enabled" callbacks for specific nickserv commands
|
// "enabled" callbacks for specific nickserv commands
|
||||||
@ -26,6 +28,10 @@ func nsEnforceEnabled(config *Config) bool {
|
|||||||
return servCmdRequiresNickRes(config) && config.Accounts.NickReservation.AllowCustomEnforcement
|
return servCmdRequiresNickRes(config) && config.Accounts.NickReservation.AllowCustomEnforcement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func servCmdRequiresBouncerEnabled(config *Config) bool {
|
||||||
|
return config.Accounts.Bouncer.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ZNC's nickserv module will not detect this unless it is:
|
// ZNC's nickserv module will not detect this unless it is:
|
||||||
// 1. sent with prefix `nickserv`
|
// 1. sent with prefix `nickserv`
|
||||||
@ -142,6 +148,16 @@ an administrator can set use this command to set up user accounts.`,
|
|||||||
capabs: []string{"accreg"},
|
capabs: []string{"accreg"},
|
||||||
minParams: 2,
|
minParams: 2,
|
||||||
},
|
},
|
||||||
|
"sessions": {
|
||||||
|
handler: nsSessionsHandler,
|
||||||
|
help: `Syntax: $bSESSIONS [nickname]$b
|
||||||
|
|
||||||
|
SESSIONS lists information about the sessions currently attached, via
|
||||||
|
the server's bouncer functionality, to your nickname. An administrator
|
||||||
|
can use this command to list another user's sessions.`,
|
||||||
|
helpShort: `$bSESSIONS$b lists the sessions attached to a nickname.`,
|
||||||
|
enabled: servCmdRequiresBouncerEnabled,
|
||||||
|
},
|
||||||
"unregister": {
|
"unregister": {
|
||||||
handler: nsUnregisterHandler,
|
handler: nsUnregisterHandler,
|
||||||
help: `Syntax: $bUNREGISTER <username> [code]$b
|
help: `Syntax: $bUNREGISTER <username> [code]$b
|
||||||
@ -569,3 +585,34 @@ func nsEnforceHandler(server *Server, client *Client, command string, params []s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nsSessionsHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
|
target := client
|
||||||
|
|
||||||
|
if 0 < len(params) {
|
||||||
|
// same permissions check as RPL_WHOISACTUALLY for now:
|
||||||
|
if !client.HasMode(modes.Operator) {
|
||||||
|
nsNotice(rb, client.t("Command restricted"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target = server.clients.Get(params[0])
|
||||||
|
if target == nil {
|
||||||
|
nsNotice(rb, client.t("No such nick"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionData, currentIndex := target.AllSessionData(rb.session)
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Nickname %s has %d attached session(s)"), target.Nick(), len(sessionData)))
|
||||||
|
for i, session := range sessionData {
|
||||||
|
if currentIndex == i {
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Session %d (currently attached session):"), i+1))
|
||||||
|
} else {
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Session %d:"), i+1))
|
||||||
|
}
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("IP address: %s"), session.ip.String()))
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Hostname: %s"), session.hostname))
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Created at: %s"), session.ctime.Format(IRCv3TimestampFormat)))
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Last active: %s"), session.atime.Format(IRCv3TimestampFormat)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -428,17 +428,18 @@ func (server *Server) tryRegister(c *Client, session *Session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// continue registration
|
// continue registration
|
||||||
server.logger.Info("localconnect", fmt.Sprintf("Client connected [%s] [u:%s] [r:%s]", c.nick, c.username, c.realname))
|
d := c.Details()
|
||||||
server.snomasks.Send(sno.LocalConnects, fmt.Sprintf("Client connected [%s] [u:%s] [h:%s] [ip:%s] [r:%s]", c.nick, c.username, c.rawHostname, c.IPString(), c.realname))
|
server.logger.Info("localconnect", fmt.Sprintf("Client connected [%s] [u:%s] [r:%s]", d.nick, d.username, d.realname))
|
||||||
|
server.snomasks.Send(sno.LocalConnects, fmt.Sprintf("Client connected [%s] [u:%s] [h:%s] [ip:%s] [r:%s]", d.nick, d.username, c.RawHostname(), c.IPString(), d.realname))
|
||||||
|
|
||||||
// send welcome text
|
// send welcome text
|
||||||
//NOTE(dan): we specifically use the NICK here instead of the nickmask
|
//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
|
// see http://modern.ircdocs.horse/#rplwelcome-001 for details on why we avoid using the nickmask
|
||||||
c.Send(nil, server.name, RPL_WELCOME, c.nick, fmt.Sprintf(c.t("Welcome to the Internet Relay Network %s"), c.nick))
|
c.Send(nil, server.name, RPL_WELCOME, d.nick, fmt.Sprintf(c.t("Welcome to the Internet Relay Network %s"), d.nick))
|
||||||
c.Send(nil, server.name, RPL_YOURHOST, c.nick, fmt.Sprintf(c.t("Your host is %[1]s, running version %[2]s"), server.name, Ver))
|
c.Send(nil, server.name, RPL_YOURHOST, d.nick, fmt.Sprintf(c.t("Your host is %[1]s, running version %[2]s"), server.name, Ver))
|
||||||
c.Send(nil, server.name, RPL_CREATED, c.nick, fmt.Sprintf(c.t("This server was created %s"), server.ctime.Format(time.RFC1123)))
|
c.Send(nil, server.name, RPL_CREATED, d.nick, fmt.Sprintf(c.t("This server was created %s"), server.ctime.Format(time.RFC1123)))
|
||||||
//TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter
|
//TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter
|
||||||
c.Send(nil, server.name, RPL_MYINFO, c.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString)
|
c.Send(nil, server.name, RPL_MYINFO, d.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString)
|
||||||
|
|
||||||
rb := NewResponseBuffer(session)
|
rb := NewResponseBuffer(session)
|
||||||
c.RplISupport(rb)
|
c.RplISupport(rb)
|
||||||
@ -447,7 +448,7 @@ func (server *Server) tryRegister(c *Client, session *Session) {
|
|||||||
|
|
||||||
modestring := c.ModeString()
|
modestring := c.ModeString()
|
||||||
if modestring != "+" {
|
if modestring != "+" {
|
||||||
c.Send(nil, c.nickMaskString, RPL_UMODEIS, c.nick, c.ModeString())
|
c.Send(nil, d.nickMask, RPL_UMODEIS, d.nick, c.ModeString())
|
||||||
}
|
}
|
||||||
if server.logger.IsLoggingRawIO() {
|
if server.logger.IsLoggingRawIO() {
|
||||||
c.Notice(c.t("This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect."))
|
c.Notice(c.t("This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect."))
|
||||||
|
Loading…
Reference in New Issue
Block a user