diff --git a/game/addons/sourcemod/configs/sourcebans/sourcebans.cfg b/game/addons/sourcemod/configs/sourcebans/sourcebans.cfg index cf6f086a9..fb7684a67 100644 --- a/game/addons/sourcemod/configs/sourcebans/sourcebans.cfg +++ b/game/addons/sourcemod/configs/sourcebans/sourcebans.cfg @@ -49,6 +49,15 @@ // This is the ID of this server (Check in the admin panel -> servers to find the ID of this server) "ServerID" "-1" + + // Public/NAT IPv4 address of this server as registered in the web panel (e.g. "1.2.3.4"). + // Set this when the server runs behind NAT (e.g. in a container) and the + // auto-detected "hostip" is a private LAN address that will never match + // the IP stored in the web panel. Leave empty to use the auto-detected IP. + // An invalid value is ignored (logged as an error) and the auto-detected IP is used. + // Note: the port is always taken from the "hostport" cvar, so if your NAT also + // remaps the port, the web panel entry must use the server's internal port. + "ServerIP" "" } /* diff --git a/game/addons/sourcemod/scripting/sbpp_comms.sp b/game/addons/sourcemod/scripting/sbpp_comms.sp index eb19fae3c..5e38084c3 100644 --- a/game/addons/sourcemod/scripting/sbpp_comms.sp +++ b/game/addons/sourcemod/scripting/sbpp_comms.sp @@ -96,6 +96,7 @@ Database SQLiteDB; char ServerIp[24] + , ServerIpOverride[24] /* Optional public/NAT IP from sourcebans.cfg */ , ServerPort[7] , DatabasePrefix[10] = "sb" #if defined LOG_QUERIES @@ -2029,6 +2030,26 @@ public SMCResult ReadConfig_KeyValue(SMCParser smc, const char[] key, const char RetryTime = 60.0; } } + else if (strcmp("ServerIP", key, false) == 0) + { + char ipValue[64]; + strcopy(ipValue, sizeof(ipValue), value); + TrimString(ipValue); + + ServerIpOverride[0] = '\0'; + + if (ipValue[0] != '\0') + { + if (IsValidServerIp(ipValue)) + { + strcopy(ServerIpOverride, sizeof(ServerIpOverride), ipValue); + } + else + { + LogError("Invalid \"ServerIP\" value \"%s\" in sourcebans.cfg, falling back to the auto-detected server IP", value); + } + } + } else if (strcmp("ServerID", key, false) == 0) { serverID = StringToInt(value); @@ -2794,8 +2815,54 @@ stock void InsertTempBlock(int length, int type, const char[] name, const char[] SQLiteDB.Query(Query_ErrorCheck, sQuery); } +/** + * Validates that the given string is a dotted-quad IPv4 address. + */ +stock bool IsValidServerIp(const char[] ip) +{ + int octets = 0, value = 0, digits = 0; + + for (int i = 0; ; i++) + { + if (ip[i] == '.' || ip[i] == '\0') + { + if (digits == 0 || value > 255) + return false; + + octets++; + + if (ip[i] == '\0') + break; + + if (octets == 4) + return false; + + value = 0; + digits = 0; + } + else if (ip[i] >= '0' && ip[i] <= '9') + { + if (++digits > 3) + return false; + + value = value * 10 + (ip[i] - '0'); + } + else + { + return false; + } + } + + return (octets == 4); +} + stock void ServerInfo() { + if (CvarHostIp == null || CvarPort == null) + { + return; + } + int pieces[4]; int longip = CvarHostIp.IntValue; pieces[0] = (longip >> 24) & 0x000000FF; @@ -2804,6 +2871,12 @@ stock void ServerInfo() pieces[3] = longip & 0x000000FF; FormatEx(ServerIp, sizeof(ServerIp), "%d.%d.%d.%d", pieces[0], pieces[1], pieces[2], pieces[3]); CvarPort.GetString(ServerPort, sizeof(ServerPort)); + + // Prefer the public/NAT IP configured in sourcebans.cfg over the auto-detected hostip. + if (ServerIpOverride[0] != '\0') + { + strcopy(ServerIp, sizeof(ServerIp), ServerIpOverride); + } } stock void ReadConfig() @@ -2816,6 +2889,10 @@ stock void ReadConfig() } char ConfigFile1[PLATFORM_MAX_PATH], ConfigFile2[PLATFORM_MAX_PATH]; + + // Reset so a removed/emptied "ServerIP" key does not keep a stale override on reload. + ServerIpOverride[0] = '\0'; + BuildPath(Path_SM, ConfigFile1, sizeof(ConfigFile1), "configs/sourcebans/sourcebans.cfg"); BuildPath(Path_SM, ConfigFile2, sizeof(ConfigFile2), "configs/sourcebans/sourcecomms.cfg"); @@ -2823,6 +2900,8 @@ stock void ReadConfig() { PrintToServer("%sLoading configs/sourcebans/sourcebans.cfg config file", PREFIX); InternalReadConfig(ConfigFile1); + // Re-apply the (possibly changed) "ServerIP" override on top of the auto-detected hostip. + ServerInfo(); } else { diff --git a/game/addons/sourcemod/scripting/sbpp_main.sp b/game/addons/sourcemod/scripting/sbpp_main.sp index 75746aa6c..a41b41833 100644 --- a/game/addons/sourcemod/scripting/sbpp_main.sp +++ b/game/addons/sourcemod/scripting/sbpp_main.sp @@ -75,6 +75,7 @@ Database SQLiteDB; char ServerIp[24] + , ServerIpOverride[24] /* Optional public/NAT IP from sourcebans.cfg */ , ServerPort[7] , DatabasePrefix[10] = "sb" , WebsiteAddress[128] @@ -97,6 +98,7 @@ bool , requireSiteLogin = false /* Require a lastvisited from SB site */ , backupConfig = true , enableAdmins = true + , ConfigLoaded = false /* sourcebans.cfg has been parsed at least once */ , PlayerStatus[MAXPLAYERS + 1]; /* Player ban check status */ int @@ -1083,6 +1085,13 @@ public void GotDatabase(Database db, const char[] error, any data) DB.Query(ErrorCheckCallback, query); } + // The database can be ready before the first ReadConfig() (OnMapStart), and every + // query below depends on config values (DatabasePrefix, AutoAdd, ServerIP override). + if (!ConfigLoaded) + { + ResetSettings(); + } + InsertServerInfo(); //CreateTimer(900.0, PruneBans); @@ -2257,6 +2266,26 @@ public SMCResult ReadConfig_KeyValue(SMCParser smc, const char[] key, const char CommandDisable |= DISABLE_ADDBAN; } } + else if (strcmp("ServerIP", key, false) == 0) + { + char ipValue[64]; + strcopy(ipValue, sizeof(ipValue), value); + TrimString(ipValue); + + ServerIpOverride[0] = '\0'; + + if (ipValue[0] != '\0') + { + if (IsValidServerIp(ipValue)) + { + strcopy(ServerIpOverride, sizeof(ServerIpOverride), ipValue); + } + else + { + LogError("Invalid \"ServerIP\" value \"%s\" in sourcebans.cfg, falling back to the auto-detected server IP", value); + } + } + } else if (strcmp("AutoAddServer", key, false) == 0) { int sAutoAdd = StringToInt(value); @@ -2647,13 +2676,57 @@ stock void CheckLoadAdmins(AdminCachePart part) } } -stock void InsertServerInfo() +/** + * Validates that the given string is a dotted-quad IPv4 address. + */ +stock bool IsValidServerIp(const char[] ip) { - if (DB == INVALID_HANDLE) { + int octets = 0, value = 0, digits = 0; + + for (int i = 0; ; i++) + { + if (ip[i] == '.' || ip[i] == '\0') + { + if (digits == 0 || value > 255) + return false; + + octets++; + + if (ip[i] == '\0') + break; + + if (octets == 4) + return false; + + value = 0; + digits = 0; + } + else if (ip[i] >= '0' && ip[i] <= '9') + { + if (++digits > 3) + return false; + + value = value * 10 + (ip[i] - '0'); + } + else + { + return false; + } + } + + return (octets == 4); +} + +/** + * Refreshes ServerIp/ServerPort from the game cvars, applying the optional + * "ServerIP" override from sourcebans.cfg (public/NAT address). + */ +stock void UpdateServerIp() +{ + if (CvarHostIp == null || CvarPort == null) { return; } - char query[100]; int pieces[4]; int longip = CvarHostIp.IntValue; @@ -2665,6 +2738,23 @@ stock void InsertServerInfo() FormatEx(ServerIp, sizeof(ServerIp), "%d.%d.%d.%d", pieces[0], pieces[1], pieces[2], pieces[3]); CvarPort.GetString(ServerPort, sizeof(ServerPort)); + // Prefer the public/NAT IP configured in sourcebans.cfg over the auto-detected hostip. + if (ServerIpOverride[0] != '\0') + { + strcopy(ServerIp, sizeof(ServerIp), ServerIpOverride); + } +} + +stock void InsertServerInfo() +{ + if (DB == INVALID_HANDLE) { + return; + } + + char query[100]; + + UpdateServerIp(); + if (AutoAdd != AUTO_ADD_SERVER_DISABLED) { FormatEx(query, sizeof(query), "SELECT sid FROM %s_servers WHERE ip = '%s' AND port = '%s'", DatabasePrefix, ServerIp, ServerPort); DB.Query(ServerInfoCallback, query); @@ -2733,9 +2823,14 @@ stock void ReadConfig() char ConfigFile[PLATFORM_MAX_PATH]; BuildPath(Path_SM, ConfigFile, sizeof(ConfigFile), "configs/sourcebans/sourcebans.cfg"); + // Reset so a removed/emptied "ServerIP" key does not keep a stale override on reload. + ServerIpOverride[0] = '\0'; + if (FileExists(ConfigFile)) { + ConfigLoaded = true; InternalReadConfig(ConfigFile); + UpdateServerIp(); PrintToServer("%sLoading configs/sourcebans.cfg config file", Prefix); } else { char Error[PLATFORM_MAX_PATH + 64];