Skip to content

Commit

Permalink
Fix ban and mute commands.
Browse files Browse the repository at this point in the history
  • Loading branch information
leighmacdonald committed Oct 5, 2021
1 parent 0e9687c commit 75f96bc
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 188 deletions.
70 changes: 42 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
![Lines of Code](https://tokei.rs/b1/github/leighmacdonald/gbans)
[![Discord chat](https://img.shields.io/discord/704508824320475218)](https://discord.gg/YEWed3wY3F)

gbans is intended to be a more modern & secure replacement
for [sourcebans](https://github.com/GameConnect/sourcebansv1) / [sourcebans++](https://sbpp.dev).
gbans was initially intended to be a more modern & secure replacement
for [sourcebans](https://github.com/GameConnect/sourcebansv1) / [sourcebans++](https://sbpp.dev). It has since
had its scope expanded to include more optional support for general game server management tasks as well
as future plans for in depth plater stat tracking.


## Stability / Usage Notice
Expand All @@ -33,43 +35,55 @@ authentication token.
- Communication over HTTPS
- Discord bot integration for administration & announcements.
- Built using [Go](https://golang.org/) & [PostgreSQL](https://www.postgresql.org/). It has a built-in
webserver that is safe to directly expose to the internet. This means its not necessary to setup MySQL,
webserver that is safe to directly expose to the internet. This means it's not necessary to setup MySQL,
Nginx/Apache and PHP on your server.
- Non-legacy codebase that is (hopefully) not a nightmare to hack on.

## Features

- [ ] Import of existing sourcebans database
- [ ] Import/Export of gbans databases
- [ ] Backend linking of gbans services to enable use of other operators lists in real-time.
- [ ] General
- [x] Multi server support
- [x] Global bans
- [x] [Docker support](https://hub.docker.com/repository/docker/leighmacdonald/gbans)
- [ ] ACME ([Lets encrypt](https://letsencrypt.org/) / [Zero SSL](https://zerossl.com/)) protocol support for automatic SSL certificates
- [ ] Import/Export of gbans databases
- [ ] Backend linking of gbans services to enable use of other operators lists in real-time.
- [ ] Multi-tenant support
- [x] Game support
- [x] Team Fortress 2
- [ ] 3rd party ban lists
- [x] [tf2_bot_detector](https://github.com/PazerOP/tf2_bot_detector/blob/master/staging/cfg/playerlist.official.json)
- [ ] Known VPN Networks
- [ ] Known non-residential addresses
- [ ] Known proxies
- [x] Multi server support
- [x] Global bans
- [x] Subnet & IP bans (CIDR)
- [ ] Blocking lists & types
- [x] Valves source server banip config
- [ ] Existing sourcebans database
- [x] [CIDR/IP](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) bans
- [x] [tf2_bot_detector](https://github.com/PazerOP/tf2_bot_detector/blob/master/staging/cfg/playerlist.official.json)
- [ ] Known VPN Networks
- [ ] Known non-residential addresses
- [ ] Known proxies
- [ ] [FireHOL](https://github.com/firehol/blocklist-ipsets) databases
- [x] Database support
- [x] Postgresql w/PostGIS
- [x] [Docker support](https://hub.docker.com/repository/docker/leighmacdonald/gbans)
- [ ] ACME ([Lets encrypt](https://letsencrypt.org/) / [Zero SSL](https://zerossl.com/)) protocol support for automatic SSL certificates
- [ ] Remote Agent
- [x] Install & Update game installations
- [ ] Apply custom configs and plugins per server
- [ ] Relay game logs to central service
- [ ] SourceMod Plugin
- [x] Game server authentication
- [ ] `/gb_ban <player_id|steam_id> duration Reason` Ban a user
- [ ] `/gb_unban` Unban a previously banned user
- [ ] `/gb_kick` Kick a user
- [x] `/gb_mod or /mod <message>` Call for a mod on discord
- [x] Game server authentication
- [x] Restrict banned players from connecting
- [x] Restrict muted/gagged players on join
- [ ] `/gb_ban <player_id|steam_id> duration Reason` Ban a user
- [ ] `/gb_unban` Unban a previously banned user
- [ ] `/gb_kick` Kick a user
- [x] `/gb_mod or /mod <message>` Call for a mod on discord
- [ ] User Interfaces
- [x] Discord
- [ ] Web
- [ ] Game server logs
- [x] Remote relay agent `gbans relay -h`
- [x] Parsing
- [x] Indexing
- [ ] Querying
- [x] CLI
- [x] Discord
- [ ] Web
- [ ] Matrix
- [ ] Game server logs event storage and processing
- [x] Remote relay agent `gbans relay -h`
- [x] Parsing
- [x] Indexing
- [ ] Querying

## Docker

Expand Down
10 changes: 6 additions & 4 deletions internal/action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ type Executor interface {
FindPlayerByCIDR(ipNet *net.IPNet, pi *model.PlayerInfo) error
PersonBySID(sid steamid.SID64, ipAddr string, p *model.Person) error
GetOrCreateProfileBySteamID(ctx context.Context, sid steamid.SID64, ipAddr string, p *model.Person) error
Mute(args MuteRequest, pi *model.PlayerInfo) error
Ban(args BanRequest, b *model.Ban) error
BanNetwork(args BanNetRequest, net *model.BanNet) error
BanASN(args BanASNRequest, net *model.BanASN) error
Expand Down Expand Up @@ -116,7 +115,8 @@ type BanRequest struct {
Target
Author
Duration
Reason string
Reason string
BanType model.BanType
}

type MuteRequest BanRequest
Expand Down Expand Up @@ -188,8 +188,9 @@ func NewFind(o model.Origin, q string) FindRequest {
return FindRequest{BaseOrigin: BaseOrigin{o}, Query: q}
}

func NewMute(o model.Origin, target string, author string, reason string, duration string) MuteRequest {
return MuteRequest{
func NewMute(o model.Origin, target string, author string, reason string, duration string) BanRequest {
return BanRequest{
BanType: model.NoComm,
BaseOrigin: BaseOrigin{o},
Target: Target(target),
Author: Author(author),
Expand All @@ -209,6 +210,7 @@ func NewKick(o model.Origin, target string, author string, reason string) KickRe

func NewBan(o model.Origin, target string, author string, reason string, duration string) BanRequest {
return BanRequest{
BanType: model.Banned,
BaseOrigin: BaseOrigin{o},
Target: Target(target),
Author: Author(author),
Expand Down
122 changes: 46 additions & 76 deletions internal/app/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,61 +21,6 @@ import (
"time"
)

// Mute will apply a mute to the players steam id. Mutes are propagated to the servers immediately.
// If duration set to 0, the value of config.DefaultExpiration() will be used.
func (g gbans) Mute(args action.MuteRequest, pi *model.PlayerInfo) error {
target, err := args.Target.SID64()
if err != nil {
return errors.Errorf("Failed to get steam id from: %s", args.Target)
}
source, errSrc := args.Author.SID64()
if errSrc != nil {
return errors.Errorf("Failed to get steam id from: %s", args.Author)
}
duration, errDur := args.Duration.Value()
if errDur != nil {
return errDur
}
until := config.DefaultExpiration()
if duration > 0 {
until = config.Now().Add(duration)
}
b := model.NewBannedPerson()
if err2 := g.db.GetBanBySteamID(g.ctx, target, false, &b); err2 != nil && err2 != store.ErrNoResult {
log.Errorf("Error getting b from db: %v", err2)
return errors.New("Internal DB Error")
} else if err2 != nil {
b = model.BannedPerson{
Ban: model.NewBan(target, source, duration),
Person: model.NewPerson(target),
}
b.Ban.BanType = model.NoComm
}
if b.Ban.BanType == model.Banned {
return errors.New("Person is already banned")
}
b.Ban.BanType = model.NoComm
b.Ban.Reason = model.Custom
b.Ban.ReasonText = args.Reason
b.Ban.ValidUntil = until
if err3 := g.db.SaveBan(g.ctx, &b.Ban); err3 != nil {
log.Errorf("Failed to save b: %v", err3)
return err3
}

if errF := g.Find(target.String(), "", pi); errF != nil {
return nil
}
if pi.InGame {
log.Infof("Gagging in-game Player")
query.RCON(g.ctx, []model.Server{*pi.Server},
fmt.Sprintf(`sm_gag "#%s"`, string(steamid.SID64ToSID(target))),
fmt.Sprintf(`sm_mute "#%s"`, string(steamid.SID64ToSID(target))))
}
log.Infof("Gagged Player successfully")
return nil
}

// Unban will set the current ban to now, making it expired.
// Returns true, nil if the ban exists, and was successfully banned.
// Returns false, nil if the ban does not exist.
Expand Down Expand Up @@ -104,6 +49,7 @@ func (g gbans) Unban(args action.UnbanRequest) (bool, error) {
return true, nil
}

// UnbanASN will remove an existing ASN ban
func (g gbans) UnbanASN(ctx context.Context, args action.UnbanASNRequest) (bool, error) {
asNum, errConv := strconv.ParseInt(args.ASNum, 10, 64)
if errConv != nil {
Expand Down Expand Up @@ -150,12 +96,12 @@ func (g gbans) Ban(args action.BanRequest, b *model.Ban) error {
}
b.SteamID = target
b.AuthorID = source
b.BanType = model.Banned
b.BanType = args.BanType
b.Reason = model.Custom
b.ReasonText = args.Reason
b.Note = ""
b.ValidUntil = until
b.Source = model.System
b.Source = args.Origin
b.CreatedOn = config.Now()
b.UpdatedOn = config.Now()

Expand Down Expand Up @@ -200,29 +146,42 @@ func (g gbans) Ban(args action.BanRequest, b *model.Ban) error {
}
}
}()
go func() {
ipAddr := ""
// kick the user if they currently are playing on a server
pi := model.NewPlayerInfo()
_ = g.Find(target.String(), "", &pi)
if pi.Valid && pi.InGame {
ipAddr = pi.Player.IP.String()
if _, errR := query.ExecRCON(*pi.Server,
fmt.Sprintf("sm_kick #%d %s", pi.Player.UserID, args.Reason)); errR != nil {
log.Errorf("Faied to kick user afeter b: %v", errR)
}
p := model.NewPerson(pi.Player.SID)
if errG := g.db.GetOrCreatePersonBySteamID(g.ctx, pi.Player.SID, &p); errG != nil {
log.Errorf("Failed to fetch banned player: %v", errG)
ipAddr := ""
// kick the user if they currently are playing on a server
pi := model.NewPlayerInfo()
_ = g.Find(target.String(), "", &pi)
if pi.Valid && pi.InGame {
switch args.BanType {
case model.NoComm:
{
log.Infof("Gagging in-game Player")
query.RCON(g.ctx, []model.Server{*pi.Server},
fmt.Sprintf(`sm_gag "#%s"`, string(steamid.SID64ToSID(target))),
fmt.Sprintf(`sm_mute "#%s"`, string(steamid.SID64ToSID(target))))
}
p.IPAddr = net.ParseIP(ipAddr)
if errS := g.db.SavePerson(g.ctx, &p); errS != nil {
log.Errorf("Failed to update banned player up: %v", errS)
case model.Banned:
{
log.Infof("Banning and kicking in-game Player")
ipAddr = pi.Player.IP.String()
if _, errR := query.ExecRCON(*pi.Server,
fmt.Sprintf("sm_kick #%d %s", pi.Player.UserID, args.Reason)); errR != nil {
log.Errorf("Faied to kick user after ban: %v", errR)
}
}
}
}()
p := model.NewPerson(pi.Player.SID)
if errG := g.db.GetOrCreatePersonBySteamID(g.ctx, pi.Player.SID, &p); errG != nil {
log.Errorf("Failed to fetch banned player: %v", errG)
}
p.IPAddr = net.ParseIP(ipAddr)
if errS := g.db.SavePerson(g.ctx, &p); errS != nil {
log.Errorf("Failed to update banned player ip: %v", errS)
}
}
return nil
}

// BanASN will ban all network ranges associated with the requested ASN
func (g gbans) BanASN(args action.BanASNRequest, banASN *model.BanASN) error {
target, errTar := args.Target.SID64()
if errTar != nil {
Expand Down Expand Up @@ -317,7 +276,7 @@ func (g gbans) BanNetwork(args action.BanNetRequest, banNet *model.BanNet) error
return nil
}

// Kick will kick the steam id from all servers.
// Kick will kick the steam id from whatever server it is connected to.
func (g gbans) Kick(args action.KickRequest, pi *model.PlayerInfo) error {
target, errTar := args.Target.SID64()
if errTar != nil {
Expand Down Expand Up @@ -346,6 +305,9 @@ func (g gbans) Kick(args action.KickRequest, pi *model.PlayerInfo) error {
return nil
}

// SetSteam is used to associate a discord user with either steam id. This is used
// instead of requiring users to link their steam account to discord itself. It also
// means the bot does not require more priviledges intents.
func (g gbans) SetSteam(args action.SetSteamIDRequest) (bool, error) {
sid, err := steamid.ResolveSID64(g.ctx, string(args.Target))
if err != nil || !sid.Valid() {
Expand All @@ -365,6 +327,7 @@ func (g gbans) SetSteam(args action.SetSteamIDRequest) (bool, error) {
return true, nil
}

// Say is used to send a message to the server via sm_say
func (g gbans) Say(args action.SayRequest) error {
var server model.Server
if err := g.db.GetServerByName(g.ctx, args.Server, &server); err != nil {
Expand All @@ -382,6 +345,7 @@ func (g gbans) Say(args action.SayRequest) error {
return nil
}

// CSay is used to send a centered message to the server via sm_csay
func (g gbans) CSay(args action.CSayRequest) error {
var (
servers []model.Server
Expand All @@ -404,6 +368,7 @@ func (g gbans) CSay(args action.CSayRequest) error {
return nil
}

// PSay is used to send a private message to a player
func (g gbans) PSay(args action.PSayRequest) error {
var pi model.PlayerInfo
_ = g.Find(string(args.Target), "", &pi)
Expand All @@ -418,6 +383,7 @@ func (g gbans) PSay(args action.PSayRequest) error {
return nil
}

// FilterAdd creates a new chat filter using a regex pattern
func (g gbans) FilterAdd(args action.FilterAddRequest) (model.Filter, error) {
re, err := regexp.Compile(args.Filter)
if err != nil {
Expand All @@ -434,6 +400,7 @@ func (g gbans) FilterAdd(args action.FilterAddRequest) (model.Filter, error) {
return filter, nil
}

// FilterDel removed and existing chat filter
func (g gbans) FilterDel(ctx context.Context, args action.FilterDelRequest) (bool, error) {
var filter model.Filter
if err := g.db.GetFilterByID(ctx, args.FilterID, &filter); err != nil {
Expand All @@ -445,6 +412,7 @@ func (g gbans) FilterDel(ctx context.Context, args action.FilterDelRequest) (boo
return true, nil
}

// FilterCheck can be used to check if a phrase will match any filters
func (g gbans) FilterCheck(args action.FilterCheckRequest) []model.Filter {
if args.Message == "" {
return nil
Expand Down Expand Up @@ -482,6 +450,7 @@ func (g gbans) ContainsFilteredWord(body string) (bool, model.Filter) {
return false, model.Filter{}
}

// PersonBySID fetches the person from the database, updating the PlayerSummary if it out of date
func (g gbans) PersonBySID(sid steamid.SID64, ipAddr string, p *model.Person) error {
if err := g.db.GetPersonBySteamID(g.ctx, sid, p); err != nil && err != store.ErrNoResult {
return err
Expand All @@ -506,6 +475,7 @@ func (g gbans) PersonBySID(sid steamid.SID64, ipAddr string, p *model.Person) er
return nil
}

// ResolveSID is just a simple helper for calling steamid.ResolveSID64
func (g gbans) ResolveSID(sidStr string) (steamid.SID64, error) {
c, cancel := context.WithTimeout(g.ctx, time.Second*5)
defer cancel()
Expand Down
Loading

0 comments on commit 75f96bc

Please sign in to comment.