move authentication data from Client to Session

This commit is contained in:
Shivaram Lingamneni 2020-02-18 21:42:27 -05:00
parent 33dac4c0ba
commit ad32356e34
7 changed files with 62 additions and 59 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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, ""

View File

@ -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

View File

@ -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
} }

View File

@ -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))
}
} }
} }

View File

@ -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"))
} }