mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-10 22:19:31 +01:00
move authentication data from Client to Session
This commit is contained in:
parent
33dac4c0ba
commit
ad32356e34
@ -1146,13 +1146,13 @@ func (am *AccountManager) ChannelsForAccount(account string) (channels []string)
|
|||||||
return unmarshalRegisteredChannels(channelStr)
|
return unmarshalRegisteredChannels(channelStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) AuthenticateByCertFP(client *Client, authzid string) error {
|
func (am *AccountManager) AuthenticateByCertFP(client *Client, certfp, authzid string) error {
|
||||||
if client.certfp == "" {
|
if certfp == "" {
|
||||||
return errAccountInvalidCredentials
|
return errAccountInvalidCredentials
|
||||||
}
|
}
|
||||||
|
|
||||||
var account string
|
var account string
|
||||||
certFPKey := fmt.Sprintf(keyCertToAccount, client.certfp)
|
certFPKey := fmt.Sprintf(keyCertToAccount, certfp)
|
||||||
|
|
||||||
err := am.server.store.View(func(tx *buntdb.Tx) error {
|
err := am.server.store.View(func(tx *buntdb.Tx) error {
|
||||||
account, _ = tx.Get(certFPKey)
|
account, _ = tx.Get(certFPKey)
|
||||||
|
@ -51,7 +51,6 @@ type Client struct {
|
|||||||
away bool
|
away bool
|
||||||
awayMessage string
|
awayMessage string
|
||||||
brbTimer BrbTimer
|
brbTimer BrbTimer
|
||||||
certfp string
|
|
||||||
channels ChannelSet
|
channels ChannelSet
|
||||||
ctime time.Time
|
ctime time.Time
|
||||||
destroyed bool
|
destroyed bool
|
||||||
@ -77,10 +76,6 @@ type Client struct {
|
|||||||
realIP net.IP
|
realIP net.IP
|
||||||
registered bool
|
registered bool
|
||||||
resumeID string
|
resumeID string
|
||||||
saslInProgress bool
|
|
||||||
saslMechanism string
|
|
||||||
saslValue string
|
|
||||||
sentPassCommand bool
|
|
||||||
server *Server
|
server *Server
|
||||||
skeleton string
|
skeleton string
|
||||||
sessions []*Session
|
sessions []*Session
|
||||||
@ -93,6 +88,15 @@ type Client struct {
|
|||||||
writerSemaphore utils.Semaphore // tier 1.5
|
writerSemaphore utils.Semaphore // tier 1.5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type saslStatus struct {
|
||||||
|
mechanism string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *saslStatus) Clear() {
|
||||||
|
*s = saslStatus{}
|
||||||
|
}
|
||||||
|
|
||||||
// Session is an individual client connection to the server (TCP connection
|
// Session is an individual client connection to the server (TCP connection
|
||||||
// and associated per-connection data, such as capabilities). There is a
|
// and associated per-connection data, such as capabilities). There is a
|
||||||
// many-one relationship between sessions and clients.
|
// many-one relationship between sessions and clients.
|
||||||
@ -112,6 +116,10 @@ type Session struct {
|
|||||||
fakelag Fakelag
|
fakelag Fakelag
|
||||||
destroyed uint32
|
destroyed uint32
|
||||||
|
|
||||||
|
certfp string
|
||||||
|
sasl saslStatus
|
||||||
|
sentPassCommand bool
|
||||||
|
|
||||||
batchCounter uint32
|
batchCounter uint32
|
||||||
|
|
||||||
quitMessage string
|
quitMessage string
|
||||||
@ -271,7 +279,7 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
|
|||||||
if conn.Config.TLSConfig != nil {
|
if conn.Config.TLSConfig != nil {
|
||||||
client.SetMode(modes.TLS, true)
|
client.SetMode(modes.TLS, true)
|
||||||
// error is not useful to us here anyways so we can ignore it
|
// error is not useful to us here anyways so we can ignore it
|
||||||
client.certfp, _ = socket.CertFP()
|
session.certfp, _ = socket.CertFP()
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn.Config.Tor {
|
if conn.Config.Tor {
|
||||||
@ -451,18 +459,18 @@ const (
|
|||||||
authFailSaslRequired
|
authFailSaslRequired
|
||||||
)
|
)
|
||||||
|
|
||||||
func (client *Client) isAuthorized(config *Config, isTor bool) AuthOutcome {
|
func (client *Client) isAuthorized(config *Config, session *Session) AuthOutcome {
|
||||||
saslSent := client.account != ""
|
saslSent := client.account != ""
|
||||||
// PASS requirement
|
// PASS requirement
|
||||||
if (config.Server.passwordBytes != nil) && !client.sentPassCommand && !(config.Accounts.SkipServerPassword && saslSent) {
|
if (config.Server.passwordBytes != nil) && !session.sentPassCommand && !(config.Accounts.SkipServerPassword && saslSent) {
|
||||||
return authFailPass
|
return authFailPass
|
||||||
}
|
}
|
||||||
// Tor connections may be required to authenticate with SASL
|
// Tor connections may be required to authenticate with SASL
|
||||||
if isTor && config.Server.TorListeners.RequireSasl && !saslSent {
|
if session.isTor && config.Server.TorListeners.RequireSasl && !saslSent {
|
||||||
return authFailTorSaslRequired
|
return authFailTorSaslRequired
|
||||||
}
|
}
|
||||||
// finally, enforce require-sasl
|
// finally, enforce require-sasl
|
||||||
if config.Accounts.RequireSasl.Enabled && !saslSent && !utils.IPInNets(client.IP(), config.Accounts.RequireSasl.exemptedNets) {
|
if config.Accounts.RequireSasl.Enabled && !saslSent && !utils.IPInNets(session.IP(), config.Accounts.RequireSasl.exemptedNets) {
|
||||||
return authFailSaslRequired
|
return authFailSaslRequired
|
||||||
}
|
}
|
||||||
return authSuccess
|
return authSuccess
|
||||||
@ -1518,11 +1526,11 @@ func (client *Client) CheckInvited(casefoldedChannel string) (invited bool) {
|
|||||||
// Implements auto-oper by certfp (scans for an auto-eligible operator block that matches
|
// Implements auto-oper by certfp (scans for an auto-eligible operator block that matches
|
||||||
// the client's cert, then applies it).
|
// the client's cert, then applies it).
|
||||||
func (client *Client) attemptAutoOper(session *Session) {
|
func (client *Client) attemptAutoOper(session *Session) {
|
||||||
if client.certfp == "" || client.HasMode(modes.Operator) {
|
if session.certfp == "" || client.HasMode(modes.Operator) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, oper := range client.server.Config().operators {
|
for _, oper := range client.server.Config().operators {
|
||||||
if oper.Auto && oper.Pass == nil && oper.Fingerprint != "" && oper.Fingerprint == client.certfp {
|
if oper.Auto && oper.Pass == nil && oper.Fingerprint != "" && oper.Fingerprint == session.certfp {
|
||||||
rb := NewResponseBuffer(session)
|
rb := NewResponseBuffer(session)
|
||||||
applyOper(client, oper, rb)
|
applyOper(client, oper, rb)
|
||||||
rb.Send(true)
|
rb.Send(true)
|
||||||
|
@ -88,7 +88,7 @@ func (client *Client) ApplyProxiedIP(session *Session, proxiedIP string, tls boo
|
|||||||
session.proxiedIP = parsedProxiedIP
|
session.proxiedIP = parsedProxiedIP
|
||||||
// nickmask will be updated when the client completes registration
|
// nickmask will be updated when the client completes registration
|
||||||
// set tls info
|
// set tls info
|
||||||
client.certfp = ""
|
session.certfp = ""
|
||||||
client.SetMode(modes.TLS, tls)
|
client.SetMode(modes.TLS, tls)
|
||||||
|
|
||||||
return nil, ""
|
return nil, ""
|
||||||
|
@ -65,6 +65,7 @@ type SessionData struct {
|
|||||||
atime time.Time
|
atime time.Time
|
||||||
ip net.IP
|
ip net.IP
|
||||||
hostname string
|
hostname string
|
||||||
|
certfp string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) AllSessionData(currentSession *Session) (data []SessionData, currentIndex int) {
|
func (client *Client) AllSessionData(currentSession *Session) (data []SessionData, currentIndex int) {
|
||||||
@ -81,6 +82,7 @@ func (client *Client) AllSessionData(currentSession *Session) (data []SessionDat
|
|||||||
atime: session.atime,
|
atime: session.atime,
|
||||||
ctime: session.ctime,
|
ctime: session.ctime,
|
||||||
hostname: session.rawHostname,
|
hostname: session.rawHostname,
|
||||||
|
certfp: session.certfp,
|
||||||
}
|
}
|
||||||
if session.proxiedIP != nil {
|
if session.proxiedIP != nil {
|
||||||
data[i].ip = session.proxiedIP
|
data[i].ip = session.proxiedIP
|
||||||
|
@ -112,6 +112,7 @@ func sendSuccessfulAccountAuth(client *Client, rb *ResponseBuffer, forNS, forSAS
|
|||||||
|
|
||||||
// AUTHENTICATE [<mechanism>|<data>|*]
|
// AUTHENTICATE [<mechanism>|<data>|*]
|
||||||
func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
session := rb.session
|
||||||
config := server.Config()
|
config := server.Config()
|
||||||
details := client.Details()
|
details := client.Details()
|
||||||
|
|
||||||
@ -128,20 +129,17 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage,
|
|||||||
// sasl abort
|
// sasl abort
|
||||||
if !server.AccountConfig().AuthenticationEnabled || len(msg.Params) == 1 && msg.Params[0] == "*" {
|
if !server.AccountConfig().AuthenticationEnabled || len(msg.Params) == 1 && msg.Params[0] == "*" {
|
||||||
rb.Add(nil, server.name, ERR_SASLABORTED, details.nick, client.t("SASL authentication aborted"))
|
rb.Add(nil, server.name, ERR_SASLABORTED, details.nick, client.t("SASL authentication aborted"))
|
||||||
client.saslInProgress = false
|
session.sasl.Clear()
|
||||||
client.saslMechanism = ""
|
|
||||||
client.saslValue = ""
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// start new sasl session
|
// start new sasl session
|
||||||
if !client.saslInProgress {
|
if session.sasl.mechanism == "" {
|
||||||
mechanism := strings.ToUpper(msg.Params[0])
|
mechanism := strings.ToUpper(msg.Params[0])
|
||||||
_, mechanismIsEnabled := EnabledSaslMechanisms[mechanism]
|
_, mechanismIsEnabled := EnabledSaslMechanisms[mechanism]
|
||||||
|
|
||||||
if mechanismIsEnabled {
|
if mechanismIsEnabled {
|
||||||
client.saslInProgress = true
|
session.sasl.mechanism = mechanism
|
||||||
client.saslMechanism = mechanism
|
|
||||||
if !config.Server.Compatibility.SendUnprefixedSasl {
|
if !config.Server.Compatibility.SendUnprefixedSasl {
|
||||||
// normal behavior
|
// normal behavior
|
||||||
rb.Add(nil, server.name, "AUTHENTICATE", "+")
|
rb.Add(nil, server.name, "AUTHENTICATE", "+")
|
||||||
@ -162,58 +160,46 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage,
|
|||||||
|
|
||||||
if len(rawData) > 400 {
|
if len(rawData) > 400 {
|
||||||
rb.Add(nil, server.name, ERR_SASLTOOLONG, details.nick, client.t("SASL message too long"))
|
rb.Add(nil, server.name, ERR_SASLTOOLONG, details.nick, client.t("SASL message too long"))
|
||||||
client.saslInProgress = false
|
session.sasl.Clear()
|
||||||
client.saslMechanism = ""
|
|
||||||
client.saslValue = ""
|
|
||||||
return false
|
return false
|
||||||
} else if len(rawData) == 400 {
|
} else if len(rawData) == 400 {
|
||||||
client.saslValue += rawData
|
|
||||||
// allow 4 'continuation' lines before rejecting for length
|
// allow 4 'continuation' lines before rejecting for length
|
||||||
if len(client.saslValue) > 400*4 {
|
if len(session.sasl.value) >= 400*4 {
|
||||||
rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Passphrase too long"))
|
rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Passphrase too long"))
|
||||||
client.saslInProgress = false
|
session.sasl.Clear()
|
||||||
client.saslMechanism = ""
|
|
||||||
client.saslValue = ""
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
session.sasl.value += rawData
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if rawData != "+" {
|
if rawData != "+" {
|
||||||
client.saslValue += rawData
|
session.sasl.value += rawData
|
||||||
}
|
}
|
||||||
|
|
||||||
var data []byte
|
var data []byte
|
||||||
var err error
|
var err error
|
||||||
if client.saslValue != "+" {
|
if session.sasl.value != "+" {
|
||||||
data, err = base64.StdEncoding.DecodeString(client.saslValue)
|
data, err = base64.StdEncoding.DecodeString(session.sasl.value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Invalid b64 encoding"))
|
rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Invalid b64 encoding"))
|
||||||
client.saslInProgress = false
|
session.sasl.Clear()
|
||||||
client.saslMechanism = ""
|
|
||||||
client.saslValue = ""
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// call actual handler
|
// call actual handler
|
||||||
handler, handlerExists := EnabledSaslMechanisms[client.saslMechanism]
|
handler, handlerExists := EnabledSaslMechanisms[session.sasl.mechanism]
|
||||||
|
|
||||||
// like 100% not required, but it's good to be safe I guess
|
// like 100% not required, but it's good to be safe I guess
|
||||||
if !handlerExists {
|
if !handlerExists {
|
||||||
rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed"))
|
rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed"))
|
||||||
client.saslInProgress = false
|
session.sasl.Clear()
|
||||||
client.saslMechanism = ""
|
|
||||||
client.saslValue = ""
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// let the SASL handler do its thing
|
// let the SASL handler do its thing
|
||||||
exiting := handler(server, client, client.saslMechanism, data, rb)
|
exiting := handler(server, client, session.sasl.mechanism, data, rb)
|
||||||
|
session.sasl.Clear()
|
||||||
// wait 'til SASL is done before emptying the sasl vars
|
|
||||||
client.saslInProgress = false
|
|
||||||
client.saslMechanism = ""
|
|
||||||
client.saslValue = ""
|
|
||||||
|
|
||||||
return exiting
|
return exiting
|
||||||
}
|
}
|
||||||
@ -270,7 +256,7 @@ func authErrorToMessage(server *Server, err error) (msg string) {
|
|||||||
|
|
||||||
// AUTHENTICATE EXTERNAL
|
// AUTHENTICATE EXTERNAL
|
||||||
func authExternalHandler(server *Server, client *Client, mechanism string, value []byte, rb *ResponseBuffer) bool {
|
func authExternalHandler(server *Server, client *Client, mechanism string, value []byte, rb *ResponseBuffer) bool {
|
||||||
if client.certfp == "" {
|
if rb.session.certfp == "" {
|
||||||
rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed, you are not connecting with a certificate"))
|
rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed, you are not connecting with a certificate"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -287,7 +273,7 @@ func authExternalHandler(server *Server, client *Client, mechanism string, value
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = server.accounts.AuthenticateByCertFP(client, authzid)
|
err = server.accounts.AuthenticateByCertFP(client, rb.session.certfp, authzid)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -2055,7 +2041,7 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
|
|||||||
oper := server.GetOperator(msg.Params[0])
|
oper := server.GetOperator(msg.Params[0])
|
||||||
if oper != nil {
|
if oper != nil {
|
||||||
if oper.Fingerprint != "" {
|
if oper.Fingerprint != "" {
|
||||||
if oper.Fingerprint == client.certfp {
|
if oper.Fingerprint == rb.session.certfp {
|
||||||
checkPassed = true
|
checkPassed = true
|
||||||
} else {
|
} else {
|
||||||
checkFailed = true
|
checkFailed = true
|
||||||
@ -2144,7 +2130,7 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
|
|||||||
|
|
||||||
// check the provided password
|
// check the provided password
|
||||||
password := []byte(msg.Params[0])
|
password := []byte(msg.Params[0])
|
||||||
client.sentPassCommand = bcrypt.CompareHashAndPassword(serverPassword, password) == nil
|
rb.session.sentPassCommand = bcrypt.CompareHashAndPassword(serverPassword, password) == nil
|
||||||
|
|
||||||
// if they failed the check, we'll bounce them later when they try to complete registration
|
// if they failed the check, we'll bounce them later when they try to complete registration
|
||||||
return false
|
return false
|
||||||
@ -2523,7 +2509,7 @@ func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
|||||||
if 0 < len(info.Password) && bcrypt.CompareHashAndPassword(info.Password, givenPassword) != nil {
|
if 0 < len(info.Password) && bcrypt.CompareHashAndPassword(info.Password, givenPassword) != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if info.Fingerprint != "" && info.Fingerprint != client.certfp {
|
if info.Fingerprint != "" && info.Fingerprint != rb.session.certfp {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -605,7 +605,7 @@ func nsIdentifyHandler(server *Server, client *Client, command string, params []
|
|||||||
|
|
||||||
var username, passphrase string
|
var username, passphrase string
|
||||||
if len(params) == 1 {
|
if len(params) == 1 {
|
||||||
if client.certfp != "" {
|
if rb.session.certfp != "" {
|
||||||
username = params[0]
|
username = params[0]
|
||||||
} else {
|
} else {
|
||||||
// XXX undocumented compatibility mode with other nickservs, allowing
|
// XXX undocumented compatibility mode with other nickservs, allowing
|
||||||
@ -628,8 +628,8 @@ func nsIdentifyHandler(server *Server, client *Client, command string, params []
|
|||||||
}
|
}
|
||||||
|
|
||||||
// try certfp
|
// try certfp
|
||||||
if !loginSuccessful && client.certfp != "" {
|
if !loginSuccessful && rb.session.certfp != "" {
|
||||||
err := server.accounts.AuthenticateByCertFP(client, "")
|
err := server.accounts.AuthenticateByCertFP(client, rb.session.certfp, "")
|
||||||
loginSuccessful = (err == nil)
|
loginSuccessful = (err == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -693,7 +693,7 @@ func nsRegisterHandler(server *Server, client *Client, command string, params []
|
|||||||
email = params[1]
|
email = params[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
certfp := client.certfp
|
certfp := rb.session.certfp
|
||||||
if passphrase == "*" {
|
if passphrase == "*" {
|
||||||
if certfp == "" {
|
if certfp == "" {
|
||||||
nsNotice(rb, client.t("You must be connected with TLS and a client certificate to do this"))
|
nsNotice(rb, client.t("You must be connected with TLS and a client certificate to do this"))
|
||||||
@ -733,7 +733,7 @@ func nsRegisterHandler(server *Server, client *Client, command string, params []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, client.certfp)
|
err := server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, rb.session.certfp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if callbackNamespace == "*" {
|
if callbackNamespace == "*" {
|
||||||
err = server.accounts.Verify(client, account, "")
|
err = server.accounts.Verify(client, account, "")
|
||||||
@ -951,6 +951,9 @@ func nsSessionsHandler(server *Server, client *Client, command string, params []
|
|||||||
nsNotice(rb, fmt.Sprintf(client.t("Hostname: %s"), session.hostname))
|
nsNotice(rb, fmt.Sprintf(client.t("Hostname: %s"), session.hostname))
|
||||||
nsNotice(rb, fmt.Sprintf(client.t("Created at: %s"), session.ctime.Format(time.RFC1123)))
|
nsNotice(rb, fmt.Sprintf(client.t("Created at: %s"), session.ctime.Format(time.RFC1123)))
|
||||||
nsNotice(rb, fmt.Sprintf(client.t("Last active: %s"), session.atime.Format(time.RFC1123)))
|
nsNotice(rb, fmt.Sprintf(client.t("Last active: %s"), session.atime.Format(time.RFC1123)))
|
||||||
|
if session.certfp != "" {
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Certfp: %s"), session.certfp))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,7 +321,7 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) {
|
|||||||
|
|
||||||
// client MUST send PASS if necessary, or authenticate with SASL if necessary,
|
// client MUST send PASS if necessary, or authenticate with SASL if necessary,
|
||||||
// before completing the other registration commands
|
// before completing the other registration commands
|
||||||
authOutcome := c.isAuthorized(server.Config(), session.isTor)
|
authOutcome := c.isAuthorized(server.Config(), session)
|
||||||
var quitMessage string
|
var quitMessage string
|
||||||
switch authOutcome {
|
switch authOutcome {
|
||||||
case authFailPass:
|
case authFailPass:
|
||||||
@ -508,8 +508,12 @@ func (client *Client) getWhoisOf(target *Client, rb *ResponseBuffer) {
|
|||||||
rb.Add(nil, client.server.name, RPL_WHOISBOT, cnick, tnick, ircfmt.Unescape(fmt.Sprintf(client.t("is a $bBot$b on %s"), client.server.Config().Network.Name)))
|
rb.Add(nil, client.server.name, RPL_WHOISBOT, cnick, tnick, ircfmt.Unescape(fmt.Sprintf(client.t("is a $bBot$b on %s"), client.server.Config().Network.Name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if target.certfp != "" && (client.HasMode(modes.Operator) || client == target) {
|
if client == target || client.HasMode(modes.Operator) {
|
||||||
rb.Add(nil, client.server.name, RPL_WHOISCERTFP, cnick, tnick, fmt.Sprintf(client.t("has client certificate fingerprint %s"), target.certfp))
|
for _, session := range target.Sessions() {
|
||||||
|
if session.certfp != "" {
|
||||||
|
rb.Add(nil, client.server.name, RPL_WHOISCERTFP, cnick, tnick, fmt.Sprintf(client.t("has client certificate fingerprint %s"), session.certfp))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rb.Add(nil, client.server.name, RPL_WHOISIDLE, cnick, tnick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), client.t("seconds idle, signon time"))
|
rb.Add(nil, client.server.name, RPL_WHOISIDLE, cnick, tnick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), client.t("seconds idle, signon time"))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user