diff --git a/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml b/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml
index ee7ba4d34ff..c87979253bc 100644
--- a/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml
+++ b/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml
@@ -1,4 +1,4 @@
-
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml.cs b/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml.cs
index c3bcf3ffa09..a57a6b5e8c5 100644
--- a/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml.cs
+++ b/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml.cs
@@ -1,4 +1,5 @@
-using Content.Shared.Administration.Events;
+using Content.Corvax.Interfaces.Shared;
+using Content.Shared.Administration.Events;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
@@ -28,6 +29,16 @@ public PanicBunkerTab()
MinOverallMinutes.OnTextEntered += args => SendMinOverallMinutes(args.Text);
MinOverallMinutes.OnFocusExit += args => SendMinOverallMinutes(args.Text);
_minOverallMinutes = MinOverallMinutes.Text;
+
+ // Corvax-VPNGuard-Start
+ var haveSecrets = IoCManager.Instance!.TryResolveType(out _); // TODO: Probably need better way to detect Secrets module
+ if (haveSecrets)
+ {
+ VPNContainer.Visible = true;
+ DenyVPN.OnPressed += _ => SendDenyVpn(DenyVPN.Pressed);
+ }
+ // Corvax-VPNGuard-End
+
}
private void SendMinAccountAge(string text)
@@ -54,6 +65,13 @@ private void SendMinOverallMinutes(string text)
_console.ExecuteCommand($"panicbunker_min_overall_minutes {minutes}");
}
+ // Corvax-VPNGuard-Start
+ private void SendDenyVpn(bool deny)
+ {
+ _console.ExecuteCommand($"panicbunker_deny_vpn {deny}");
+ }
+ // Corvax-VPNGuard-End
+
public void UpdateStatus(PanicBunkerStatus status)
{
EnabledButton.Pressed = status.Enabled;
@@ -73,5 +91,6 @@ public void UpdateStatus(PanicBunkerStatus status)
MinOverallMinutes.Text = status.MinOverallMinutes.ToString();
_minOverallMinutes = MinOverallMinutes.Text;
+ DenyVPN.Pressed = status.DenyVpn; // Corvax-VPNGuard
}
}
diff --git a/Content.Server/Administration/Commands/PanicBunkerCommand.cs b/Content.Server/Administration/Commands/PanicBunkerCommand.cs
index 18aed7e3f08..ff3f2987f93 100644
--- a/Content.Server/Administration/Commands/PanicBunkerCommand.cs
+++ b/Content.Server/Administration/Commands/PanicBunkerCommand.cs
@@ -1,4 +1,4 @@
-using Content.Shared.Administration;
+using Content.Shared.Administration;
using Content.Shared.CCVar;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
@@ -144,7 +144,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args)
if (args.Length > 1)
{
- shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
+ shell.WriteError(Loc.GetString("shell-need-between-arguments", ("lower", 0), ("upper", 1)));
return;
}
@@ -176,7 +176,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args)
if (args.Length > 1)
{
- shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
+ shell.WriteError(Loc.GetString("shell-need-between-arguments", ("lower", 0), ("upper", 1)));
return;
}
diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs
index c51fa7f6146..efc5b750d68 100644
--- a/Content.Server/Administration/Systems/AdminSystem.cs
+++ b/Content.Server/Administration/Systems/AdminSystem.cs
@@ -14,6 +14,7 @@
using Content.Shared.Bank.Components;
using Content.Shared.Bank.Events;
using Content.Shared.CCVar;
+using Content.Shared.Corvax.CCCVars;
using Content.Shared.GameTicking;
using Content.Shared.Hands.Components;
using Content.Shared.IdentityManagement;
@@ -82,6 +83,7 @@ public override void Initialize()
Subs.CVar(_config, CCVars.PanicBunkerShowReason, OnPanicBunkerShowReasonChanged, true);
Subs.CVar(_config, CCVars.PanicBunkerMinAccountAge, OnPanicBunkerMinAccountAgeChanged, true);
Subs.CVar(_config, CCVars.PanicBunkerMinOverallMinutes, OnPanicBunkerMinOverallMinutesChanged, true);
+ Subs.CVar(_config, CCCVars.PanicBunkerDenyVPN, OnPanicBunkerDenyVpnChanged, true); // Corvax-VPNGuard
/*
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
@@ -347,6 +349,14 @@ private void OnBabyJailMaxOverallMinutesChanged(int minutes)
SendBabyJailStatusAll();
}
+ // Corvax-VPNGuard-Start
+ private void OnPanicBunkerDenyVpnChanged(bool deny)
+ {
+ PanicBunker.DenyVpn = deny;
+ SendPanicBunkerStatusAll();
+ }
+ // Corvax-VPNGuard-End
+
private void UpdatePanicBunker()
{
var admins = PanicBunker.CountDeadminnedAdmins
diff --git a/Content.Server/Connection/ConnectionManager.cs b/Content.Server/Connection/ConnectionManager.cs
index 3d2e05cbb75..57b8c65d523 100644
--- a/Content.Server/Connection/ConnectionManager.cs
+++ b/Content.Server/Connection/ConnectionManager.cs
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
+using System.Linq;
using System.Runtime.InteropServices;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
@@ -6,14 +7,19 @@
using Content.Server.Administration;
using Content.Server.Database;
using Content.Corvax.Interfaces.Server;
+using Content.Corvax.Interfaces.Shared;
+using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
using Content.Server.Preferences.Managers;
using Content.Shared.CCVar;
+using Content.Shared.Corvax.CCCVars;
using Content.Shared.GameTicking;
using Content.Shared.Players.PlayTimeTracking;
using Robust.Server.Player;
using Robust.Shared.Configuration;
+using Robust.Shared.Enums;
using Robust.Shared.Network;
+using Robust.Shared.Player;
using Robust.Shared.Timing;
@@ -52,11 +58,13 @@ public sealed class ConnectionManager : IConnectionManager
[Dependency] private readonly ServerDbEntryManager _serverDbEntry = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ILogManager _logManager = default!;
+ [Dependency] private readonly IChatManager _chatManager = default!;
- private IServerSponsorsManager? _sponsorsMgr; //
//frontier
[Dependency] private readonly MiniAuthManager _authManager = default!;
+ private IServerVPNGuardManager? _vpnGuardMgr; // Corvax-VPNGuard
+
private readonly Dictionary _temporaryBypasses = [];
private ISawmill _sawmill = default!;
@@ -64,9 +72,9 @@ public void Initialize()
{
_sawmill = _logManager.GetSawmill("connections");
- IoCManager.Instance!.TryResolveType(out _sponsorsMgr); // Corvax-Sponsors
_netMgr.Connecting += NetMgrOnConnecting;
_netMgr.AssignUserIdCallback = AssignUserIdCallback;
+ _plyMgr.PlayerStatusChanged += PlayerStatusChanged;
// Approval-based IP bans disabled because they don't play well with Happy Eyeballs.
// _netMgr.HandleApprovalCallback = HandleApproval;
}
@@ -135,6 +143,42 @@ 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)));
+ }
+
private async Task<(ConnectionDenyReason, string, List? bansHit)?> ShouldDeny(
NetConnectingArgs e)
{
@@ -197,8 +241,8 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
}
var minOverallMinutes = _cfg.GetCVar(CCVars.PanicBunkerMinOverallMinutes);
- var overallTime = ( await _db.GetPlayTimes(e.UserId)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall);
- var haveMinOverallTime = overallTime != null && overallTime.TimeSpent.TotalHours > minOverallMinutes;
+ var overallTime = (await _db.GetPlayTimes(e.UserId)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall);
+ var haveMinOverallTime = overallTime != null && overallTime.TimeSpent.TotalMinutes > minOverallMinutes;
// Use the custom reason if it exists & they don't have the minimum time
if (customReason != string.Empty && !haveMinOverallTime && !bypassAllowed)
@@ -210,10 +254,27 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
{
return (ConnectionDenyReason.Panic,
Loc.GetString("panic-bunker-account-denied-reason",
- ("reason", Loc.GetString("panic-bunker-account-reason-overall", ("hours", minOverallMinutes)))), null);
+ ("reason", Loc.GetString("panic-bunker-account-reason-overall", ("minutes", minOverallMinutes)))), null);
+ }
+
+ // Corvax-VPNGuard-Start
+ if (_vpnGuardMgr == null) // "lazyload" because of problems with dependency resolve order
+ IoCManager.Instance!.TryResolveType(out _vpnGuardMgr);
+
+ var denyVpn = false;
+ if (_cfg.GetCVar(CCCVars.PanicBunkerDenyVPN) && _vpnGuardMgr != null)
+ {
+ denyVpn = await _vpnGuardMgr.IsConnectionVpn(e.IP.Address);
+ if (denyVpn)
+ {
+ return (ConnectionDenyReason.Panic,
+ Loc.GetString("panic-bunker-account-denied-reason",
+ ("reason", Loc.GetString("panic-bunker-account-reason-vpn"))), null);
+ }
}
+ // Corvax-VPNGuard-End
- if (!validAccountAge || !haveMinOverallTime && !bypassAllowed)
+ if ((!validAccountAge || !haveMinOverallTime || denyVpn) && !bypassAllowed) // Corvax-VPNGuard
{
return (ConnectionDenyReason.Panic, Loc.GetString("panic-bunker-account-denied"), null);
}
@@ -231,8 +292,8 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
var adminBypass = _cfg.GetCVar(CCVars.AdminBypassMaxPlayers) && adminData != null;
// Corvax-Queue-Start
var isQueueEnabled = IoCManager.Instance!.TryResolveType(out var mgr) && mgr.IsEnabled;
- if (_plyMgr.PlayerCount >= _cfg.GetCVar(CCVars.SoftMaxPlayers) && !isPrivileged && !isQueueEnabled)
- // Corvax-Queue-End
+ if ((_plyMgr.PlayerCount >= _cfg.GetCVar(CCVars.SoftMaxPlayers) && !adminBypass) && !wasInGame && !isQueueEnabled)
+ // Corvax-Queue-End
{
return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null);
}
@@ -349,13 +410,11 @@ private bool HasTemporaryBypass(NetUserId user)
// Corvax-Queue-Start: Make these conditions in one place, for checks in the connection and in the queue
public async Task HavePrivilegedJoin(NetUserId userId)
{
- var adminBypass = await _dbManager.GetAdminDataForAsync(userId) != null;
- var havePriorityJoin = _sponsorsMgr != null && _sponsorsMgr.HavePriorityJoin(userId); // Corvax-Sponsors
+ var adminBypass = _cfg.GetCVar(CCVars.AdminBypassMaxPlayers) && await _dbManager.GetAdminDataForAsync(userId) != null;
var wasInGame = EntitySystem.TryGet(out var ticker) &&
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
status == PlayerGameStatus.JoinedGame;
return adminBypass ||
- havePriorityJoin || // Corvax-Sponsors
wasInGame;
}
// Corvax-Queue-End
diff --git a/Content.Server/Corvax/Administration/Commands/PanicBunkerCommand.cs b/Content.Server/Corvax/Administration/Commands/PanicBunkerCommand.cs
new file mode 100644
index 00000000000..1db98128a19
--- /dev/null
+++ b/Content.Server/Corvax/Administration/Commands/PanicBunkerCommand.cs
@@ -0,0 +1,33 @@
+using Content.Server.Administration;
+using Content.Shared.Administration;
+using Content.Shared.Corvax.CCCVars;
+using Robust.Shared.Configuration;
+using Robust.Shared.Console;
+
+namespace Content.Server.Corvax.Administration.Commands;
+
+[AdminCommand(AdminFlags.Server)]
+public sealed class PanicBunkerDenyVpnCommand : LocalizedCommands
+{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ public override string Command => "panicbunker_deny_vpn";
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ if (args.Length != 1)
+ {
+ shell.WriteError(Loc.GetString("shell-need-exactly-one-argument"));
+ return;
+ }
+
+ if (!bool.TryParse(args[0], out var deny))
+ {
+ shell.WriteError(Loc.GetString("shell-argument-must-be-boolean"));
+ return;
+ }
+
+ _cfg.SetCVar(CCCVars.PanicBunkerDenyVPN, deny);
+ shell.WriteLine(Loc.GetString(deny ? "panicbunker-command-deny-vpn-enabled" : "panicbunker-command-deny-vpn-disabled"));
+ }
+}
diff --git a/Content.Shared/Administration/Events/PanicBunkerChangedEvent.cs b/Content.Shared/Administration/Events/PanicBunkerChangedEvent.cs
index 786f645a7f0..b360d808a3d 100644
--- a/Content.Shared/Administration/Events/PanicBunkerChangedEvent.cs
+++ b/Content.Shared/Administration/Events/PanicBunkerChangedEvent.cs
@@ -1,4 +1,4 @@
-using Robust.Shared.Serialization;
+using Robust.Shared.Serialization;
namespace Content.Shared.Administration.Events;
@@ -12,6 +12,7 @@ public sealed class PanicBunkerStatus
public bool ShowReason;
public int MinAccountAgeMinutes;
public int MinOverallMinutes;
+ public bool DenyVpn; // Corvax-VPNGuard
}
[Serializable, NetSerializable]
diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs
index 9cacd1aeec5..8c81185faea 100644
--- a/Content.Shared/CCVar/CCVars.cs
+++ b/Content.Shared/CCVar/CCVars.cs
@@ -872,6 +872,15 @@ public static readonly CVarDef
public static readonly CVarDef ServerBanErasePlayer =
CVarDef.Create("admin.server_ban_erase_player", false, CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED);
+ ///
+ /// Minimum players sharing a connection required to create an alert. -1 to disable the alert.
+ ///
+ ///
+ /// If you set this to 0 or 1 then it will alert on every connection, so probably don't do that.
+ ///
+ public static readonly CVarDef AdminAlertMinPlayersSharingConnection =
+ CVarDef.Create("admin.alert.min_players_sharing_connection", -1, CVar.SERVERONLY);
+
///
/// Minimum explosion intensity to create an admin alert message. -1 to disable the alert.
///
diff --git a/Content.Shared/Corvax/CCCVars/CCCVars.cs b/Content.Shared/Corvax/CCCVars/CCCVars.cs
new file mode 100644
index 00000000000..898e59d35a9
--- /dev/null
+++ b/Content.Shared/Corvax/CCCVars/CCCVars.cs
@@ -0,0 +1,17 @@
+using Robust.Shared.Configuration;
+
+namespace Content.Shared.Corvax.CCCVars;
+
+///
+/// Corvax modules console variables
+///
+[CVarDefs]
+// ReSharper disable once InconsistentNaming
+public sealed class CCCVars
+{
+ ///
+ /// Deny any VPN connections.
+ ///
+ public static readonly CVarDef PanicBunkerDenyVPN =
+ CVarDef.Create("game.panic_bunker.deny_vpn", false, CVar.SERVERONLY);
+}
diff --git a/Resources/Locale/ru-RU/administration/ui/tabs/panicbunker-tab.ftl b/Resources/Locale/ru-RU/administration/ui/tabs/panicbunker-tab.ftl
index 3a90e14e4a7..cd2e6be0d94 100644
--- a/Resources/Locale/ru-RU/administration/ui/tabs/panicbunker-tab.ftl
+++ b/Resources/Locale/ru-RU/administration/ui/tabs/panicbunker-tab.ftl
@@ -11,7 +11,7 @@ admin-ui-panic-bunker-count-deadminned-admins-tooltip = Count deadminned admins
admin-ui-panic-bunker-show-reason = Show Reason
admin-ui-panic-bunker-show-reason-tooltip = Show the user why they were blocked from connecting by the panic bunker.
admin-ui-panic-bunker-min-account-age = Min. Account Age
-admin-ui-panic-bunker-min-overall-hours = Min. Overall Playtime
+admin-ui-panic-bunker-min-overall-minutes = Min. Overall Playtime
admin-ui-panic-bunker-is-enabled = The panic bunker is currently enabled.
admin-ui-panic-bunker-enabled-admin-alert = The panic bunker has been enabled.
admin-ui-panic-bunker-disabled-admin-alert = The panic bunker has been disabled.
diff --git a/Resources/Locale/ru-RU/corvax/administration/commands/panicbunker.ftl b/Resources/Locale/ru-RU/corvax/administration/commands/panicbunker.ftl
index bd25c9d5512..27f9c8da369 100644
--- a/Resources/Locale/ru-RU/corvax/administration/commands/panicbunker.ftl
+++ b/Resources/Locale/ru-RU/corvax/administration/commands/panicbunker.ftl
@@ -1,4 +1,2 @@
-cmd-panicbunker_deny_vpn-desc = Включает или отключает запрет доступа через VPN-соединения.
-cmd-panicbunker_deny_vpn-help = Использование: panicbunker_min_overall_hours
panicbunker-command-deny-vpn-enabled = Бункер теперь будет блокировать подключения через VPN.
panicbunker-command-deny-vpn-disabled = Бункер больше не будет блокировать подключения через VPN.
diff --git a/Resources/Locale/ru-RU/generic.ftl b/Resources/Locale/ru-RU/generic.ftl
index b74a568d6a7..3e9a08c08ce 100644
--- a/Resources/Locale/ru-RU/generic.ftl
+++ b/Resources/Locale/ru-RU/generic.ftl
@@ -8,5 +8,6 @@ generic-unknown-title = Неизвестно
generic-error = ошибка
generic-invalid = недействительно
generic-hours = часов
+generic-minutes = минут
generic-playtime-title = Игровое время
generic-confirm = Подтвердить