3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-11 06:29:29 +01:00

Merge pull request #1272 from ajaspers/session_kill

Add NickServ "CLIENTS LIST" and "CLIENTS LOGOUT".
This commit is contained in:
Shivaram Lingamneni 2020-09-21 03:24:53 -07:00 committed by GitHub
commit f9ca93118f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 123 additions and 22 deletions

View File

@ -90,6 +90,7 @@ type Client struct {
lastSeen map[string]time.Time // maps device ID (including "") to time of last received command lastSeen map[string]time.Time // maps device ID (including "") to time of last received command
lastSeenLastWrite time.Time // last time `lastSeen` was written to the datastore lastSeenLastWrite time.Time // last time `lastSeen` was written to the datastore
loginThrottle connection_limits.GenericThrottle loginThrottle connection_limits.GenericThrottle
nextSessionID int64 // Incremented when a new session is established
nick string nick string
nickCasefolded string nickCasefolded string
nickMaskCasefolded string nickMaskCasefolded string
@ -150,6 +151,7 @@ type Session struct {
idleTimer *time.Timer idleTimer *time.Timer
pingSent bool // we sent PING to a putatively idle connection and we're waiting for PONG pingSent bool // we sent PING to a putatively idle connection and we're waiting for PONG
sessionID int64
socket *Socket socket *Socket
realIP net.IP realIP net.IP
proxiedIP net.IP proxiedIP net.IP
@ -353,6 +355,7 @@ func (server *Server) RunClient(conn IRCConn) {
realIP: realIP, realIP: realIP,
proxiedIP: proxiedIP, proxiedIP: proxiedIP,
requireSASL: requireSASL, requireSASL: requireSASL,
nextSessionID: 1,
} }
if requireSASL { if requireSASL {
client.requireSASLMessage = banMsg client.requireSASLMessage = banMsg
@ -420,6 +423,8 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
alwaysOn: true, alwaysOn: true,
realname: realname, realname: realname,
nextSessionID: 1,
} }
client.SetMode(modes.TLS, true) client.SetMode(modes.TLS, true)

View File

@ -65,12 +65,13 @@ func (client *Client) GetSessionByResumeID(resumeID string) (result *Session) {
} }
type SessionData struct { type SessionData struct {
ctime time.Time ctime time.Time
atime time.Time atime time.Time
ip net.IP ip net.IP
hostname string hostname string
certfp string certfp string
deviceID string deviceID string
sessionID int64
} }
func (client *Client) AllSessionData(currentSession *Session) (data []SessionData, currentIndex int) { func (client *Client) AllSessionData(currentSession *Session) (data []SessionData, currentIndex int) {
@ -84,11 +85,12 @@ func (client *Client) AllSessionData(currentSession *Session) (data []SessionDat
currentIndex = i currentIndex = i
} }
data[i] = SessionData{ data[i] = SessionData{
atime: session.lastActive, atime: session.lastActive,
ctime: session.ctime, ctime: session.ctime,
hostname: session.rawHostname, hostname: session.rawHostname,
certfp: session.certfp, certfp: session.certfp,
deviceID: session.deviceID, deviceID: session.deviceID,
sessionID: session.sessionID,
} }
if session.proxiedIP != nil { if session.proxiedIP != nil {
data[i].ip = session.proxiedIP data[i].ip = session.proxiedIP
@ -109,6 +111,8 @@ func (client *Client) AddSession(session *Session) (success bool, numSessions in
} }
// success, attach the new session to the client // success, attach the new session to the client
session.client = client session.client = client
session.sessionID = client.nextSessionID
client.nextSessionID++
newSessions := make([]*Session, len(client.sessions)+1) newSessions := make([]*Session, len(client.sessions)+1)
copy(newSessions, client.sessions) copy(newSessions, client.sessions)
newSessions[len(newSessions)-1] = session newSessions[len(newSessions)-1] = session

View File

@ -43,6 +43,23 @@ const nickservHelp = `NickServ lets you register, log in to, and manage an accou
var ( var (
nickservCommands = map[string]*serviceCommand{ nickservCommands = map[string]*serviceCommand{
"clients": {
handler: nsClientsHandler,
help: `Syntax: $bCLIENTS LIST [nickname]$b
CLIENTS LIST shows information about the clients currently attached, via
the server's multiclient functionality, to your nickname. An administrator
can use this command to list another user's clients.
Syntax: $bCLIENTS LOGOUT [nickname] [client_id/all]$b
CLIENTS LOGOUT detaches a single client, or all other clients currently
attached, via the server's multiclient functionality, to your nickname. An
administrator can use this command to logout another user's clients.`,
helpShort: `$bCLIENTS$b can list and logout the sessions attached to a nickname.`,
enabled: servCmdRequiresBouncerEnabled,
minParams: 1,
},
"drop": { "drop": {
handler: nsDropHandler, handler: nsDropHandler,
help: `Syntax: $bDROP [nickname]$b help: `Syntax: $bDROP [nickname]$b
@ -150,14 +167,13 @@ an administrator can set use this command to set up user accounts.`,
minParams: 1, minParams: 1,
}, },
"sessions": { "sessions": {
handler: nsSessionsHandler, hidden: true,
handler: nsClientsHandler,
help: `Syntax: $bSESSIONS [nickname]$b help: `Syntax: $bSESSIONS [nickname]$b
SESSIONS lists information about the sessions currently attached, via SESSIONS is an alias for $bCLIENTS LIST$b. See the help entry for $bCLIENTS$b
the server's multiclient functionality, to your nickname. An administrator for more information.`,
can use this command to list another user's sessions.`, enabled: servCmdRequiresBouncerEnabled,
helpShort: `$bSESSIONS$b lists the sessions attached to a nickname.`,
enabled: servCmdRequiresBouncerEnabled,
}, },
"unregister": { "unregister": {
handler: nsUnregisterHandler, handler: nsUnregisterHandler,
@ -1065,9 +1081,30 @@ func nsEnforceHandler(server *Server, client *Client, command string, params []s
} }
} }
func nsSessionsHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { func nsClientsHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
target := client var verb string
if command == "sessions" {
// Legacy "SESSIONS" command is an alias for CLIENTS LIST.
verb = "list"
} else if len(params) > 0 {
verb = strings.ToLower(params[0])
params = params[1:]
}
switch verb {
case "list":
nsClientsListHandler(server, client, params, rb)
case "logout":
nsClientsLogoutHandler(server, client, params, rb)
default:
nsNotice(rb, client.t("Invalid parameters"))
return
}
}
func nsClientsListHandler(server *Server, client *Client, params []string, rb *ResponseBuffer) {
target := client
if 0 < len(params) { if 0 < len(params) {
target = server.clients.Get(params[0]) target = server.clients.Get(params[0])
if target == nil { if target == nil {
@ -1082,12 +1119,12 @@ func nsSessionsHandler(server *Server, client *Client, command string, params []
} }
sessionData, currentIndex := target.AllSessionData(rb.session) sessionData, currentIndex := target.AllSessionData(rb.session)
nsNotice(rb, fmt.Sprintf(client.t("Nickname %[1]s has %[2]d attached session(s)"), target.Nick(), len(sessionData))) nsNotice(rb, fmt.Sprintf(client.t("Nickname %[1]s has %[2]d attached clients(s)"), target.Nick(), len(sessionData)))
for i, session := range sessionData { for i, session := range sessionData {
if currentIndex == i { if currentIndex == i {
nsNotice(rb, fmt.Sprintf(client.t("Session %d (currently attached session):"), i+1)) nsNotice(rb, fmt.Sprintf(client.t("Client %d (currently attached client):"), session.sessionID))
} else { } else {
nsNotice(rb, fmt.Sprintf(client.t("Session %d:"), i+1)) nsNotice(rb, fmt.Sprintf(client.t("Client %d:"), session.sessionID))
} }
if session.deviceID != "" { if session.deviceID != "" {
nsNotice(rb, fmt.Sprintf(client.t("Device ID: %s"), session.deviceID)) nsNotice(rb, fmt.Sprintf(client.t("Device ID: %s"), session.deviceID))
@ -1102,6 +1139,61 @@ func nsSessionsHandler(server *Server, client *Client, command string, params []
} }
} }
func nsClientsLogoutHandler(server *Server, client *Client, params []string, rb *ResponseBuffer) {
if len(params) < 1 {
nsNotice(rb, client.t("Missing client ID to logout (or \"all\")"))
return
}
target := client
if len(params) >= 2 {
// CLIENTS LOGOUT [nickname] [client ID]
target = server.clients.Get(params[0])
if target == nil {
nsNotice(rb, client.t("No such nick"))
return
}
// User must have "local_kill" privileges to logout other user sessions.
if target != client {
oper := client.Oper()
if oper == nil || !oper.Class.Capabilities.Has("local_kill") {
nsNotice(rb, client.t("Insufficient oper privs"))
return
}
}
params = params[1:]
}
var sessionToDestroy *Session // target.destroy(nil) will logout all sessions
if strings.ToLower(params[0]) != "all" {
sessionID, err := strconv.ParseInt(params[0], 10, 64)
if err != nil {
nsNotice(rb, client.t("Client ID to logout should be an integer (or \"all\")"))
return
}
// Find the client ID that the user requested to logout.
sessions := target.Sessions()
for _, session := range sessions {
if session.sessionID == sessionID {
sessionToDestroy = session
}
}
if sessionToDestroy == nil {
nsNotice(rb, client.t("Specified client ID does not exist"))
return
}
}
target.destroy(sessionToDestroy)
if (sessionToDestroy != nil && rb.session != sessionToDestroy) || client != target {
if sessionToDestroy != nil {
nsNotice(rb, client.t("Successfully logged out session"))
} else {
nsNotice(rb, client.t("Successfully logged out all sessions"))
}
}
}
func nsCertHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { func nsCertHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
verb := strings.ToLower(params[0]) verb := strings.ToLower(params[0])
params = params[1:] params = params[1:]