Skip to content

Commit

Permalink
ConnectionManager stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
sleepyyapril committed Dec 22, 2024
1 parent 6352f51 commit ecc7c6b
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 55 deletions.
1 change: 0 additions & 1 deletion Content.Server.Database/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ protected ServerDbContext(DbContextOptions options) : base(options)
public DbSet<AdminNote> AdminNotes { get; set; } = null!;
public DbSet<AdminWatchlist> AdminWatchlists { get; set; } = null!;
public DbSet<AdminMessage> AdminMessages { get; set; } = null!;
public DbSet<BanTemplate> BanTemplate { get; set; } = null!;
public DbSet<RoleWhitelist> RoleWhitelists { get; set; } = null!;
public DbSet<BanTemplate> BanTemplate { get; set; } = null!;

Expand Down
136 changes: 91 additions & 45 deletions Content.Server/Connection/ConnectionManager.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
using System.Collections.Immutable;
using System.Linq;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Content.Server.Connection.Whitelist;
using Content.Server.Connection.Whitelist.Conditions;
using System.Runtime.InteropServices;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Content.Server.Chat.Managers;
using Content.Server.Database;
using Content.Server.GameTicking;
using Content.Server.Preferences.Managers;
Expand All @@ -15,6 +11,7 @@
using Content.Shared.Players.PlayTimeTracking;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Player;
Expand All @@ -31,8 +28,6 @@ public interface IConnectionManager
void Initialize();
void PostInit();

Task<bool> HasPrivilegedJoin(NetUserId userId);

/// <summary>
/// Temporarily allow a user to bypass regular connection requirements.
/// </summary>
Expand All @@ -51,7 +46,6 @@ public interface IConnectionManager
/// </summary>
public sealed partial class ConnectionManager : IConnectionManager
{
[Dependency] private readonly IServerDbManager _dbManager = default!;
[Dependency] private readonly IPlayerManager _plyMgr = default!;
[Dependency] private readonly IServerNetManager _netMgr = default!;
[Dependency] private readonly IServerDbManager _db = default!;
Expand All @@ -61,10 +55,11 @@ public sealed partial class ConnectionManager : IConnectionManager
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!;

private ISawmill _sawmill = default!;
private readonly Dictionary<NetUserId, TimeSpan> _temporaryBypasses = [];
private ISawmill _sawmill = default!;


private List<NetUserId> _connectedWhitelistedPlayers = new(); // DeltaV - Soft whitelist improvements

Expand All @@ -76,6 +71,7 @@ public void Initialize()
_netMgr.Connected += OnConnected; // DeltaV - Soft whitelist improvements
_netMgr.Disconnect += OnDisconnected; // DeltaV - Soft whitelist improvements
_netMgr.AssignUserIdCallback = AssignUserIdCallback;
_plyMgr.PlayerStatusChanged += PlayerStatusChanged;
// Approval-based IP bans disabled because they don't play well with Happy Eyeballs.
// _netMgr.HandleApprovalCallback = HandleApproval;
}
Expand Down Expand Up @@ -130,7 +126,11 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
if (banHits is { Count: > 0 })
await _db.AddServerBanHitsAsync(id, banHits);

e.Deny(msg);
var properties = new Dictionary<string, object>();
if (reason == ConnectionDenyReason.Full)
properties["delay"] = _cfg.GetCVar(CCVars.GameServerFullReconnectDelay);

e.Deny(new NetDenyReason(msg, properties));
}
else
{
Expand All @@ -143,6 +143,46 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
}
}

private async void PlayerStatusChanged(object? sender, SessionStatusEventArgs args)
{
if (args.NewStatus == SessionStatus.Connected)
{
AdminAlertIfSharedConnection(args.Session);
}
}

