mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-22 18:52:41 +01:00
Add NickServ "CLIENTS LIST" and "CLIENTS LOGOUT".
CLIENTS LIST shows information about clients attached to a nick. CLIENTS LOGOUT allows individual (or all) sessions to be logged out. SESSIONS is now an alias for CLIENTS LIST. Fixes #1072.
This commit is contained in:
parent
536c21a874
commit
ca2132ff09
@ -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)
|
||||||
|
@ -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
|
||||||
|
114
irc/nickserv.go
114
irc/nickserv.go
@ -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:]
|
||||||
|
Loading…
Reference in New Issue
Block a user