add NICKSERV SESSIONS command to list sessions

This commit is contained in:
Shivaram Lingamneni 2019-05-08 04:11:54 -04:00
parent b11bf503e7
commit da656c07c8
6 changed files with 122 additions and 22 deletions

View File

@ -94,7 +94,14 @@ type Client struct {
type Session struct {
client *Client
socket *Socket
ctime time.Time
atime time.Time
socket *Socket
realIP net.IP
proxiedIP net.IP
rawHostname string
idletimer IdleTimer
fakelag Fakelag
@ -104,9 +111,6 @@ type Session struct {
maxlenRest uint32
capState caps.State
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
@ -187,6 +191,8 @@ func RunNewClient(server *Server, conn clientConn) {
socket: socket,
capVersion: caps.Cap301,
capState: caps.NoneState,
ctime: now,
atime: now,
}
session.SetMaxlenRest()
client.sessions = []*Session{session}
@ -197,20 +203,29 @@ func RunNewClient(server *Server, conn clientConn) {
client.certfp, _ = socket.CertFP()
}
remoteAddr := conn.Conn.RemoteAddr()
if conn.IsTor {
client.SetMode(modes.TLS, true)
client.realIP = utils.IPv4LoopbackAddress
client.rawHostname = config.Server.TorListeners.Vhost
session.realIP = utils.AddrToIP(remoteAddr)
// 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 {
remoteAddr := conn.Conn.RemoteAddr()
client.realIP = utils.AddrToIP(remoteAddr)
// Set the hostname for this client
// (may be overridden by a later PROXY command from stunnel)
client.rawHostname = utils.LookupHostname(client.realIP.String())
session.realIP = utils.AddrToIP(remoteAddr)
// set the hostname for this client (may be overridden later by PROXY or WEBIRC)
session.rawHostname = utils.LookupHostname(session.realIP.String())
if utils.AddrIsLocal(remoteAddr) {
// treat local connections as secure (may be overridden later by WEBIRC)
client.SetMode(modes.TLS, true)
}
if config.Server.CheckIdent && !utils.AddrIsUnix(remoteAddr) {
client.doIdentLookup(conn.Conn)
}
}
client.realIP = session.realIP
client.rawHostname = session.rawHostname
client.proxiedIP = session.proxiedIP
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).
func (client *Client) Active() {
func (client *Client) Active(session *Session) {
// TODO normalize all times to utc?
now := time.Now()
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
client.atime = time.Now()
session.atime = now
client.atime = now
}
// Ping sends the client a PING message.

View File

@ -58,8 +58,8 @@ func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ir
session.idletimer.Touch()
}
if !cmd.leaveClientIdle {
client.Active()
if client.registered && !cmd.leaveClientIdle {
client.Active(session)
}
return exiting

View File

@ -73,7 +73,9 @@ func (client *Client) ApplyProxiedIP(session *Session, proxiedIP string, tls boo
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
session.proxiedIP = parsedProxiedIP
client.proxiedIP = parsedProxiedIP
session.rawHostname = rawHostname
client.rawHostname = rawHostname
// nickmask will be updated when the client completes registration
// set tls info

View File

@ -4,6 +4,7 @@
package irc
import (
"net"
"time"
"github.com/oragono/oragono/irc/isupport"
@ -70,6 +71,37 @@ func (client *Client) Sessions() (sessions []*Session) {
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) {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()

View File

@ -7,6 +7,8 @@ import (
"fmt"
"github.com/goshuirc/irc-go/ircfmt"
"github.com/oragono/oragono/irc/modes"
)
// "enabled" callbacks for specific nickserv commands
@ -26,6 +28,10 @@ func nsEnforceEnabled(config *Config) bool {
return servCmdRequiresNickRes(config) && config.Accounts.NickReservation.AllowCustomEnforcement
}
func servCmdRequiresBouncerEnabled(config *Config) bool {
return config.Accounts.Bouncer.Enabled
}
var (
// ZNC's nickserv module will not detect this unless it is:
// 1. sent with prefix `nickserv`
@ -142,6 +148,16 @@ an administrator can set use this command to set up user accounts.`,
capabs: []string{"accreg"},
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": {
handler: nsUnregisterHandler,
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)))
}
}

View File

@ -428,17 +428,18 @@ func (server *Server) tryRegister(c *Client, session *Session) {
}
// continue registration
server.logger.Info("localconnect", fmt.Sprintf("Client connected [%s] [u:%s] [r:%s]", c.nick, c.username, c.realname))
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))
d := c.Details()
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
//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
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_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_CREATED, c.nick, fmt.Sprintf(c.t("This server was created %s"), server.ctime.Format(time.RFC1123)))
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, 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, 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
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)
c.RplISupport(rb)
@ -447,7 +448,7 @@ func (server *Server) tryRegister(c *Client, session *Session) {
modestring := c.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() {
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."))