private void AdminAlertIfSharedConnection(ICommonSession newSession)
{
var playerThreshold = _cfg.GetCVar(CCVars.AdminAlertMinPlayersSharingConnection);
if (playerThreshold < 0)
return;

var addr = newSession.Channel.RemoteEndPoint.Address;

var otherConnectionsFromAddress = _plyMgr.Sessions.Where(session =>
session.Status is SessionStatus.Connected or SessionStatus.InGame
&& session.Channel.RemoteEndPoint.Address.Equals(addr)
&& session.UserId != newSession.UserId)
.ToList();

var otherConnectionCount = otherConnectionsFromAddress.Count;
if (otherConnectionCount + 1 < playerThreshold) // Add one for the total, not just others, using the address
return;

var username = newSession.Name;
var otherUsernames = string.Join(", ",
otherConnectionsFromAddress.Select(session => session.Name));

_chatManager.SendAdminAlert(Loc.GetString("admin-alert-shared-connection",
("player", username),
("otherCount", otherConnectionCount),
("otherList", otherUsernames)));
}

/*
* TODO: Jesus H Christ what is this utter mess of a function
* TODO: Break this apart into is constituent steps.
*/
private async Task<(ConnectionDenyReason, string, List<ServerBanDef>? bansHit)?> ShouldDeny(
NetConnectingArgs e)
{
Expand Down Expand Up @@ -173,17 +213,17 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
return null;
}

var adminData = await _dbManager.GetAdminDataForAsync(e.UserId);
var adminData = await _db.GetAdminDataForAsync(e.UserId);

if (_cfg.GetCVar(CCVars.PanicBunkerEnabled) && adminData == null)
{
var showReason = _cfg.GetCVar(CCVars.PanicBunkerShowReason);
var customReason = _cfg.GetCVar(CCVars.PanicBunkerCustomReason);

var minMinutesAge = _cfg.GetCVar(CCVars.PanicBunkerMinAccountAge);
var record = await _dbManager.GetPlayerRecordByUserId(userId);
var minHoursAge = _cfg.GetCVar(CCVars.PanicBunkerMinAccountAge);
var record = await _db.GetPlayerRecordByUserId(userId);
var validAccountAge = record != null &&
record.FirstSeenTime.CompareTo(DateTimeOffset.Now - TimeSpan.FromMinutes(minMinutesAge)) <= 0;
record.FirstSeenTime.CompareTo(DateTimeOffset.UtcNow - TimeSpan.FromHours(minHoursAge)) <= 0;
var bypassAllowed = _cfg.GetCVar(CCVars.BypassBunkerWhitelist) && await _db.GetWhitelistStatusAsync(userId);

// Use the custom reason if it exists & they don't have the minimum account age
Expand All @@ -196,7 +236,7 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
{
return (ConnectionDenyReason.Panic,
Loc.GetString("panic-bunker-account-denied-reason",
("reason", Loc.GetString("panic-bunker-account-reason-account", ("minutes", minMinutesAge)))), null);
("reason", Loc.GetString("panic-bunker-account-reason-account", ("hours", minHoursAge)))), null);
}

var minOverallHours = _cfg.GetCVar(CCVars.PanicBunkerMinOverallHours);
Expand Down Expand Up @@ -235,40 +275,55 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
status == PlayerGameStatus.JoinedGame;
var adminBypass = _cfg.GetCVar(CCVars.AdminBypassMaxPlayers) && adminData != null;
if ((_plyMgr.PlayerCount >= _cfg.GetCVar(CCVars.SoftMaxPlayers) && !adminBypass) && !wasInGame)
{
return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null);
}

// Checks for whitelist IF it's enabled AND the user isn't an admin. Admins are always allowed.
if (false)
// DeltaV - Replace existing softwhitelist implementation
if (false)//if (_cfg.GetCVar(CCVars.WhitelistEnabled) && adminData is null)
{
var min = _cfg.GetCVar(CCVars.WhitelistMinPlayers);
var max = _cfg.GetCVar(CCVars.WhitelistMaxPlayers);
var playerCountValid = _plyMgr.PlayerCount >= min && _plyMgr.PlayerCount < max;
if (_whitelists is null)
{
_sawmill.Error("Whitelist enabled but no whitelists loaded.");
// Misconfigured, deny everyone.
return (ConnectionDenyReason.Whitelist, Loc.GetString("whitelist-misconfigured"), null);
}

if (playerCountValid && await _db.GetWhitelistStatusAsync(userId) == false
&& adminData is null)
foreach (var whitelist in _whitelists)
{
var msg = Loc.GetString(_cfg.GetCVar(CCVars.WhitelistReason));
// was the whitelist playercount changed?
if (min > 0 || max < int.MaxValue)
msg += "\n" + Loc.GetString("whitelist-playercount-invalid", ("min", min), ("max", max));
return (ConnectionDenyReason.Whitelist, msg, null);
if (!IsValid(whitelist, _plyMgr.PlayerCount))
{
// Not valid for current player count.
continue;
}

var whitelistStatus = await IsWhitelisted(whitelist, e.UserData, _sawmill);
if (!whitelistStatus.isWhitelisted)
{
// Not whitelisted.
return (ConnectionDenyReason.Whitelist, Loc.GetString("whitelist-fail-prefix", ("msg", whitelistStatus.denyMessage!)), null);
}

// Whitelisted, don't check any more.
break;
}
}

