300 lignes
6.8 KiB
Go
300 lignes
6.8 KiB
Go
package auth
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/patrickmn/go-cache"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
// BanType is used to categorize what a particular ban targets.
|
|
type BanType uint32
|
|
|
|
const (
|
|
// Client is a ban targetting the users SSH client.
|
|
Client BanType = iota
|
|
// Name is a ban targetting nicknames.
|
|
Name
|
|
// Key is a ban targetting SSH public keys.
|
|
Key
|
|
// IP is a ban targetting IP addresses.
|
|
IP
|
|
)
|
|
|
|
var banCache *cache.Cache
|
|
|
|
// BanList represents our list of banned items.
|
|
type BanList struct {
|
|
Items []string
|
|
}
|
|
|
|
func init() {
|
|
banCache = cache.New(2*time.Hour, 25*time.Minute)
|
|
}
|
|
|
|
func parseBanQuery(s string) (string, BanType, error) {
|
|
query := strings.TrimSpace(s)
|
|
request := strings.Split(query, "=")
|
|
bantype, err := StringToBanType(request[0])
|
|
if err != nil {
|
|
log.Debug().Err(err).Str("caller", s).
|
|
Str("request[0]", request[0]).
|
|
Str("request[1]", request[1]).Msg("unknown key")
|
|
return "", 100, errors.New("unknown key")
|
|
|
|
}
|
|
return request[1], bantype, nil
|
|
}
|
|
|
|
// BanQuery is used to ingest strings that contain various types of bans seperated by an equals sign.
|
|
// e.g: /ban name=douchebag
|
|
func (users *UserDB) BanQuery(query string) (err error) {
|
|
target, bantype, err := parseBanQuery(query)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return users.BanOther(target, bantype, true)
|
|
}
|
|
|
|
// UnBanQuery is used to ingest strings that contain various types of bans seperated by an equals sign.
|
|
// e.g: /unban name=knucklehead
|
|
func (users *UserDB) UnBanQuery(query string) (err error) {
|
|
target, bantype, err := parseBanQuery(query)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return users.BanOther(target, bantype, false)
|
|
}
|
|
|
|
// Banned returns the current banned items list.
|
|
func (users *UserDB) Banned() ([]string, []string, []string, []string) {
|
|
users.mu.RLock()
|
|
defer users.mu.RUnlock()
|
|
names := users.banned(Name)
|
|
ips := users.banned(IP)
|
|
fprints := users.banned(Key)
|
|
clients := users.banned(Client)
|
|
return names, ips, fprints, clients
|
|
}
|
|
|
|
func (users *UserDB) banned(bantype BanType) []string {
|
|
var banned = new(BanList)
|
|
var bans = uint32ToBytes(uint32(bantype))
|
|
|
|
banbytes, err := users.DB.Get(bans)
|
|
if err != nil {
|
|
log.Debug().Err(err).Uint16("type", uint16(bantype)).Msg("failed to get bans")
|
|
return []string{}
|
|
}
|
|
err = json.Unmarshal(banbytes, &banned)
|
|
if err != nil {
|
|
log.Error().Caller().Err(err).Uint16("type", uint16(bantype)).Msg("failed to unmarshal")
|
|
}
|
|
return banned.Items
|
|
}
|
|
|
|
// CheckBans checks our ban list for the instance of any of the given elements.
|
|
func (users *UserDB) CheckBans(user string, addr net.Addr, key ssh.PublicKey, s string) error {
|
|
log.Debug().Caller().Msg("checkbans")
|
|
|
|
var (
|
|
userRet = errors.New("user is banned")
|
|
addrRet = errors.New("ip is banned")
|
|
keyRet = errors.New("key is banned")
|
|
clientRet = errors.New("client is banned")
|
|
)
|
|
|
|
present := func(target string) (ok bool) {
|
|
_, ok = banCache.Get(target)
|
|
return
|
|
}
|
|
|
|
clientAddr, _, err := net.SplitHostPort(addr.String())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// check cache before we check the database
|
|
switch {
|
|
case present(user):
|
|
return userRet
|
|
case present(clientAddr):
|
|
return addrRet
|
|
case present(s):
|
|
return clientRet
|
|
default:
|
|
if key == nil {
|
|
break
|
|
}
|
|
if present(string(key.Marshal())) {
|
|
return keyRet
|
|
}
|
|
}
|
|
|
|
var toCheck = make(map[string]BanType)
|
|
toCheck[addr.String()] = IP
|
|
toCheck[s] = Client
|
|
toCheck[user] = Name
|
|
|
|
for item, bt := range toCheck {
|
|
if users.CheckBanOther(item, bt) {
|
|
if err := banCache.Add(addr.String(), true, cache.DefaultExpiration); err != nil {
|
|
log.Debug().Caller().Err(err).Msg("ban cache failure")
|
|
}
|
|
switch bt {
|
|
case IP:
|
|
return addrRet
|
|
case Client:
|
|
return clientRet
|
|
case Name:
|
|
return userRet
|
|
default:
|
|
log.Warn().Caller().Msg("unhandled ban")
|
|
}
|
|
}
|
|
}
|
|
|
|
if key == nil {
|
|
return nil
|
|
}
|
|
if users.CheckBanOther(string(key.Marshal()), Key) {
|
|
if err := banCache.Add(string(key.Marshal()), true, cache.DefaultExpiration); err != nil {
|
|
log.Debug().Caller().Err(err).Msg("ban cache failure")
|
|
}
|
|
return keyRet
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// BanOther creates a ban on various types of client attributes, or unbans them if banunban is false.
|
|
func (users *UserDB) BanOther(target string, bantype BanType, banUnban bool) error {
|
|
bans := uint32ToBytes(uint32(bantype))
|
|
bad := &BanList{Items: []string{}}
|
|
var contains = false
|
|
|
|
if users.DB.Has(bans) {
|
|
banJSON, err := users.DB.Get(bans)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := json.Unmarshal(banJSON, &bad); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, b := range bad.Items {
|
|
if b == target {
|
|
contains = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if contains && banUnban {
|
|
return errors.New("item is already banned: " + target)
|
|
}
|
|
|
|
if !contains && !banUnban {
|
|
return errors.New("ban does not exist")
|
|
}
|
|
|
|
var newbans []string
|
|
|
|
if banUnban {
|
|
bad.Items = append(bad.Items, target)
|
|
} else {
|
|
for _, item := range bad.Items {
|
|
if item != target {
|
|
newbans = append(bad.Items, item)
|
|
}
|
|
}
|
|
bad.Items = newbans
|
|
}
|
|
|
|
newitems, err := json.Marshal(bad)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return users.DB.Put(bans, newitems)
|
|
}
|
|
|
|
// StringToBanType takes a string and returns the corresponding BanType if found.
|
|
func StringToBanType(banstr string) (BanType, error) {
|
|
switch banstr {
|
|
case "name":
|
|
return Name, nil
|
|
case "ip":
|
|
return IP, nil
|
|
case "client":
|
|
return Client, nil
|
|
case "key":
|
|
return Key, nil
|
|
default:
|
|
return 100, errors.New("ban type not found")
|
|
}
|
|
}
|
|
|
|
// CheckBanOther checks a given target of bantype against our banned items.
|
|
func (users *UserDB) CheckBanOther(target string, bantype BanType) bool {
|
|
bans := uint32ToBytes(uint32(bantype))
|
|
|
|
if bantype == IP {
|
|
ip, _, err := net.SplitHostPort(target)
|
|
if err == nil {
|
|
target = ip
|
|
}
|
|
}
|
|
|
|
bad := &BanList{Items: []string{}}
|
|
|
|
if !users.DB.Has(bans) {
|
|
return false
|
|
}
|
|
|
|
badBytes, err := users.DB.Get(bans)
|
|
if err != nil {
|
|
log.Error().Err(err).Uint32("bantype", uint32(bantype)).Msg("failed to load bans!!")
|
|
// ban anyway for safety
|
|
return true
|
|
}
|
|
if err := json.Unmarshal(badBytes, bad); err != nil {
|
|
log.Error().Err(err).Uint32("bantype", uint32(bantype)).Msg("failed to load bans!!")
|
|
return true
|
|
}
|
|
|
|
for _, banned := range bad.Items {
|
|
if banned == target {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Ban bans a user from accessing their account.
|
|
func (users *UserDB) Ban(username string) error {
|
|
user, err := users.GetUser(username)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := users.SetPrivLevel(user, LevelBanned); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UnBan unbans a user from accessing their account and resets their privileges.
|
|
// If they were previously opped you will need to re-op them.
|
|
func (users *UserDB) UnBan(username string) error {
|
|
user, err := users.GetUser(username)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := users.SetPrivLevel(user, Default); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|