diff --git a/Content.Server/Administration/Commands/BanCommand.cs b/Content.Server/Administration/Commands/BanCommand.cs index 62878d364d..2ad40c7c2d 100644 --- a/Content.Server/Administration/Commands/BanCommand.cs +++ b/Content.Server/Administration/Commands/BanCommand.cs @@ -48,7 +48,7 @@ namespace Content.Server.Administration.Commands expires = DateTimeOffset.Now + TimeSpan.FromMinutes(duration); } - await dbMan.AddServerBanAsync(new ServerBanDef(targetUid, null, DateTimeOffset.Now, expires, reason, player?.UserId)); + await dbMan.AddServerBanAsync(new ServerBanDef(null, targetUid, null, DateTimeOffset.Now, expires, reason, player?.UserId)); if (plyMgr.TryGetSessionById(targetUid, out var targetPlayer)) { diff --git a/Content.Server/Administration/Commands/BanListCommand.cs b/Content.Server/Administration/Commands/BanListCommand.cs new file mode 100644 index 0000000000..368df2224e --- /dev/null +++ b/Content.Server/Administration/Commands/BanListCommand.cs @@ -0,0 +1,89 @@ +using System; +using System.Text; +using Content.Server.Database; +using Content.Shared.Administration; +using Robust.Server.Player; +using Robust.Shared.Console; +using Robust.Shared.IoC; +using Robust.Shared.Network; + +namespace Content.Server.Administration.Commands +{ + [AdminCommand(AdminFlags.Ban)] + public class BanListCommand : IConsoleCommand + { + public string Command => "banlist"; + public string Description => "Lists somebody's bans"; + public string Help => "Usage: "; + + public async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 1) + { + shell.WriteLine($"Invalid amount of args. {Help}"); + return; + } + + var plyMgr = IoCManager.Resolve(); + var dbMan = IoCManager.Resolve(); + + var target = args[0]; + NetUserId targetUid; + + if (plyMgr.TryGetSessionByUsername(target, out var targetSession)) + { + targetUid = targetSession.UserId; + } + else if (Guid.TryParse(target, out var targetGuid)) + { + targetUid = new NetUserId(targetGuid); + } + else + { + shell.WriteLine("Unable to find user with that name."); + return; + } + + var bans = await dbMan.GetServerBansAsync(null, targetUid); + + if (bans.Count == 0) + { + shell.WriteLine("That user has no bans in their record."); + return; + } + + var bansString = new StringBuilder("Bans in record:\n"); + + foreach (var ban in bans) + { + bansString + .Append("Ban ID: ") + .Append(ban.Id) + .Append("\n") + .Append("Banned in ") + .Append(ban.BanTime); + + if (ban.ExpirationTime == null) + { + bansString.Append("."); + } + else + { + bansString + .Append(" until ") + .Append(ban.ExpirationTime.Value) + .Append("."); + } + + bansString.Append("\n"); + + bansString + .Append("Reason: ") + .Append(ban.Reason) + .Append("\n\n"); + } + + shell.WriteLine(bansString.ToString()); + } + } +} diff --git a/Content.Server/Database/ServerBanDef.cs b/Content.Server/Database/ServerBanDef.cs index 835e45699b..7a7cef948d 100644 --- a/Content.Server/Database/ServerBanDef.cs +++ b/Content.Server/Database/ServerBanDef.cs @@ -8,6 +8,7 @@ namespace Content.Server.Database { public sealed class ServerBanDef { + public int? Id { get; } public NetUserId? UserId { get; } public (IPAddress address, int cidrMask)? Address { get; } @@ -16,7 +17,7 @@ namespace Content.Server.Database public string Reason { get; } public NetUserId? BanningAdmin { get; } - public ServerBanDef(NetUserId? userId, (IPAddress, int)? address, DateTimeOffset banTime, DateTimeOffset? expirationTime, string reason, NetUserId? banningAdmin) + public ServerBanDef(int? id, NetUserId? userId, (IPAddress, int)? address, DateTimeOffset banTime, DateTimeOffset? expirationTime, string reason, NetUserId? banningAdmin) { if (userId == null && address == null) { @@ -30,6 +31,7 @@ namespace Content.Server.Database address = (addr.Item1.MapToIPv4(), addr.Item2 - 96); } + Id = id; UserId = userId; Address = address; BanTime = banTime; diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index 0d95804350..a2a6828fcb 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -230,6 +230,7 @@ namespace Content.Server.Database * BAN STUFF */ public abstract Task GetServerBanAsync(IPAddress? address, NetUserId? userId); + public abstract Task> GetServerBansAsync(IPAddress? address, NetUserId? userId); public abstract Task AddServerBanAsync(ServerBanDef serverBan); /* diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index edcc2a5cd6..aac5b8d6df 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Net; using System.Threading; @@ -41,6 +42,7 @@ namespace Content.Server.Database // Ban stuff Task GetServerBanAsync(IPAddress? address, NetUserId? userId); + Task> GetServerBansAsync(IPAddress? address, NetUserId? userId); Task AddServerBanAsync(ServerBanDef serverBan); // Player records @@ -142,6 +144,11 @@ namespace Content.Server.Database return _db.GetServerBanAsync(address, userId); } + public Task> GetServerBansAsync(IPAddress? address, NetUserId? userId) + { + return _db.GetServerBansAsync(address, userId); + } + public Task AddServerBanAsync(ServerBanDef serverBan) { return _db.AddServerBanAsync(serverBan); diff --git a/Content.Server/Database/ServerDbPostgres.cs b/Content.Server/Database/ServerDbPostgres.cs index c1a55e1e62..16deb6d66b 100644 --- a/Content.Server/Database/ServerDbPostgres.cs +++ b/Content.Server/Database/ServerDbPostgres.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Data; using System.Linq; using System.Net; @@ -74,6 +75,56 @@ namespace Content.Server.Database return ConvertBan(ban); } + public override async Task> GetServerBansAsync(IPAddress? address, NetUserId? userId) + { + if (address == null && userId == null) + { + throw new ArgumentException("Address and userId cannot both be null"); + } + + await using var db = await GetDbImpl(); + + var query = db.PgDbContext.Ban + .Include(p => p.Unban).AsQueryable(); + + if (userId is { } uid) + { + if (address == null) + { + // Only have a user ID. + query = query.Where(p => p.UserId == uid.UserId); + } + else + { + // Have both user ID and IP address. + query = query.Where(p => + (p.Address != null && EF.Functions.ContainsOrEqual(p.Address.Value, address)) + || p.UserId == uid.UserId); + } + } + else + { + // Only have a connecting address. + query = query.Where( + p => p.Address != null && EF.Functions.ContainsOrEqual(p.Address.Value, address)); + } + + var queryBans = await query.ToArrayAsync(); + var bans = new List(); + + foreach (var ban in queryBans) + { + var banDef = ConvertBan(ban); + + if (banDef != null) + { + bans.Add(banDef); + } + } + + return bans; + } + private static ServerBanDef? ConvertBan(PostgresServerBan? ban) { if (ban == null) @@ -94,6 +145,7 @@ namespace Content.Server.Database } return new ServerBanDef( + ban.Id, uid, ban.Address, ban.BanTime, diff --git a/Content.Server/Database/ServerDbSqlite.cs b/Content.Server/Database/ServerDbSqlite.cs index 56ed4f134d..e69484358e 100644 --- a/Content.Server/Database/ServerDbSqlite.cs +++ b/Content.Server/Database/ServerDbSqlite.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; @@ -72,6 +73,42 @@ namespace Content.Server.Database return null; } + public override async Task> GetServerBansAsync(IPAddress? address, NetUserId? userId) + { + await using var db = await GetDbImpl(); + + // SQLite can't do the net masking stuff we need to match IP address ranges. + // So just pull down the whole list into memory. + var queryBans = await db.SqliteDbContext.Ban + .Include(p => p.Unban) + .ToListAsync(); + + var bans = new List(); + + foreach (var ban in queryBans) + { + ServerBanDef? banDef = null; + + if (address != null && ban.Address != null && address.IsInSubnet(ban.Address)) + { + banDef = ConvertBan(ban); + } + else if (userId is { } id && ban.UserId == id.UserId) + { + banDef = ConvertBan(ban); + } + + if (banDef == null) + { + continue; + } + + bans.Add(banDef); + } + + return bans; + } + public override async Task AddServerBanAsync(ServerBanDef serverBan) { await using var db = await GetDbImpl(); @@ -182,6 +219,7 @@ namespace Content.Server.Database } return new ServerBanDef( + ban.Id, uid, addrTuple, ban.BanTime,