// DeltaV - Soft whitelist improvements
// TODO: replace this with a whitelist config prototype with a connected whitelisted players condition
if (_cfg.GetCVar(CCVars.WhitelistEnabled))
{
var connectedPlayers = _plyMgr.PlayerCount;
var connectedWhitelist = _connectedWhitelistedPlayers.Count;

var slots = _cfg.GetCVar(CCVars.WhitelistMinPlayers);
var slots = 25;

var noSlotsOpen = slots > 0 && slots < connectedPlayers - connectedWhitelist;

if (noSlotsOpen && await _db.GetWhitelistStatusAsync(userId) == false
&& adminData is null)
{
var msg = Loc.GetString(_cfg.GetCVar(CCVars.WhitelistReason));
var msg = Loc.GetString("whitelist-not-whitelisted-peri");

if (slots > 0)
msg += "\n" + Loc.GetString("whitelist-playercount-invalid", ("min", slots), ("max", _cfg.GetCVar(CCVars.SoftMaxPlayers)));
Expand All @@ -289,17 +344,17 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
// Initial cvar retrieval
var showReason = _cfg.GetCVar(CCVars.BabyJailShowReason);
var reason = _cfg.GetCVar(CCVars.BabyJailCustomReason);
var maxAccountAgeMinutes = _cfg.GetCVar(CCVars.BabyJailMaxAccountAge);
var maxPlaytimeMinutes = _cfg.GetCVar(CCVars.BabyJailMaxOverallMinutes);
var maxAccountAgeHours = _cfg.GetCVar(CCVars.BabyJailMaxAccountAge);
var maxPlaytimeHours = _cfg.GetCVar(CCVars.BabyJailMaxOverallHours);

// Wait some time to lookup data
var record = await _dbManager.GetPlayerRecordByUserId(userId);
var record = await _db.GetPlayerRecordByUserId(userId);

// No player record = new account or the DB is having a skill issue
if (record == null)
return (false, "");

var isAccountAgeInvalid = record.FirstSeenTime.CompareTo(DateTimeOffset.Now - TimeSpan.FromMinutes(maxAccountAgeMinutes)) <= 0;
var isAccountAgeInvalid = record.FirstSeenTime.CompareTo(DateTimeOffset.UtcNow - TimeSpan.FromHours(maxAccountAgeHours)) <= 0;

if (isAccountAgeInvalid)
{
Expand All @@ -314,13 +369,13 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
("reason",
Loc.GetString(
"baby-jail-account-reason-account",
("minutes", maxAccountAgeMinutes))));
("hours", maxAccountAgeHours))));

return (true, locAccountReason);
}

var overallTime = ( await _db.GetPlayTimes(e.UserId)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall);
var isTotalPlaytimeInvalid = overallTime != null && overallTime.TimeSpent.TotalMinutes >= maxPlaytimeMinutes;
var isTotalPlaytimeInvalid = overallTime != null && overallTime.TimeSpent.TotalHours >= maxAccountAgeHours;

if (isTotalPlaytimeInvalid)
{
Expand All @@ -335,7 +390,7 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
("reason",
Loc.GetString(
"baby-jail-account-reason-overall",
("minutes", maxPlaytimeMinutes))));
("hours", maxPlaytimeHours))));

return (true, locPlaytimeReason);
}
Expand Down Expand Up @@ -394,14 +449,5 @@ private async void OnDisconnected(object? sender, NetChannelArgs e)
_connectedWhitelistedPlayers.Remove(e.Channel.UserId);
}
}

