mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-10 22:19:31 +01:00
accounts: Add SASL EXTERNAL handler
This commit is contained in:
parent
70665850aa
commit
b5e7d8968f
120
irc/accounts.go
120
irc/accounts.go
@ -44,6 +44,37 @@ type ClientAccount struct {
|
|||||||
Clients []*Client
|
Clients []*Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadAccountCredentials loads an account's credentials from the store.
|
||||||
|
func loadAccountCredentials(tx *buntdb.Tx, accountKey string) (*AccountCredentials, error) {
|
||||||
|
credText, err := tx.Get(fmt.Sprintf(keyAccountCredentials, accountKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var creds AccountCredentials
|
||||||
|
err = json.Unmarshal([]byte(credText), &creds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &creds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadAccount loads an account from the store, note that the account must actually exist.
|
||||||
|
func loadAccount(server *Server, tx *buntdb.Tx, accountKey string) *ClientAccount {
|
||||||
|
name, _ := tx.Get(fmt.Sprintf(keyAccountName, accountKey))
|
||||||
|
regTime, _ := tx.Get(fmt.Sprintf(keyAccountRegTime, accountKey))
|
||||||
|
regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
|
||||||
|
accountInfo := ClientAccount{
|
||||||
|
Name: name,
|
||||||
|
RegisteredAt: time.Unix(regTimeInt, 0),
|
||||||
|
Clients: []*Client{},
|
||||||
|
}
|
||||||
|
server.accounts[accountKey] = &accountInfo
|
||||||
|
|
||||||
|
return &accountInfo
|
||||||
|
}
|
||||||
|
|
||||||
// authenticateHandler parses the AUTHENTICATE command (for SASL authentication).
|
// authenticateHandler parses the AUTHENTICATE command (for SASL authentication).
|
||||||
func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||||
// sasl abort
|
// sasl abort
|
||||||
@ -101,13 +132,17 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage)
|
|||||||
}
|
}
|
||||||
client.saslValue += rawData
|
client.saslValue += rawData
|
||||||
|
|
||||||
data, err := base64.StdEncoding.DecodeString(client.saslValue)
|
var data []byte
|
||||||
if err != nil {
|
var err error
|
||||||
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed: Invalid b64 encoding")
|
if client.saslValue != "+" {
|
||||||
client.saslInProgress = false
|
data, err = base64.StdEncoding.DecodeString(client.saslValue)
|
||||||
client.saslMechanism = ""
|
if err != nil {
|
||||||
client.saslValue = ""
|
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed: Invalid b64 encoding")
|
||||||
return false
|
client.saslInProgress = false
|
||||||
|
client.saslMechanism = ""
|
||||||
|
client.saslValue = ""
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// call actual handler
|
// call actual handler
|
||||||
@ -139,28 +174,22 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
accountString := string(splitValue[0])
|
accountKey := string(splitValue[0])
|
||||||
authzid := string(splitValue[1])
|
authzid := string(splitValue[1])
|
||||||
|
|
||||||
if accountString != authzid {
|
if accountKey != authzid {
|
||||||
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed: authcid and authzid should be the same")
|
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed: authcid and authzid should be the same")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// casefolding, rough for now bit will improve later.
|
// casefolding, rough for now bit will improve later.
|
||||||
// keep it the same as in the REG CREATE stage
|
// keep it the same as in the REG CREATE stage
|
||||||
accountString = NewName(accountString).String()
|
accountKey = NewName(accountKey).String()
|
||||||
|
|
||||||
// load and check acct data all in one update to prevent races.
|
// load and check acct data all in one update to prevent races.
|
||||||
// as noted elsewhere, change to proper locking for Account type later probably
|
// as noted elsewhere, change to proper locking for Account type later probably
|
||||||
err := server.store.Update(func(tx *buntdb.Tx) error {
|
err := server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
credText, err := tx.Get(fmt.Sprintf(keyAccountCredentials, accountString))
|
creds, err := loadAccountCredentials(tx, accountKey)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var creds AccountCredentials
|
|
||||||
err = json.Unmarshal([]byte(credText), &creds)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -173,18 +202,9 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
|
|||||||
err = server.passwords.CompareHashAndPassword(creds.PassphraseHash, creds.PassphraseSalt, password)
|
err = server.passwords.CompareHashAndPassword(creds.PassphraseHash, creds.PassphraseSalt, password)
|
||||||
|
|
||||||
// succeeded, load account info if necessary
|
// succeeded, load account info if necessary
|
||||||
account, exists := server.accounts[accountString]
|
account, exists := server.accounts[accountKey]
|
||||||
if !exists {
|
if !exists {
|
||||||
name, _ := tx.Get(fmt.Sprintf(keyAccountName, accountString))
|
account = loadAccount(server, tx, accountKey)
|
||||||
regTime, _ := tx.Get(fmt.Sprintf(keyAccountRegTime, accountString))
|
|
||||||
regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
|
|
||||||
accountInfo := ClientAccount{
|
|
||||||
Name: name,
|
|
||||||
RegisteredAt: time.Unix(regTimeInt, 0),
|
|
||||||
Clients: []*Client{},
|
|
||||||
}
|
|
||||||
server.accounts[accountString] = &accountInfo
|
|
||||||
account = &accountInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
account.Clients = append(account.Clients, client)
|
account.Clients = append(account.Clients, client)
|
||||||
@ -205,6 +225,48 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
|
|||||||
|
|
||||||
// authExternalHandler parses the SASL EXTERNAL mechanism.
|
// authExternalHandler parses the SASL EXTERNAL mechanism.
|
||||||
func authExternalHandler(server *Server, client *Client, mechanism string, value []byte) bool {
|
func authExternalHandler(server *Server, client *Client, mechanism string, value []byte) bool {
|
||||||
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed: Mechanism not yet implemented")
|
if client.certfp == "" {
|
||||||
|
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed, you are not connecting with a certificate")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
err := server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
|
// certfp lookup key
|
||||||
|
accountKey, err := tx.Get(fmt.Sprintf(keyCertToAccount, client.certfp))
|
||||||
|
if err != nil {
|
||||||
|
return errSaslFail
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm account exists
|
||||||
|
_, err = tx.Get(fmt.Sprintf(keyAccountExists, accountKey))
|
||||||
|
if err != nil {
|
||||||
|
return errSaslFail
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm the certfp in that account's credentials
|
||||||
|
creds, err := loadAccountCredentials(tx, accountKey)
|
||||||
|
if err != nil || creds.Certificate != client.certfp {
|
||||||
|
return errSaslFail
|
||||||
|
}
|
||||||
|
|
||||||
|
// succeeded, load account info if necessary
|
||||||
|
account, exists := server.accounts[accountKey]
|
||||||
|
if !exists {
|
||||||
|
account = loadAccount(server, tx, accountKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
account.Clients = append(account.Clients, client)
|
||||||
|
client.account = account
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Send(nil, server.nameString, RPL_LOGGEDIN, client.nickString, client.nickMaskString, client.account.Name, fmt.Sprintf("You are now logged in as %s", client.account.Name))
|
||||||
|
client.Send(nil, server.nameString, RPL_SASLSUCCESS, client.nickString, "SASL authentication successful")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,9 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
|||||||
}
|
}
|
||||||
if isTLS {
|
if isTLS {
|
||||||
client.flags[TLS] = true
|
client.flags[TLS] = true
|
||||||
|
|
||||||
|
// error is not useful to us here anyways so we can ignore it
|
||||||
|
client.certfp, _ = client.socket.CertFP()
|
||||||
}
|
}
|
||||||
if server.checkIdent {
|
if server.checkIdent {
|
||||||
_, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String())
|
_, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String())
|
||||||
@ -205,11 +208,6 @@ func (client *Client) Register() {
|
|||||||
if client.registered {
|
if client.registered {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if client.flags[TLS] {
|
|
||||||
// error is not useful to us here anyways, so we can ignore it
|
|
||||||
client.certfp, _ = client.socket.CertFP()
|
|
||||||
//TODO(dan): login based on certfp
|
|
||||||
}
|
|
||||||
client.registered = true
|
client.registered = true
|
||||||
client.Touch()
|
client.Touch()
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,12 @@ const (
|
|||||||
keyAccountName = "account %s name" // stores the 'preferred name' of the account, not casemapped
|
keyAccountName = "account %s name" // stores the 'preferred name' of the account, not casemapped
|
||||||
keyAccountRegTime = "account %s registered.time"
|
keyAccountRegTime = "account %s registered.time"
|
||||||
keyAccountCredentials = "account %s credentials"
|
keyAccountCredentials = "account %s credentials"
|
||||||
|
keyCertToAccount = "account.creds.certfp %s"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errAccountCreation = errors.New("Account could not be created")
|
errAccountCreation = errors.New("Account could not be created")
|
||||||
|
errCertfpAlreadyExists = errors.New("An account already exists with your certificate")
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccountRegistration manages the registration of accounts.
|
// AccountRegistration manages the registration of accounts.
|
||||||
@ -91,8 +93,6 @@ func removeFailedRegCreateData(store buntdb.DB, account string) {
|
|||||||
|
|
||||||
// regCreateHandler parses the REG CREATE command.
|
// regCreateHandler parses the REG CREATE command.
|
||||||
func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||||
client.Notice("Parsing CREATE")
|
|
||||||
|
|
||||||
// get and sanitise account name
|
// get and sanitise account name
|
||||||
account := NewName(msg.Params[1])
|
account := NewName(msg.Params[1])
|
||||||
//TODO(dan): probably don't need explicit check for "*" here... until we actually casemap properly as per rfc7700
|
//TODO(dan): probably don't need explicit check for "*" here... until we actually casemap properly as per rfc7700
|
||||||
@ -196,6 +196,20 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
|
|||||||
|
|
||||||
// store details
|
// store details
|
||||||
err = server.store.Update(func(tx *buntdb.Tx) error {
|
err = server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
|
// certfp special lookup key
|
||||||
|
if credentialType == "certfp" {
|
||||||
|
assembledKeyCertToAccount := fmt.Sprintf(keyCertToAccount, client.certfp)
|
||||||
|
|
||||||
|
// make sure certfp doesn't already exist because that'd be silly
|
||||||
|
_, err := tx.Get(assembledKeyCertToAccount)
|
||||||
|
if err != buntdb.ErrNotFound {
|
||||||
|
return errCertfpAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Set(assembledKeyCertToAccount, account.String(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make creds
|
||||||
var creds AccountCredentials
|
var creds AccountCredentials
|
||||||
|
|
||||||
// always set passphrase salt
|
// always set passphrase salt
|
||||||
@ -223,7 +237,11 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
|
|||||||
|
|
||||||
// details could not be stored and relevant numerics have been dispatched, abort
|
// details could not be stored and relevant numerics have been dispatched, abort
|
||||||
if err != nil {
|
if err != nil {
|
||||||
client.Send(nil, server.nameString, ERR_UNKNOWNERROR, client.nickString, "REG", "CREATE", "Could not register")
|
errMsg := "Could not register"
|
||||||
|
if err == errCertfpAlreadyExists {
|
||||||
|
errMsg = "An account already exists for your certificate fingerprint"
|
||||||
|
}
|
||||||
|
client.Send(nil, server.nameString, ERR_UNKNOWNERROR, client.nickString, "REG", "CREATE", errMsg)
|
||||||
log.Println("Could not save registration creds:", err.Error())
|
log.Println("Could not save registration creds:", err.Error())
|
||||||
removeFailedRegCreateData(server.store, accountString)
|
removeFailedRegCreateData(server.store, accountString)
|
||||||
return false
|
return false
|
||||||
|
@ -16,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errNotTls = errors.New("Not a TLS connection")
|
errNotTLS = errors.New("Not a TLS connection")
|
||||||
errNoPeerCerts = errors.New("Client did not provide a certificate")
|
errNoPeerCerts = errors.New("Client did not provide a certificate")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -48,9 +48,12 @@ func (socket *Socket) Close() {
|
|||||||
func (socket *Socket) CertFP() (string, error) {
|
func (socket *Socket) CertFP() (string, error) {
|
||||||
var tlsConn, isTLS = socket.conn.(*tls.Conn)
|
var tlsConn, isTLS = socket.conn.(*tls.Conn)
|
||||||
if !isTLS {
|
if !isTLS {
|
||||||
return "", errNotTls
|
return "", errNotTLS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure handehake is performed
|
||||||
|
tlsConn.Handshake()
|
||||||
|
|
||||||
peerCerts := tlsConn.ConnectionState().PeerCertificates
|
peerCerts := tlsConn.ConnectionState().PeerCertificates
|
||||||
if len(peerCerts) < 1 {
|
if len(peerCerts) < 1 {
|
||||||
return "", errNoPeerCerts
|
return "", errNoPeerCerts
|
||||||
|
Loading…
Reference in New Issue
Block a user