2017-03-27 14:15:02 +02:00
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
2016-09-04 11:25:33 +02:00
// released under the MIT license
package irc
2016-09-06 08:31:59 +02:00
import (
2016-09-07 12:46:01 +02:00
"encoding/json"
"errors"
"fmt"
"strconv"
2016-09-06 08:31:59 +02:00
"time"
2017-06-15 18:14:19 +02:00
"github.com/goshuirc/irc-go/ircfmt"
2017-09-29 04:07:52 +02:00
"github.com/oragono/oragono/irc/caps"
2017-06-14 20:00:53 +02:00
"github.com/oragono/oragono/irc/sno"
2016-09-07 12:46:01 +02:00
"github.com/tidwall/buntdb"
2016-09-06 08:31:59 +02:00
)
2016-09-04 11:25:33 +02:00
2017-03-11 13:01:40 +01:00
const (
keyAccountExists = "account.exists %s"
keyAccountVerified = "account.verified %s"
keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped
keyAccountRegTime = "account.registered.time %s"
keyAccountCredentials = "account.credentials %s"
keyCertToAccount = "account.creds.certfp %s"
)
2016-09-05 14:35:13 +02:00
var (
2016-09-06 08:31:59 +02:00
// EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
// This can be moved to some other data structure/place if we need to load/unload mechs later.
EnabledSaslMechanisms = map [ string ] func ( * Server , * Client , string , [ ] byte ) bool {
"PLAIN" : authPlainHandler ,
"EXTERNAL" : authExternalHandler ,
}
// NoAccount is a placeholder which means that the user is not logged into an account.
2016-09-05 14:35:13 +02:00
NoAccount = ClientAccount {
Name : "*" , // * is used until actual account name is set
}
2016-09-07 12:46:01 +02:00
// generic sasl fail error
errSaslFail = errors . New ( "SASL failed" )
2016-09-05 14:35:13 +02:00
)
// ClientAccount represents a user account.
type ClientAccount struct {
2016-09-04 11:25:33 +02:00
// Name of the account.
Name string
// RegisteredAt represents the time that the account was registered.
RegisteredAt time . Time
// Clients that are currently logged into this account (useful for notifications).
2016-09-05 14:54:09 +02:00
Clients [ ] * Client
2016-09-04 11:25:33 +02:00
}
2016-09-06 08:31:59 +02:00
2016-09-07 13:32:58 +02:00
// 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
}
2017-03-08 12:50:12 +01:00
// LoginToAccount logs the client into the given account.
func ( client * Client ) LoginToAccount ( account * ClientAccount ) {
if client . account == account {
// already logged into this acct, no changing necessary
return
2017-09-28 07:49:01 +02:00
} else if client . LoggedIntoAccount ( ) {
2017-03-08 12:50:12 +01:00
// logout of existing acct
var newClientAccounts [ ] * Client
for _ , c := range account . Clients {
if c != client {
newClientAccounts = append ( newClientAccounts , c )
}
}
account . Clients = newClientAccounts
}
account . Clients = append ( account . Clients , client )
client . account = account
2017-05-28 20:43:09 +02:00
client . server . snomasks . Send ( sno . LocalAccounts , fmt . Sprintf ( ircfmt . Unescape ( "Client $c[grey][$r%s$c[grey]] logged into account $c[grey][$r%s$c[grey]]" ) , client . nickMaskString , account . Name ) )
2017-09-11 01:16:13 +02:00
//TODO(dan): This should output the AccountNotify message instead of the sasl accepted function below.
}
// LogoutOfAccount logs the client out of their current account.
func ( client * Client ) LogoutOfAccount ( ) {
account := client . account
if account == nil {
// already logged out
return
}
// logout of existing acct
var newClientAccounts [ ] * Client
for _ , c := range account . Clients {
if c != client {
newClientAccounts = append ( newClientAccounts , c )
}
}
account . Clients = newClientAccounts
client . account = nil
// dispatch account-notify
2017-09-29 04:07:52 +02:00
for friend := range client . Friends ( caps . AccountNotify ) {
2017-09-11 01:16:13 +02:00
friend . Send ( nil , client . nickMaskString , "ACCOUNT" , "*" )
}
2017-03-08 12:50:12 +01:00
}
2016-10-13 10:18:00 +02:00
// successfulSaslAuth means that a SASL auth attempt completed successfully, and is used to dispatch messages.
2017-04-16 03:31:33 +02:00
func ( client * Client ) successfulSaslAuth ( ) {
client . Send ( nil , client . server . name , RPL_LOGGEDIN , client . nick , client . nickMaskString , client . account . Name , fmt . Sprintf ( "You are now logged in as %s" , client . account . Name ) )
2018-01-22 12:26:01 +01:00
client . Send ( nil , client . server . name , RPL_SASLSUCCESS , client . nick , client . t ( "SASL authentication successful" ) )
2016-10-13 10:18:00 +02:00
// dispatch account-notify
2017-09-29 04:07:52 +02:00
for friend := range client . Friends ( caps . AccountNotify ) {
2017-04-16 03:31:33 +02:00
friend . Send ( nil , client . nickMaskString , "ACCOUNT" , client . account . Name )
2016-10-13 10:18:00 +02:00
}
}