public async Task<bool> HasPrivilegedJoin(NetUserId userId)
{
var isAdmin = await _dbManager.GetAdminDataForAsync(userId) != null;
var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
status == PlayerGameStatus.JoinedGame;
return isAdmin || wasInGame;
}
}
}
8 changes: 4 additions & 4 deletions Content.Shared/CCVar/CCVars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -355,17 +355,17 @@ public static readonly CVarDef<bool>
CVarDef.Create("game.baby_jail.show_reason", false, CVar.SERVERONLY);

/// <summary>
/// Maximum age of the account (from server's PoV, so from first-seen date) in minutes that can access baby
/// Maximum age of the account (from server's PoV, so from first-seen date) in hours that can access baby
/// jailed servers.
/// </summary>
public static readonly CVarDef<int> BabyJailMaxAccountAge =
CVarDef.Create("game.baby_jail.max_account_age", 1440, CVar.SERVERONLY);
CVarDef.Create("game.baby_jail.max_account_age", 24, CVar.SERVERONLY);

/// <summary>
/// Maximum overall played time allowed to access baby jailed servers.
/// </summary>
public static readonly CVarDef<int> BabyJailMaxOverallMinutes =
CVarDef.Create("game.baby_jail.max_overall_minutes", 120, CVar.SERVERONLY);
public static readonly CVarDef<int> BabyJailMaxOverallHours =
CVarDef.Create("game.baby_jail.max_overall_hours", 2, CVar.SERVERONLY);

/// <summary>
/// A custom message that will be used for connections denied due to the baby jail.
Expand Down
10 changes: 5 additions & 5 deletions Resources/Locale/en-US/connection-messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ ban-banned-3 = Attempts to circumvent this ban such as creating a new account wi
soft-player-cap-full = The server is full!
panic-bunker-account-denied = This server is in panic bunker mode, often enabled as a precaution against raids. New connections by accounts not meeting certain requirements are temporarily not accepted. Try again later
panic-bunker-account-denied-reason = This server is in panic bunker mode, often enabled as a precaution against raids. New connections by accounts not meeting certain requirements are temporarily not accepted. Try again later. Reason: "{$reason}"
panic-bunker-account-reason-account = Your Space Station 14 account is too new. It must be older than {$minutes} minutes
panic-bunker-account-reason-overall = Your overall playtime on the server must be greater than {$minutes} $minutes
panic-bunker-account-reason-account = Your Space Station 14 account is too new. It must be older than {$hours} hours
panic-bunker-account-reason-overall = Your overall playtime on the server must be greater than {$hours} hours.
whitelist-playtime = You do not have enough playtime to join this server. You need at least {$minutes} minutes of playtime to join this server.
whitelist-playtime = You do not have enough playtime to join this server. You need at least {$hours} minutes of playtime to join this server.
whitelist-player-count = This server is currently not accepting players. Please try again later.
whitelist-notes = You currently have too many admin notes to join this server. You can check your notes by typing /adminremarks in chat.
whitelist-manual = You are not whitelisted on this server.
Expand All @@ -53,5 +53,5 @@ cmd-blacklistremove-arg-player = [player]
baby-jail-account-denied = This server is a newbie server, intended for new players and those who want to help them. New connections by accounts that are too old or are not on a whitelist are not accepted. Check out some other servers and see everything Space Station 14 has to offer. Have fun!
baby-jail-account-denied-reason = This server is a newbie server, intended for new players and those who want to help them. New connections by accounts that are too old or are not on a whitelist are not accepted. Check out some other servers and see everything Space Station 14 has to offer. Have fun! Reason: "{$reason}"
baby-jail-account-reason-account = Your Space Station 14 account is too old. It must be younger than {$minutes} minutes
baby-jail-account-reason-overall = Your overall playtime on the server must be younger than {$minutes} $minutes
baby-jail-account-reason-account = Your Space Station 14 account is too old. It must be younger than {$hours} hours.
baby-jail-account-reason-overall = Your overall playtime on the server must be younger than {$hours} hours.

0 comments on commit ecc7c6b

Please sign in to comment.