| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- using System.Collections.Immutable;
- using System.Linq;
- using System.Net;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using Content.Server.Chat.Managers;
- using Content.Server.Database;
- using Content.Server.GameTicking;
- using Content.Shared.CCVar;
- using Content.Shared.Database;
- using Content.Shared.Players;
- using Content.Shared.Players.PlayTimeTracking;
- using Content.Shared.Roles;
- using Robust.Server.Player;
- using Robust.Shared.Asynchronous;
- using Robust.Shared.Collections;
- using Robust.Shared.Configuration;
- using Robust.Shared.Enums;
- using Robust.Shared.Network;
- using Robust.Shared.Player;
- using Robust.Shared.Prototypes;
- using Robust.Shared.Timing;
- namespace Content.Server.Administration.Managers;
- public sealed partial class BanManager : IBanManager, IPostInjectInit
- {
- [Dependency] private readonly IServerDbManager _db = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly IEntitySystemManager _systems = default!;
- [Dependency] private readonly IConfigurationManager _cfg = default!;
- [Dependency] private readonly ILocalizationManager _localizationManager = default!;
- [Dependency] private readonly ServerDbEntryManager _entryManager = default!;
- [Dependency] private readonly IChatManager _chat = default!;
- [Dependency] private readonly INetManager _netManager = default!;
- [Dependency] private readonly ILogManager _logManager = default!;
- [Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly ITaskManager _taskManager = default!;
- [Dependency] private readonly UserDbDataManager _userDbData = default!;
- private ISawmill _sawmill = default!;
- public const string SawmillId = "admin.bans";
- public const string JobPrefix = "Job:";
- private readonly Dictionary<ICommonSession, List<ServerRoleBanDef>> _cachedRoleBans = new();
- // Cached ban exemption flags are used to handle
- private readonly Dictionary<ICommonSession, ServerBanExemptFlags> _cachedBanExemptions = new();
- public void Initialize()
- {
- _netManager.RegisterNetMessage<MsgRoleBans>();
- _db.SubscribeToJsonNotification<BanNotificationData>(
- _taskManager,
- _sawmill,
- BanNotificationChannel,
- ProcessBanNotification,
- OnDatabaseNotificationEarlyFilter);
- _userDbData.AddOnLoadPlayer(CachePlayerData);
- _userDbData.AddOnPlayerDisconnect(ClearPlayerData);
- }
- private async Task CachePlayerData(ICommonSession player, CancellationToken cancel)
- {
- var flags = await _db.GetBanExemption(player.UserId, cancel);
- var netChannel = player.Channel;
- ImmutableArray<byte>? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId;
- var modernHwids = netChannel.UserData.ModernHWIds;
- var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, modernHwids, false);
- var userRoleBans = new List<ServerRoleBanDef>();
- foreach (var ban in roleBans)
- {
- userRoleBans.Add(ban);
- }
- cancel.ThrowIfCancellationRequested();
- _cachedBanExemptions[player] = flags;
- _cachedRoleBans[player] = userRoleBans;
- SendRoleBans(player);
- }
- private void ClearPlayerData(ICommonSession player)
- {
- _cachedBanExemptions.Remove(player);
- }
- private async Task<bool> AddRoleBan(ServerRoleBanDef banDef)
- {
- banDef = await _db.AddServerRoleBanAsync(banDef);
- if (banDef.UserId != null
- && _playerManager.TryGetSessionById(banDef.UserId, out var player)
- && _cachedRoleBans.TryGetValue(player, out var cachedBans))
- {
- cachedBans.Add(banDef);
- }
- return true;
- }
- public HashSet<string>? GetRoleBans(NetUserId playerUserId)
- {
- if (!_playerManager.TryGetSessionById(playerUserId, out var session))
- return null;
- return _cachedRoleBans.TryGetValue(session, out var roleBans)
- ? roleBans.Select(banDef => banDef.Role).ToHashSet()
- : null;
- }
- public void Restart()
- {
- // Clear out players that have disconnected.
- var toRemove = new ValueList<ICommonSession>();
- foreach (var player in _cachedRoleBans.Keys)
- {
- if (player.Status == SessionStatus.Disconnected)
- toRemove.Add(player);
- }
- foreach (var player in toRemove)
- {
- _cachedRoleBans.Remove(player);
- }
- // Check for expired bans
- foreach (var roleBans in _cachedRoleBans.Values)
- {
- roleBans.RemoveAll(ban => DateTimeOffset.Now > ban.ExpirationTime);
- }
- }
- #region Server Bans
- public async void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason)
- {
- DateTimeOffset? expires = null;
- if (minutes > 0)
- {
- expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value);
- }
- _systems.TryGetEntitySystem<GameTicker>(out var ticker);
- int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
- var playtime = target == null ? TimeSpan.Zero : (await _db.GetPlayTimes(target.Value)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
- var banDef = new ServerBanDef(
- null,
- target,
- addressRange,
- hwid,
- DateTimeOffset.Now,
- expires,
- roundId,
- playtime,
- reason,
- severity,
- banningAdmin,
- null);
- await _db.AddServerBanAsync(banDef);
- if (_cfg.GetCVar(CCVars.ServerBanResetLastReadRules) && target != null)
- await _db.SetLastReadRules(target.Value, null); // Reset their last read rules. They probably need a refresher!
- var adminName = banningAdmin == null
- ? Loc.GetString("system-user")
- : (await _db.GetPlayerRecordByUserId(banningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user");
- var targetName = target is null ? "null" : $"{targetUsername} ({target})";
- var addressRangeString = addressRange != null
- ? $"{addressRange.Value.Item1}/{addressRange.Value.Item2}"
- : "null";
- var hwidString = hwid?.ToString() ?? "null";
- var expiresString = expires == null ? Loc.GetString("server-ban-string-never") : $"{expires}";
- var key = _cfg.GetCVar(CCVars.AdminShowPIIOnBan) ? "server-ban-string" : "server-ban-string-no-pii";
- var logMessage = Loc.GetString(
- key,
- ("admin", adminName),
- ("severity", severity),
- ("expires", expiresString),
- ("name", targetName),
- ("ip", addressRangeString),
- ("hwid", hwidString),
- ("reason", reason));
- _sawmill.Info(logMessage);
- _chat.SendAdminAlert(logMessage);
- KickMatchingConnectedPlayers(banDef, "newly placed ban");
- }
- private void KickMatchingConnectedPlayers(ServerBanDef def, string source)
- {
- foreach (var player in _playerManager.Sessions)
- {
- if (BanMatchesPlayer(player, def))
- {
- KickForBanDef(player, def);
- _sawmill.Info($"Kicked player {player.Name} ({player.UserId}) through {source}");
- }
- }
- }
- private bool BanMatchesPlayer(ICommonSession player, ServerBanDef ban)
- {
- var playerInfo = new BanMatcher.PlayerInfo
- {
- UserId = player.UserId,
- Address = player.Channel.RemoteEndPoint.Address,
- HWId = player.Channel.UserData.HWId,
- ModernHWIds = player.Channel.UserData.ModernHWIds,
- // It's possible for the player to not have cached data loading yet due to coincidental timing.
- // If this is the case, we assume they have all flags to avoid false-positives.
- ExemptFlags = _cachedBanExemptions.GetValueOrDefault(player, ServerBanExemptFlags.All),
- IsNewPlayer = false,
- };
- return BanMatcher.BanMatches(ban, playerInfo);
- }
- private void KickForBanDef(ICommonSession player, ServerBanDef def)
- {
- var message = def.FormatBanMessage(_cfg, _localizationManager);
- player.Channel.Disconnect(message);
- }
- #endregion
- #region Job Bans
- // If you are trying to remove timeOfBan, please don't. It's there because the note system groups role bans by time, reason and banning admin.
- // Removing it will clutter the note list. Please also make sure that department bans are applied to roles with the same DateTimeOffset.
- public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan)
- {
- if (!_prototypeManager.TryIndex(role, out JobPrototype? _))
- {
- throw new ArgumentException($"Invalid role '{role}'", nameof(role));
- }
- role = string.Concat(JobPrefix, role);
- DateTimeOffset? expires = null;
- if (minutes > 0)
- {
- expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value);
- }
- _systems.TryGetEntitySystem(out GameTicker? ticker);
- int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
- var playtime = target == null ? TimeSpan.Zero : (await _db.GetPlayTimes(target.Value)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
- var banDef = new ServerRoleBanDef(
- null,
- target,
- addressRange,
- hwid,
- timeOfBan,
- expires,
- roundId,
- playtime,
- reason,
- severity,
- banningAdmin,
- null,
- role);
- if (!await AddRoleBan(banDef))
- {
- _chat.SendAdminAlert(Loc.GetString("cmd-roleban-existing", ("target", targetUsername ?? "null"), ("role", role)));
- return;
- }
- var length = expires == null ? Loc.GetString("cmd-roleban-inf") : Loc.GetString("cmd-roleban-until", ("expires", expires));
- _chat.SendAdminAlert(Loc.GetString("cmd-roleban-success", ("target", targetUsername ?? "null"), ("role", role), ("reason", reason), ("length", length)));
- if (target != null && _playerManager.TryGetSessionById(target.Value, out var session))
- {
- SendRoleBans(session);
- }
- }
- public async Task<string> PardonRoleBan(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime)
- {
- var ban = await _db.GetServerRoleBanAsync(banId);
- if (ban == null)
- {
- return $"No ban found with id {banId}";
- }
- if (ban.Unban != null)
- {
- var response = new StringBuilder("This ban has already been pardoned");
- if (ban.Unban.UnbanningAdmin != null)
- {
- response.Append($" by {ban.Unban.UnbanningAdmin.Value}");
- }
- response.Append($" in {ban.Unban.UnbanTime}.");
- return response.ToString();
- }
- await _db.AddServerRoleUnbanAsync(new ServerRoleUnbanDef(banId, unbanningAdmin, DateTimeOffset.Now));
- if (ban.UserId is { } player
- && _playerManager.TryGetSessionById(player, out var session)
- && _cachedRoleBans.TryGetValue(session, out var roleBans))
- {
- roleBans.RemoveAll(roleBan => roleBan.Id == ban.Id);
- SendRoleBans(session);
- }
- return $"Pardoned ban with id {banId}";
- }
- public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId)
- {
- if (!_playerManager.TryGetSessionById(playerUserId, out var session))
- return null;
- if (!_cachedRoleBans.TryGetValue(session, out var roleBans))
- return null;
- return roleBans
- .Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal))
- .Select(ban => new ProtoId<JobPrototype>(ban.Role[JobPrefix.Length..]))
- .ToHashSet();
- }
- #endregion
- public void SendRoleBans(ICommonSession pSession)
- {
- var roleBans = _cachedRoleBans.GetValueOrDefault(pSession) ?? new List<ServerRoleBanDef>();
- var bans = new MsgRoleBans()
- {
- Bans = roleBans.Select(o => o.Role).ToList()
- };
- _sawmill.Debug($"Sent rolebans to {pSession.Name}");
- _netManager.ServerSendMessage(bans, pSession.Channel);
- }
- public void PostInject()
- {
- _sawmill = _logManager.GetSawmill(SawmillId);
- }
- }
|