Add ban and unban commands
This commit is contained in:
parent
0a4b5e9b09
commit
253dd9b2ae
10 changed files with 271 additions and 12 deletions
|
@ -150,6 +150,14 @@ must write some data to the server at least once every 40 seconds.
|
|||
4. Warning message is broadcast to all users.
|
||||
5. Normal operation.
|
||||
|
||||
- `ban <username> [reason]`
|
||||
- Ban a user by IP addresss and account (if logged in).
|
||||
- This command is only available to server administrators and moderators.
|
||||
|
||||
- `unban <IP>/<username> <reason>`
|
||||
- Unban a user by IP address or account.
|
||||
- This command is only available to server administrators and moderators.
|
||||
|
||||
- `shutdown <minutes> <reason>`
|
||||
- Prevent the creation of new matches and periodically warn players about the server shutting down.
|
||||
- This command is only available to server administrators.
|
||||
|
|
|
@ -34,6 +34,8 @@ const (
|
|||
CommandMOTD = "motd" // Read (or write) the message of the day.
|
||||
CommandBroadcast = "broadcast" // Send a message to all players.
|
||||
CommandDefcon = "defcon" // Apply restrictions to guests to prevent abuse.
|
||||
CommandBan = "ban" // Ban an IP address or account.
|
||||
CommandUnban = "unban" // Unban an IP address or account.
|
||||
CommandShutdown = "shutdown" // Prevent the creation of new matches.
|
||||
)
|
||||
|
||||
|
@ -91,5 +93,7 @@ var HelpText = map[string]string{
|
|||
CommandMOTD: "[message] - View (or set) message of the day. Specifying a new message of the day is only available to server administrators.",
|
||||
CommandBroadcast: "<message> - Send a message to all players. This command is only available to server administrators.",
|
||||
CommandDefcon: "[level] - Apply restrictions to guests to prevent abuse. Levels:\n1. Disallow new accounts from being registered.\n2. Only registered users may create and join matches.\n3. Only registered users may chat and set custom match titles.\n4. Warning message is broadcast to all users.\n5. Normal operation.",
|
||||
CommandBan: "<username> - Ban a user by IP addresss and account (if logged in).",
|
||||
CommandUnban: "<IP>/<username> - Unban a user by IP address or account.",
|
||||
CommandShutdown: "<minutes> <reason> - Prevent the creation of new matches and periodically warn players about the server shutting down. This command is only available to server administrators.",
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ type socketClient struct {
|
|||
func newSocketClient(conn net.Conn, commands chan<- []byte, events chan []byte, verbose bool) *socketClient {
|
||||
return &socketClient{
|
||||
conn: conn,
|
||||
address: conn.RemoteAddr().String(),
|
||||
address: hashIP(conn.RemoteAddr().String()),
|
||||
events: events,
|
||||
commands: commands,
|
||||
verbose: verbose,
|
||||
|
|
|
@ -38,7 +38,7 @@ func newWebSocketClient(r *http.Request, w http.ResponseWriter, commands chan<-
|
|||
|
||||
return &webSocketClient{
|
||||
conn: conn,
|
||||
address: r.RemoteAddr,
|
||||
address: hashIP(r.RemoteAddr),
|
||||
events: events,
|
||||
commands: commands,
|
||||
verbose: verbose,
|
||||
|
|
|
@ -91,6 +91,14 @@ CREATE TABLE follow (
|
|||
FOREIGN KEY(target)
|
||||
REFERENCES account(id)
|
||||
);
|
||||
CREATE TABLE ban (
|
||||
ip text NOT NULL,
|
||||
account integer NOT NULL,
|
||||
created integer NOT NULL,
|
||||
staff integer NOT NULL,
|
||||
reason text NOT NULL,
|
||||
UNIQUE (ip, account)
|
||||
);
|
||||
`
|
||||
|
||||
var (
|
||||
|
@ -739,6 +747,108 @@ func replayByID(id int) ([]byte, error) {
|
|||
return replay, nil
|
||||
}
|
||||
|
||||
func addBan(ipHash string, account int, staff int, reason string) error {
|
||||
dbLock.Lock()
|
||||
defer dbLock.Unlock()
|
||||
|
||||
if db == nil || (ipHash == "" && account == 0) {
|
||||
return nil
|
||||
}
|
||||
|
||||
tx, err := begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Commit(context.Background())
|
||||
|
||||
timestamp := time.Now().Unix()
|
||||
|
||||
if ipHash != "" {
|
||||
var result int
|
||||
err = tx.QueryRow(context.Background(), "SELECT COUNT(*) FROM ban WHERE ip = $1", ipHash).Scan(&result)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
} else if result == 0 {
|
||||
_, err = tx.Exec(context.Background(), "INSERT INTO ban (ip, account, created, staff, reason) VALUES ($1, $2, $3, $4, $5)", ipHash, 0, timestamp, staff, reason)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if account != 0 {
|
||||
var result int
|
||||
err = tx.QueryRow(context.Background(), "SELECT COUNT(*) FROM ban WHERE account = $1", account).Scan(&result)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
} else if result == 0 {
|
||||
_, err = tx.Exec(context.Background(), "INSERT INTO ban (ip, account, created, staff, reason) VALUES ($1, $2, $3, $4, $5)", "", account, timestamp, staff, reason)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkBan(ipHash string, account int) (bool, string) {
|
||||
dbLock.Lock()
|
||||
defer dbLock.Unlock()
|
||||
|
||||
if db == nil || (ipHash == "" && account == 0) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
tx, err := begin()
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
defer tx.Commit(context.Background())
|
||||
|
||||
var row pgx.Row
|
||||
if account == 0 {
|
||||
row = tx.QueryRow(context.Background(), "SELECT reason FROM ban WHERE ip = $1 LIMIT 1", ipHash)
|
||||
} else {
|
||||
row = tx.QueryRow(context.Background(), "SELECT reason FROM ban WHERE ip = $1 OR account = $2 LIMIT 1", ipHash, account)
|
||||
}
|
||||
var reason string
|
||||
err = row.Scan(&reason)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
return true, reason
|
||||
}
|
||||
|
||||
func deleteBan(ipHash string, account int) error {
|
||||
dbLock.Lock()
|
||||
defer dbLock.Unlock()
|
||||
|
||||
if db == nil || (ipHash == "" && account == 0) {
|
||||
return nil
|
||||
}
|
||||
|
||||
tx, err := begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Commit(context.Background())
|
||||
|
||||
if ipHash != "" {
|
||||
_, err = tx.Exec(context.Background(), "DELETE FROM ban WHERE ip = $1", ipHash)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if account != 0 {
|
||||
_, err = tx.Exec(context.Background(), "DELETE FROM ban WHERE account = $1", account)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func matchHistory(username string) ([]*bgammon.HistoryMatch, error) {
|
||||
dbLock.Lock()
|
||||
defer dbLock.Unlock()
|
||||
|
|
|
@ -63,6 +63,18 @@ func replayByID(id int) ([]byte, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func addBan(ipHash string, account int, staff int, reason string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkBan(ipHash string, account int) (bool, string) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func deleteBan(ipHash string, account int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func recordGameResult(g *serverGame, winType int8, replay [][]byte) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -41,6 +41,8 @@ var (
|
|||
|
||||
var allowDebugCommands bool
|
||||
|
||||
var ipSalt string
|
||||
|
||||
//go:embed locales
|
||||
var assetFS embed.FS
|
||||
|
||||
|
@ -88,7 +90,6 @@ type server struct {
|
|||
mailServer string
|
||||
passwordSalt string
|
||||
resetSalt string
|
||||
ipSalt string
|
||||
|
||||
tz *time.Location
|
||||
languageTags []language.Tag
|
||||
|
@ -106,7 +107,7 @@ type server struct {
|
|||
shutdownReason string
|
||||
}
|
||||
|
||||
func NewServer(tz string, dataSource string, mailServer string, passwordSalt string, resetSalt string, ipSalt string, certDomain string, certFolder string, certEmail string, certAddress string, relayChat bool, verbose bool, allowDebug bool) *server {
|
||||
func NewServer(tz string, dataSource string, mailServer string, passwordSalt string, resetSalt string, ipAddressSalt string, certDomain string, certFolder string, certEmail string, certAddress string, relayChat bool, verbose bool, allowDebug bool) *server {
|
||||
const bufferSize = 10
|
||||
s := &server{
|
||||
newGameIDs: make(chan int),
|
||||
|
@ -117,7 +118,6 @@ func NewServer(tz string, dataSource string, mailServer string, passwordSalt str
|
|||
mailServer: mailServer,
|
||||
passwordSalt: passwordSalt,
|
||||
resetSalt: resetSalt,
|
||||
ipSalt: ipSalt,
|
||||
certDomain: certDomain,
|
||||
certFolder: certFolder,
|
||||
certEmail: certEmail,
|
||||
|
@ -142,6 +142,8 @@ func NewServer(tz string, dataSource string, mailServer string, passwordSalt str
|
|||
s.tz = time.UTC
|
||||
}
|
||||
|
||||
ipSalt = ipAddressSalt
|
||||
|
||||
if dataSource != "" {
|
||||
err := connectDB(dataSource)
|
||||
if err != nil {
|
||||
|
|
|
@ -81,8 +81,7 @@ func (s *server) handleFirstCommand(cmd serverCommand, keyword string, params []
|
|||
username: username,
|
||||
password: password,
|
||||
}
|
||||
ipHash := s.hashIP(cmd.client.Address())
|
||||
err := registerAccount(s.passwordSalt, a, ipHash)
|
||||
err := registerAccount(s.passwordSalt, a, cmd.client.Address())
|
||||
if err != nil {
|
||||
cmd.client.Terminate(fmt.Sprintf("Failed to register: %s", err))
|
||||
return
|
||||
|
@ -178,6 +177,16 @@ func (s *server) handleFirstCommand(cmd serverCommand, keyword string, params []
|
|||
cmd.client.name = username
|
||||
}
|
||||
|
||||
banned, banReason := checkBan(cmd.client.Address(), cmd.client.accountID)
|
||||
if banned {
|
||||
msg := "You are banned"
|
||||
if banReason != "" {
|
||||
msg += ": " + banReason
|
||||
}
|
||||
cmd.client.Terminate(msg)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.client.sendEvent(&bgammon.EventWelcome{
|
||||
PlayerName: string(cmd.client.name),
|
||||
Clients: len(s.clients),
|
||||
|
@ -340,7 +349,7 @@ COMMANDS:
|
|||
clientGame := s.gameByClient(cmd.client)
|
||||
if clientGame != nil && clientGame.client1 != cmd.client && clientGame.client2 != cmd.client {
|
||||
switch keyword {
|
||||
case bgammon.CommandHelp, "h", bgammon.CommandJSON, bgammon.CommandList, "ls", bgammon.CommandBoard, "b", bgammon.CommandLeave, "l", bgammon.CommandReplay, bgammon.CommandSet, bgammon.CommandPassword, bgammon.CommandFollow, bgammon.CommandUnfollow, bgammon.CommandPong, bgammon.CommandDisconnect, bgammon.CommandMOTD, bgammon.CommandBroadcast, bgammon.CommandShutdown:
|
||||
case bgammon.CommandHelp, "h", bgammon.CommandJSON, bgammon.CommandList, "ls", bgammon.CommandBoard, "b", bgammon.CommandLeave, "l", bgammon.CommandReplay, bgammon.CommandSet, bgammon.CommandPassword, bgammon.CommandFollow, bgammon.CommandUnfollow, bgammon.CommandPong, bgammon.CommandDisconnect, bgammon.CommandMOTD, bgammon.CommandBroadcast, bgammon.CommandDefcon, bgammon.CommandBan, bgammon.CommandUnban, bgammon.CommandShutdown:
|
||||
// These commands are allowed to be used by spectators.
|
||||
default:
|
||||
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Command ignored: You are spectating this match."))
|
||||
|
@ -1473,6 +1482,120 @@ COMMANDS:
|
|||
}
|
||||
s.clientsLock.Unlock()
|
||||
}
|
||||
case bgammon.CommandBan:
|
||||
if len(params) == 0 {
|
||||
cmd.client.sendNotice("Please specify an IP address or username.")
|
||||
continue
|
||||
} else if !cmd.client.Admin() && !cmd.client.Mod() {
|
||||
cmd.client.sendNotice("Access denied.")
|
||||
continue
|
||||
}
|
||||
|
||||
var reason string
|
||||
if len(params) > 1 {
|
||||
reason = string(bytes.Join(params[1:], []byte(" ")))
|
||||
}
|
||||
|
||||
msg := "You are banned"
|
||||
if reason != "" {
|
||||
msg += ": " + reason
|
||||
}
|
||||
|
||||
isIP := bytes.ContainsRune(params[0], '.') || bytes.ContainsRune(params[0], ':')
|
||||
if isIP {
|
||||
ip := hashIP(string(params[0]))
|
||||
err := addBan(ip, 0, cmd.client.accountID, reason)
|
||||
if err != nil {
|
||||
cmd.client.sendNotice("Failed to add ban: " + err.Error())
|
||||
}
|
||||
|
||||
s.clientsLock.Lock()
|
||||
for _, sc := range s.clients {
|
||||
if sc.Address() == ip {
|
||||
sc.Client.Terminate(msg)
|
||||
}
|
||||
}
|
||||
s.clientsLock.Unlock()
|
||||
|
||||
cmd.client.sendNotice(fmt.Sprintf("Banned %s.", params[0]))
|
||||
} else {
|
||||
account, err := accountByUsername(string(params[0]))
|
||||
if err != nil {
|
||||
cmd.client.sendNotice("Failed to add ban: " + err.Error())
|
||||
continue
|
||||
} else if account == nil || account.id == 0 {
|
||||
var found bool
|
||||
nameLower := bytes.ToLower(params[0])
|
||||
s.clientsLock.Lock()
|
||||
for _, sc := range s.clients {
|
||||
if bytes.Equal(bytes.ToLower(sc.name), nameLower) {
|
||||
found = true
|
||||
err := addBan(sc.Address(), 0, cmd.client.accountID, reason)
|
||||
if err != nil {
|
||||
cmd.client.sendNotice("Failed to add ban: " + err.Error())
|
||||
}
|
||||
sc.Client.Terminate(msg)
|
||||
break
|
||||
}
|
||||
}
|
||||
s.clientsLock.Unlock()
|
||||
|
||||
if !found {
|
||||
cmd.client.sendNotice("No account was found with that username.")
|
||||
} else {
|
||||
cmd.client.sendNotice(fmt.Sprintf("Banned %s.", params[0]))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
err = addBan("", account.id, cmd.client.accountID, reason)
|
||||
if err != nil {
|
||||
cmd.client.sendNotice("Failed to add ban: " + err.Error())
|
||||
}
|
||||
|
||||
s.clientsLock.Lock()
|
||||
for _, sc := range s.clients {
|
||||
if sc.accountID == account.id {
|
||||
sc.Client.Terminate(msg)
|
||||
break
|
||||
}
|
||||
}
|
||||
s.clientsLock.Unlock()
|
||||
|
||||
cmd.client.sendNotice(fmt.Sprintf("Banned %s.", params[0]))
|
||||
}
|
||||
case bgammon.CommandUnban:
|
||||
if len(params) == 0 {
|
||||
cmd.client.sendNotice("Please specify an IP address or username.")
|
||||
continue
|
||||
} else if !cmd.client.Admin() && !cmd.client.Mod() {
|
||||
cmd.client.sendNotice("Access denied.")
|
||||
continue
|
||||
}
|
||||
|
||||
isIP := bytes.ContainsRune(params[0], '.') || bytes.ContainsRune(params[0], ':')
|
||||
if isIP {
|
||||
err := deleteBan(hashIP(string(params[0])), 0)
|
||||
if err != nil {
|
||||
cmd.client.sendNotice("Failed to remove ban: " + err.Error())
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
account, err := accountByUsername(string(params[0]))
|
||||
if err != nil {
|
||||
cmd.client.sendNotice("Failed to remove ban: " + err.Error())
|
||||
continue
|
||||
} else if account == nil || account.id == 0 {
|
||||
cmd.client.sendNotice("No account was found with that username.")
|
||||
continue
|
||||
}
|
||||
err = deleteBan("", account.id)
|
||||
if err != nil {
|
||||
cmd.client.sendNotice("Failed to remove ban: " + err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
cmd.client.sendNotice(fmt.Sprintf("Unbanned %s.", params[0]))
|
||||
case bgammon.CommandShutdown:
|
||||
if !cmd.client.Admin() {
|
||||
cmd.client.sendNotice("Access denied.")
|
||||
|
|
|
@ -10,6 +10,6 @@ func (s *server) Listen(network string, address string) {
|
|||
log.Fatal("bgammon-server was built without the 'full' tag. Only local connections are possible.")
|
||||
}
|
||||
|
||||
func (s *server) hashIP(address string) string {
|
||||
func hashIP(address string) string {
|
||||
return address
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
)
|
||||
|
||||
func (s *server) Listen(network string, address string) {
|
||||
if s.passwordSalt == "" || s.resetSalt == "" || s.ipSalt == "" {
|
||||
if s.passwordSalt == "" || s.resetSalt == "" || ipSalt == "" {
|
||||
log.Fatal("error: password, reset and ip salts must be configured")
|
||||
}
|
||||
|
||||
|
@ -381,7 +381,7 @@ func (s *server) handlePrintWildBGStats(w http.ResponseWriter, r *http.Request)
|
|||
w.Write(s.cachedStats(4))
|
||||
}
|
||||
|
||||
func (s *server) hashIP(address string) string {
|
||||
func hashIP(address string) string {
|
||||
leftBracket, rightBracket := strings.IndexByte(address, '['), strings.IndexByte(address, ']')
|
||||
if leftBracket != -1 && rightBracket != -1 && rightBracket > leftBracket {
|
||||
address = address[1:rightBracket]
|
||||
|
@ -392,7 +392,7 @@ func (s *server) hashIP(address string) string {
|
|||
}
|
||||
}
|
||||
|
||||
buf := []byte(address + s.ipSalt)
|
||||
buf := []byte(address + ipSalt)
|
||||
h := make([]byte, 64)
|
||||
sha3.ShakeSum256(h, buf)
|
||||
return fmt.Sprintf("%x\n", h)
|
||||
|
|
Loading…
Reference in a new issue