29 Commits

Author SHA1 Message Date
0f2ec7de29 docs: audit de reprise (revue 5 casquettes) avant reprise du projet
Ajoute AUDIT.md : revue archi / securite / backend .NET / DCS-Lua / DevOps-DBA
du fork de reprise. Constats majeurs P0 : injection SQL via donnees joueur
(DatabaseController.cs) et listener TCP non authentifie expose (TCPController.cs).
Feuille de route priorisee P0->P3 + plan de reprise inclus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 14:31:50 +02:00
Szymon Porwolik
28fba57604 Update README to reflect project archival status
Added a notice indicating that the repository is no longer actively maintained and archived.
2026-03-29 01:23:14 +01:00
ags
da26e9b37f Merge pull request #76 from VEAF/nil-exception-protection
Nil exception protection
2024-08-22 23:42:54 +01:00
ags
9ddf139767 Merge pull request #75 from VEAF/make-record-chat-message-optional
Make recording of chat messages optional
2024-08-22 23:42:42 +01:00
ags
af310ba403 Merge pull request #77 from VEAF/stop-after-sqlerror-1366
Stop after sqlerror 1366
2024-08-22 23:42:27 +01:00
ags
661d05526d Merge pull request #84 from szporwolik/dependabot/nuget/02_Windows_App/Perun_v1/BouncyCastle-1.8.9
Bump BouncyCastle from 1.8.3.1 to 1.8.9 in /02_Windows_App/Perun_v1
2024-08-22 23:42:00 +01:00
ags
2ce3836130 Update README.md 2024-07-13 16:10:04 +01:00
dependabot[bot]
7fd4a1e95c Bump BouncyCastle from 1.8.3.1 to 1.8.9 in /02_Windows_App/Perun_v1
Bumps BouncyCastle from 1.8.3.1 to 1.8.9.

---
updated-dependencies:
- dependency-name: BouncyCastle
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-30 19:15:03 +00:00
ags
b26b5b115b Merge pull request #82 from szporwolik/dependabot/nuget/02_Windows_App/Perun_v1/Newtonsoft.Json-13.0.1
Bump Newtonsoft.Json from 12.0.2 to 13.0.1 in /02_Windows_App/Perun_v1
2022-06-29 01:02:10 +02:00
ags
0fd2633d5b Merge pull request #81 from szporwolik/dependabot/nuget/02_Windows_App/Perun_v1/00_Config/Newtonsoft.Json-13.0.1
Bump Newtonsoft.Json from 12.0.1 to 13.0.1 in /02_Windows_App/Perun_v1/00_Config
2022-06-29 01:01:49 +02:00
dependabot[bot]
d239f63a56 Bump Newtonsoft.Json from 12.0.2 to 13.0.1 in /02_Windows_App/Perun_v1
Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 12.0.2 to 13.0.1.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/12.0.2...13.0.1)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-22 21:54:15 +00:00
dependabot[bot]
25dcbb818b Bump Newtonsoft.Json in /02_Windows_App/Perun_v1/00_Config
Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 12.0.1 to 13.0.1.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/12.0.1...13.0.1)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-22 20:42:38 +00:00
ags
7074503039 Merge pull request #79 from szporwolik/dependabot/nuget/02_Windows_App/Perun_v1/Google.Protobuf-3.15.0
Bump Google.Protobuf from 3.6.1 to 3.15.0 in /02_Windows_App/Perun_v1
2022-06-02 01:12:59 +01:00
ags
d0620a3dca Merge pull request #78 from szporwolik/dependabot/nuget/02_Windows_App/Perun_v1/00_Config/Google.Protobuf-3.15.0
Bump Google.Protobuf from 3.5.1 to 3.15.0 in /02_Windows_App/Perun_v1/00_Config
2022-06-02 01:12:18 +01:00
dependabot[bot]
8ba30d31cd Bump Google.Protobuf from 3.6.1 to 3.15.0 in /02_Windows_App/Perun_v1
Bumps [Google.Protobuf](https://github.com/protocolbuffers/protobuf) from 3.6.1 to 3.15.0.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/master/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.6.1...v3.15.0)

---
updated-dependencies:
- dependency-name: Google.Protobuf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-01 23:51:05 +00:00
dependabot[bot]
009117d3c5 Bump Google.Protobuf in /02_Windows_App/Perun_v1/00_Config
Bumps [Google.Protobuf](https://github.com/protocolbuffers/protobuf) from 3.5.1 to 3.15.0.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/master/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.5.1...v3.15.0)

---
updated-dependencies:
- dependency-name: Google.Protobuf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-01 23:50:43 +00:00
ags
483b15276e Merge pull request #80 from szporwolik/dependabot/nuget/02_Windows_App/Perun_v1/SSH.NET-2020.0.2
Bump SSH.NET from 2016.1.0 to 2020.0.2 in /02_Windows_App/Perun_v1
2022-06-02 00:50:03 +01:00
dependabot[bot]
07f8b14611 Bump SSH.NET from 2016.1.0 to 2020.0.2 in /02_Windows_App/Perun_v1
Bumps [SSH.NET](https://github.com/sshnet/SSH.NET) from 2016.1.0 to 2020.0.2.
- [Release notes](https://github.com/sshnet/SSH.NET/releases)
- [Commits](https://github.com/sshnet/SSH.NET/compare/2016.1.0...2020.0.2)

---
updated-dependencies:
- dependency-name: SSH.NET
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-01 23:25:17 +00:00
Andrzej Grzesik
6648d5cad3 Update app.config
Bumped default port to what's used in the docs
2021-12-26 00:51:38 +00:00
ags
4bbb2ad98a WinApp: use utf8mb4 for sql connection 2021-12-20 17:23:19 +00:00
ags
98fc7a799f default tables to utf8mb4 2021-12-20 17:11:05 +00:00
David Pierron
a88109b85c If a packet generates a SQL error 1366 (invalid encoding), it'll be discarded from the queue and never sent to the database again. 2021-06-28 13:14:55 +02:00
David Pierron
c9f55992ad Protected calls to Perun.LogEvent in case of a nil value in the message elements 2021-06-28 13:06:22 +02:00
David Pierron
3cbbcd023f Made chat message recording optional (PerunConfig.RecordChatMessages in the configuration file) 2021-06-28 13:05:02 +02:00
David Pierron
4bea05e7a2 Merge pull request #1 from szporwolik/master
Merged origin/master to our master
2021-04-24 09:47:34 +02:00
Szymon Porwolik
f46ffd9385 Merge pull request #65 from szporwolik/dev
Dev
2021-03-01 16:13:01 +01:00
Szymon Porwolik
7697e30fb3 Update README.md 2021-03-01 16:12:36 +01:00
Szymon Porwolik
3ec0e7bd15 Update README.md
Fixed dll path
2021-03-01 16:12:19 +01:00
VladMordock
727b026207 Config for v0.12.1 2021-02-28 13:15:26 +01:00
12 changed files with 312 additions and 47 deletions

View File

@@ -18,6 +18,7 @@ PerunConfig.ConnectionError_L1 = "[Perun] ERROR: Connection broken - contact ser
-- Misc -- Misc
PerunConfig.BroadcastPerunErrors = 1 -- (int) [0 (default),1] Value greater than 0 will broadcast chat message about missing connection to Perun PerunConfig.BroadcastPerunErrors = 1 -- (int) [0 (default),1] Value greater than 0 will broadcast chat message about missing connection to Perun
PerunConfig.RecordChatMessages = 1
-- Debug -- Debug
PerunConfig.DebugMode = 1 -- (int) [0 (default),1,2] Value greater than 0 will display Perun information in DCS log file, values: 1 - minimal verbose, 2 - all log information will be logged PerunConfig.DebugMode = 1 -- (int) [0 (default),1,2] Value greater than 0 will display Perun information in DCS log file, values: 1 - minimal verbose, 2 - all log information will be logged

View File

@@ -24,9 +24,10 @@ Perun.MOTD_L1 = PerunConfig.MOTD_L1
Perun.MOTD_L2 = PerunConfig.MOTD_L2 Perun.MOTD_L2 = PerunConfig.MOTD_L2
Perun.ConnectionError = PerunConfig.ConnectionError_L1 Perun.ConnectionError = PerunConfig.ConnectionError_L1
Perun.BroadcastPerunErrors = PerunConfig.BroadcastPerunErrors Perun.BroadcastPerunErrors = PerunConfig.BroadcastPerunErrors
Perun.RecordChatMessages = PerunConfig.RecordChatMessages
-- Variable init -- Variable init
Perun.Version = "v0.12.0" Perun.Version = "v0.12.1"
Perun.StatusData = {} Perun.StatusData = {}
Perun.SlotsData = {} Perun.SlotsData = {}
@@ -47,6 +48,14 @@ Perun.lastFrameTime = 0;
Perun.ReconnectTimeout = 30; Perun.ReconnectTimeout = 30;
Perun.RefreshKeepAlive = 3 Perun.RefreshKeepAlive = 3
Perun.PTS = function(data) -- ProtectedToString
if data == nil then
return "[nil]"
else
return tostring(data)
end
end
-- ################################ Helper function definitions ################################ -- ################################ Helper function definitions ################################
Perun.GetCategory = function(id) Perun.GetCategory = function(id)
-- Helper function returns object category basing on https://pastebin.com/GUAXrd2U -- Helper function returns object category basing on https://pastebin.com/GUAXrd2U
@@ -81,7 +90,7 @@ Perun.SideID2Name = function(id)
[2] = 'BLUE', [2] = 'BLUE',
[3] = 'NEUTRAL', -- TBD check once this is released in DCS [3] = 'NEUTRAL', -- TBD check once this is released in DCS
} }
if id > 0 and id <= 3 then if id and id > 0 and id <= 3 then
return _sides[id] return _sides[id]
else else
return "?" return "?"
@@ -621,7 +630,7 @@ end
Perun.onSimulationStart = function() Perun.onSimulationStart = function()
-- Simulation was started -- Simulation was started
Perun.MissionHash=Perun.GenerateMissionHash() Perun.MissionHash=Perun.GenerateMissionHash()
Perun.LogEvent("SimStart","Mission " .. Perun.MissionHash .. " started",nil,nil); Perun.LogEvent("SimStart","Mission " .. Perun.PTS(Perun.MissionHash) .. " started",nil,nil);
Perun.StatData = {} Perun.StatData = {}
Perun.StatDataLastType = {} Perun.StatDataLastType = {}
Perun.PlayersTableCache = {} Perun.PlayersTableCache = {}
@@ -632,7 +641,7 @@ end
Perun.onSimulationStop = function() Perun.onSimulationStop = function()
-- Simulation was stopped -- Simulation was stopped
Perun.LogEvent("SimStop","Mission " .. Perun.MissionHash .. " finished",nil,nil); Perun.LogEvent("SimStop","Mission " .. Perun.PTS(Perun.MissionHash) .. " finished",nil,nil);
Perun.LogAllStats() Perun.LogAllStats()
Perun.MissionHash=Perun.GenerateMissionHash(); Perun.MissionHash=Perun.GenerateMissionHash();
Perun.StatData = {} Perun.StatData = {}
@@ -644,14 +653,14 @@ end
Perun.onPlayerDisconnect = function(id, err_code) Perun.onPlayerDisconnect = function(id, err_code)
-- Player disconnected -- Player disconnected
Perun.LogEvent("disconnect", "Player " .. id .. " disconnected.",nil,nil); Perun.LogEvent("disconnect", "Player " .. Perun.PTS(id) .. " disconnected.",nil,nil);
return return
end end
Perun.onPlayerStop = function (id) Perun.onPlayerStop = function (id)
-- Player left the simulation (happens right before a disconnect, if player exited by desire) -- Player left the simulation (happens right before a disconnect, if player exited by desire)
Perun.LogEvent("quit", "Player " .. id .. " quit the server.",nil,nil); Perun.LogEvent("quit", "Player " .. Perun.PTS(id) .. " quit the server.",nil,nil);
return return
end end
@@ -706,7 +715,7 @@ end
Perun.onPlayerTrySendChat = function (playerID, msg, all) Perun.onPlayerTrySendChat = function (playerID, msg, all)
-- Somebody tries to send chat message -- Somebody tries to send chat message
if msg~=Perun.MOTD_L1 and msg~=Perun.MOTD_L2 and msg~=Perun.ConnectionError then if Perun.RecordChatMessages and msg~=Perun.MOTD_L1 and msg~=Perun.MOTD_L2 and msg~=Perun.ConnectionError then
Perun.LogChat(playerID,msg,all) Perun.LogChat(playerID,msg,all)
end end
@@ -724,11 +733,11 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7)
arg2 = "Cannon" arg2 = "Cannon"
end end
Perun.LogEvent(eventName,Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .." killed friendly " .. Perun.GetMulticrewCrewNames(arg3) .. " using " .. arg2,nil,nil); Perun.LogEvent(Perun.PTS(eventName),Perun.SideID2Name( Perun.PTS(net.get_player_info(arg1, "side"))) .. " player(s) " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg1)) .." killed friendly " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg3)) .. " using " .. Perun.PTS(arg2),nil,nil);
elseif eventName == "mission_end" then elseif eventName == "mission_end" then
--"mission_end", winner, msg --"mission_end", winner, msg
Perun.LogEvent(eventName,"Mission finished, winner " .. arg1 .. " message: " .. arg2,nil,nil); Perun.LogEvent(Perun.PTS(eventName),"Mission finished, winner " .. Perun.PTS(arg1) .. " message: " .. Perun.PTS(arg2),nil,nil);
elseif eventName == "kill" then elseif eventName == "kill" then
--"kill", killerPlayerID, killerUnitType, killerSide, victimPlayerID, victimUnitType, victimSide, weaponName --"kill", killerPlayerID, killerUnitType, killerSide, victimPlayerID, victimUnitType, victimSide, weaponName
@@ -791,12 +800,12 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7)
victim_vehicle = "?" victim_vehicle = "?"
end end
Perun.LogEvent(eventName,Perun.SideID2Name(arg3) .. _temp_killers .. " in " .. arg2 .. " killed " .. Perun.SideID2Name(arg6) .. _temp_victims .. " in " .. victim_vehicle .. " using " .. arg7 .. " [".. Perun.GetCategory(arg5).."]",arg7,Perun.GetCategory(arg5)); Perun.LogEvent(Perun.PTS(eventName),Perun.PTS(Perun.SideID2Name(arg3)) .. _temp_killers .. " in " .. Perun.PTS(arg2) .. " killed " .. Perun.PTS(Perun.SideID2Name(arg6)) .. Perun.PTS(_temp_victims) .. " in " .. Perun.PTS(victim_vehicle) .. " using " .. Perun.PTS(arg7) .. " [".. Perun.PTS(Perun.GetCategory(arg5)).."]",arg7,Perun.GetCategory(arg5));
elseif eventName == "self_kill" then elseif eventName == "self_kill" then
--"self_kill", playerID --"self_kill", playerID
Perun.LogStats(arg1); Perun.LogStats(arg1);
Perun.LogEvent(eventName,net.get_player_info(arg1, "name") .. " killed himself",nil,nil); Perun.LogEvent(Perun.PTS(eventName),Perun.PTS(net.get_player_info(arg1, "name")) .. " killed himself",nil,nil);
elseif eventName == "change_slot" then elseif eventName == "change_slot" then
--"change_slot", playerID, slotID, prevSide --"change_slot", playerID, slotID, prevSide
@@ -807,7 +816,7 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7)
else else
_sub_slot =" (" .. _sub_slot .. ") " _sub_slot =" (" .. _sub_slot .. ") "
end end
Perun.LogEvent(eventName,Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player " .. net.get_player_info(arg1, "name") .. " changed slot to " .. _master_type .. " " .. _sub_slot,nil,nil); Perun.LogEvent(Perun.PTS(eventName),Perun.PTS(Perun.SideID2Name( net.get_player_info(arg1, "side"))) .. " player " .. Perun.PTS(net.get_player_info(arg1, "name")) .. " changed slot to " .. Perun.PTS(_master_type) .. " " .. Perun.PTS(_sub_slot),nil,nil);
Perun.LogStats(arg1); Perun.LogStats(arg1);
Perun.LogStatsCount(arg1,"init") Perun.LogStatsCount(arg1,"init")
@@ -816,23 +825,23 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7)
elseif eventName == "connect" then elseif eventName == "connect" then
--"connect", playerID, name --"connect", playerID, name
Perun.LogLogin(arg1); Perun.LogLogin(arg1);
Perun.LogEvent(eventName,"Player "..net.get_player_info(arg1, "name") .. " connected",nil,nil); Perun.LogEvent(Perun.PTS(eventName),"Player "..Perun.PTS(net.get_player_info(arg1, "name")) .. " connected",nil,nil);
Perun.PlayersTableCache["p"..arg1]=net.get_player_info(arg1); Perun.PlayersTableCache["p"..arg1]=net.get_player_info(arg1);
elseif eventName == "disconnect" then elseif eventName == "disconnect" then
--"disconnect", playerID, name, playerSide, reason_code --"disconnect", playerID, name, playerSide, reason_code
Perun.LogEvent(eventName,"Player " .. arg2 .. " disconnected (".. arg4 .. ")." ,arg4,nil); Perun.LogEvent(Perun.PTS(eventName),"Player " .. Perun.PTS(arg2) .. " disconnected (".. Perun.PTS(arg4) .. ")." ,arg4,nil);
Perun.LogStats(arg1); Perun.LogStats(arg1);
elseif eventName == "crash" then elseif eventName == "crash" then
--"crash", playerID, unit_missionID --"crash", playerID, unit_missionID
Perun.LogStatsCountCrew (arg1,"crash") Perun.LogStatsCountCrew (arg1,"crash")
Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " crashed in " .. DCS.getUnitType(arg2),nil,nil); Perun.LogEvent(Perun.PTS(eventName), Perun.PTS(Perun.SideID2Name( net.get_player_info(arg1, "side"))) .. " player(s) " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg1)) .. " crashed in " .. Perun.PTS(DCS.getUnitType(arg2)),nil,nil);
elseif eventName == "eject" then elseif eventName == "eject" then
--"eject", playerID, unit_missionID --"eject", playerID, unit_missionID
Perun.LogStatsCountCrew (arg1,"eject") -- TBD crew or initiator only? Perun.LogStatsCountCrew (arg1,"eject") -- TBD crew or initiator only?
Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " ejected " .. DCS.getUnitType(arg2),nil,nil); Perun.LogEvent(Perun.PTS(eventName), Perun.PTS(Perun.SideID2Name( net.get_player_info(arg1, "side"))) .. " player(s) " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg1)) .. " ejected " .. Perun.PTS(DCS.getUnitType(arg2)),nil,nil);
elseif eventName == "takeoff" then elseif eventName == "takeoff" then
--"takeoff", playerID, unit_missionID, airdromeName --"takeoff", playerID, unit_missionID, airdromeName
@@ -843,7 +852,7 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7)
end end
Perun.LogStatsCountCrew (arg1,Perun.GetTakeOffLandingEvent(true,arg3)) Perun.LogStatsCountCrew (arg1,Perun.GetTakeOffLandingEvent(true,arg3))
Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " took off in ".. DCS.getUnitType(arg2) .. _temp_airfield,arg3,nil); Perun.LogEvent(Perun.PTS(eventName), Perun.PTS(Perun.SideID2Name( net.get_player_info(arg1, "side"))) .. " player(s) " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg1)) .. " took off in ".. Perun.PTS(DCS.getUnitType(arg2)) .. Perun.PTS(_temp_airfield),arg3,nil);
elseif eventName == "landing" then elseif eventName == "landing" then
--"landing", playerID, unit_missionID, airdromeName --"landing", playerID, unit_missionID, airdromeName
@@ -854,15 +863,15 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7)
end end
Perun.LogStatsCountCrew (arg1,Perun.GetTakeOffLandingEvent(false,arg3)) Perun.LogStatsCountCrew (arg1,Perun.GetTakeOffLandingEvent(false,arg3))
Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " landed in " .. DCS.getUnitType(arg2).. _temp_airfield,arg3,nil); Perun.LogEvent(Perun.PTS(eventName), Perun.PTS(Perun.SideID2Name( net.get_player_info(arg1, "side"))) .. " player(s) " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg1)) .. " landed in " .. Perun.PTS(DCS.getUnitType(arg2)).. Perun.PTS(_temp_airfield),arg3,nil);
elseif eventName == "pilot_death" then elseif eventName == "pilot_death" then
--"pilot_death", playerID, unit_missionID --"pilot_death", playerID, unit_missionID
Perun.LogStatsCountCrew (arg1,"pilot_death") -- TBD crew or initiator only? Perun.LogStatsCountCrew (arg1,"pilot_death") -- TBD crew or initiator only?
Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " in " .. DCS.getUnitType(arg2) .. " died",nil,nil); Perun.LogEvent(Perun.PTS(eventName), Perun.PTS(Perun.SideID2Name( net.get_player_info(arg1, "side"))) .. " player(s) " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg1)) .. " in " .. Perun.PTS(DCS.getUnitType(arg2)) .. " died",nil,nil);
else else
Perun.LogEvent(eventName,"Unknown event type",nil,nil); Perun.LogEvent(Perun.PTS(eventName),"Unknown event type",nil,nil);
end end
local _delay = (DCS.getRealTime() - _now) * 1000000 local _delay = (DCS.getRealTime() - _now) * 1000000

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Google.Protobuf" version="3.5.1" targetFramework="net461" /> <package id="Google.Protobuf" version="3.15.0" targetFramework="net461" />
<package id="MySql.Data" version="8.0.15" targetFramework="net461" /> <package id="MySql.Data" version="8.0.15" targetFramework="net461" />
<package id="Newtonsoft.Json" version="12.0.1" targetFramework="net461" /> <package id="Newtonsoft.Json" version="13.0.1" targetFramework="net461" />
</packages> </packages>

View File

@@ -261,6 +261,14 @@ public class DatabaseController
DatabaseStatus = true; // True as connection is not broken DatabaseStatus = true; // True as connection is not broken
ReturnValue = 1; // Return a value to remove this query from quae ReturnValue = 1; // Return a value to remove this query from quae
break; break;
case 1366: // Invalid character
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - error id: {m_ex.Number}", 1, 1, TCPFrameType);
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - query: {SQLQueryTxt}", 1, 1, TCPFrameType);
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - error: {m_ex.Message}", 1, 1, TCPFrameType);
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - frame skipped, it will not be saved to the database", 1, 1, TCPFrameType);
DatabaseStatus = true; // True as connection is not broken
ReturnValue = 1; // Return a value to remove this query from queue
break;
default: // Default error handler default: // Default error handler
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - error id: {m_ex.Number}", 1, 1, TCPFrameType); PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - error id: {m_ex.Number}", 1, 1, TCPFrameType);
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - query: {SQLQueryTxt}", 1, 1, TCPFrameType); PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - query: {SQLQueryTxt}", 1, 1, TCPFrameType);

View File

@@ -231,7 +231,7 @@ namespace Perun_v1
trayIconMain.Text = this.Text; // Set notification icon text trayIconMain.Text = this.Text; // Set notification icon text
// Prepare MySQL connection string // Prepare MySQL connection string
Globals.DatabaseConnection.DatabaseConnectionString = $"server={con_txt_mysql_server.Text};user={con_txt_mysql_username.Text};database={con_txt_mysql_database.Text};port={con_txt_mysql_port.Text};password={con_txt_mysql_password.Text}"; Globals.DatabaseConnection.DatabaseConnectionString = $"server={con_txt_mysql_server.Text};user={con_txt_mysql_username.Text};database={con_txt_mysql_database.Text};port={con_txt_mysql_port.Text};password={con_txt_mysql_password.Text};CharSet=utf8mb4";
// Start listening // Start listening
PerunHelper.LogInfo(ref Globals.AppLogHistory, "Opening connections", 0, 1); PerunHelper.LogInfo(ref Globals.AppLogHistory, "Opening connections", 0, 1);

View File

@@ -31,8 +31,8 @@
<PublisherName>szporwolik</PublisherName> <PublisherName>szporwolik</PublisherName>
<SuiteName>Perun</SuiteName> <SuiteName>Perun</SuiteName>
<WebPage>Perun.htm</WebPage> <WebPage>Perun.htm</WebPage>
<ApplicationRevision>1</ApplicationRevision> <ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>0.12.0.%2a</ApplicationVersion> <ApplicationVersion>0.12.1.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust> <UseApplicationTrust>false</UseApplicationTrust>
<CreateDesktopShortcut>true</CreateDesktopShortcut> <CreateDesktopShortcut>true</CreateDesktopShortcut>
<PublishWizardCompleted>true</PublishWizardCompleted> <PublishWizardCompleted>true</PublishWizardCompleted>

View File

@@ -33,7 +33,7 @@ using System.Runtime.InteropServices;
// Możesz określić wszystkie wartości lub użyć domyślnych numerów kompilacji i poprawki // Możesz określić wszystkie wartości lub użyć domyślnych numerów kompilacji i poprawki
// przy użyciu symbolu „*”, tak jak pokazano poniżej: // przy użyciu symbolu „*”, tak jak pokazano poniżej:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.12.0.0")] [assembly: AssemblyVersion("0.12.1.0")]
[assembly: AssemblyFileVersion("0.12.0.0")] [assembly: AssemblyFileVersion("0.12.1.0")]
[assembly: NeutralResourcesLanguage("en")] [assembly: NeutralResourcesLanguage("en")]

View File

@@ -35,7 +35,7 @@
<value>3306</value> <value>3306</value>
</setting> </setting>
<setting name="DCS_Server_Port" serializeAs="String"> <setting name="DCS_Server_Port" serializeAs="String">
<value>48620</value> <value>48621</value>
</setting> </setting>
<setting name="DCS_Instance" serializeAs="String"> <setting name="DCS_Instance" serializeAs="String">
<value>1</value> <value>1</value>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="BouncyCastle" version="1.8.3.1" targetFramework="net48" /> <package id="BouncyCastle" version="1.8.9" targetFramework="net48" />
<package id="Google.Protobuf" version="3.6.1" targetFramework="net48" /> <package id="Google.Protobuf" version="3.15.0" targetFramework="net48" />
<package id="MySql.Data" version="8.0.17" targetFramework="net48" /> <package id="MySql.Data" version="8.0.17" targetFramework="net48" />
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net48" /> <package id="Newtonsoft.Json" version="13.0.1" targetFramework="net48" />
<package id="SSH.NET" version="2016.1.0" targetFramework="net48" /> <package id="SSH.NET" version="2020.0.2" targetFramework="net48" />
</packages> </packages>

View File

@@ -14,10 +14,10 @@ CREATE TABLE IF NOT EXISTS `pe_Config` (
`pe_Config_id` int(11) NOT NULL, `pe_Config_id` int(11) NOT NULL,
`pe_Config_payload` varchar(255) DEFAULT NULL, `pe_Config_payload` varchar(255) DEFAULT NULL,
PRIMARY KEY (`pe_Config_id`) PRIMARY KEY (`pe_Config_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
INSERT INTO `pe_Config` (`pe_Config_id`, `pe_Config_payload`) VALUES INSERT INTO `pe_Config` (`pe_Config_id`, `pe_Config_payload`) VALUES
(1, 'v0.12.0'); (1, 'v0.12.1');
DROP TABLE IF EXISTS `pe_DataMissionHashes`; DROP TABLE IF EXISTS `pe_DataMissionHashes`;
CREATE TABLE IF NOT EXISTS `pe_DataMissionHashes` ( CREATE TABLE IF NOT EXISTS `pe_DataMissionHashes` (
@@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS `pe_DataMissionHashes` (
PRIMARY KEY (`pe_DataMissionHashes_id`), PRIMARY KEY (`pe_DataMissionHashes_id`),
UNIQUE KEY `UNIQUE_hash` (`pe_DataMissionHashes_hash`), UNIQUE KEY `UNIQUE_hash` (`pe_DataMissionHashes_hash`),
KEY `pe_DataMissionHashes_instance` (`pe_DataMissionHashes_instance`) KEY `pe_DataMissionHashes_instance` (`pe_DataMissionHashes_instance`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `pe_DataPlayers`; DROP TABLE IF EXISTS `pe_DataPlayers`;
CREATE TABLE IF NOT EXISTS `pe_DataPlayers` ( CREATE TABLE IF NOT EXISTS `pe_DataPlayers` (
@@ -39,7 +39,7 @@ CREATE TABLE IF NOT EXISTS `pe_DataPlayers` (
`pe_DataPlayers_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `pe_DataPlayers_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`pe_DataPlayers_id`), PRIMARY KEY (`pe_DataPlayers_id`),
UNIQUE KEY `UNIQUE_UCID` (`pe_DataPlayers_ucid`) UNIQUE KEY `UNIQUE_UCID` (`pe_DataPlayers_ucid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `pe_DataRaw`; DROP TABLE IF EXISTS `pe_DataRaw`;
CREATE TABLE IF NOT EXISTS `pe_DataRaw` ( CREATE TABLE IF NOT EXISTS `pe_DataRaw` (
@@ -50,7 +50,7 @@ CREATE TABLE IF NOT EXISTS `pe_DataRaw` (
PRIMARY KEY (`pe_dataraw_type`,`pe_dataraw_instance`), PRIMARY KEY (`pe_dataraw_type`,`pe_dataraw_instance`),
KEY `pe_dataraw_type_pe_dataraw_instance` (`pe_dataraw_type`,`pe_dataraw_instance`), KEY `pe_dataraw_type_pe_dataraw_instance` (`pe_dataraw_type`,`pe_dataraw_instance`),
KEY `pe_dataraw_updated` (`pe_dataraw_updated`) KEY `pe_dataraw_updated` (`pe_dataraw_updated`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `pe_DataTypes`; DROP TABLE IF EXISTS `pe_DataTypes`;
CREATE TABLE IF NOT EXISTS `pe_DataTypes` ( CREATE TABLE IF NOT EXISTS `pe_DataTypes` (
@@ -59,7 +59,7 @@ CREATE TABLE IF NOT EXISTS `pe_DataTypes` (
`pe_DataTypes_update` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `pe_DataTypes_update` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`pe_DataTypes_id`), PRIMARY KEY (`pe_DataTypes_id`),
UNIQUE KEY `UNIQUE_TYPE_NAME` (`pe_DataTypes_name`) UNIQUE KEY `UNIQUE_TYPE_NAME` (`pe_DataTypes_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `pe_LogChat`; DROP TABLE IF EXISTS `pe_LogChat`;
CREATE TABLE IF NOT EXISTS `pe_LogChat` ( CREATE TABLE IF NOT EXISTS `pe_LogChat` (
@@ -74,7 +74,7 @@ CREATE TABLE IF NOT EXISTS `pe_LogChat` (
KEY `pe_LogChat_playerid` (`pe_LogChat_playerid`), KEY `pe_LogChat_playerid` (`pe_LogChat_playerid`),
KEY `pe_LogChat_datetime` (`pe_LogChat_datetime`), KEY `pe_LogChat_datetime` (`pe_LogChat_datetime`),
KEY `pe_LogChat_all` (`pe_LogChat_all`) KEY `pe_LogChat_all` (`pe_LogChat_all`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `pe_LogEvent`; DROP TABLE IF EXISTS `pe_LogEvent`;
CREATE TABLE IF NOT EXISTS `pe_LogEvent` ( CREATE TABLE IF NOT EXISTS `pe_LogEvent` (
@@ -90,7 +90,7 @@ CREATE TABLE IF NOT EXISTS `pe_LogEvent` (
KEY `pe_LogEvent_missionhash_id` (`pe_LogEvent_missionhash_id`), KEY `pe_LogEvent_missionhash_id` (`pe_LogEvent_missionhash_id`),
KEY `pe_LogEvent_datetime` (`pe_LogEvent_datetime`), KEY `pe_LogEvent_datetime` (`pe_LogEvent_datetime`),
KEY `pe_LogEvent_type_2` (`pe_LogEvent_type`) KEY `pe_LogEvent_type_2` (`pe_LogEvent_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `pe_LogLogins`; DROP TABLE IF EXISTS `pe_LogLogins`;
CREATE TABLE IF NOT EXISTS `pe_LogLogins` ( CREATE TABLE IF NOT EXISTS `pe_LogLogins` (
@@ -104,7 +104,7 @@ CREATE TABLE IF NOT EXISTS `pe_LogLogins` (
KEY `pe_LogLogins_playerid` (`pe_LogLogins_playerid`), KEY `pe_LogLogins_playerid` (`pe_LogLogins_playerid`),
KEY `pe_LogLogins_datetime` (`pe_LogLogins_datetime`), KEY `pe_LogLogins_datetime` (`pe_LogLogins_datetime`),
KEY `pe_LogLogins_instance` (`pe_LogLogins_instance`) KEY `pe_LogLogins_instance` (`pe_LogLogins_instance`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `pe_LogStats`; DROP TABLE IF EXISTS `pe_LogStats`;
CREATE TABLE IF NOT EXISTS `pe_LogStats` ( CREATE TABLE IF NOT EXISTS `pe_LogStats` (
@@ -149,7 +149,7 @@ CREATE TABLE IF NOT EXISTS `pe_LogStats` (
KEY `pe_LogStats_masterslot` (`pe_LogStats_masterslot`), KEY `pe_LogStats_masterslot` (`pe_LogStats_masterslot`),
KEY `pe_LogStats_mstatus` (`pe_LogStats_mstatus`), KEY `pe_LogStats_mstatus` (`pe_LogStats_mstatus`),
KEY `pe_LogStats_seat` (`pe_LogStats_seat`) KEY `pe_LogStats_seat` (`pe_LogStats_seat`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP TRIGGER IF EXISTS `pe_LogStats_UPDATE`; DROP TRIGGER IF EXISTS `pe_LogStats_UPDATE`;
DELIMITER $$ DELIMITER $$
CREATE TRIGGER `pe_LogStats_UPDATE` BEFORE UPDATE ON `pe_LogStats` FOR EACH ROW BEGIN CREATE TRIGGER `pe_LogStats_UPDATE` BEFORE UPDATE ON `pe_LogStats` FOR EACH ROW BEGIN
@@ -182,7 +182,7 @@ CREATE TABLE IF NOT EXISTS `pe_OnlinePlayers` (
`pe_OnlinePlayers_slot` varchar(255) DEFAULT NULL, `pe_OnlinePlayers_slot` varchar(255) DEFAULT NULL,
`pe_OnlinePlayers_ucid` varchar(255) DEFAULT NULL, `pe_OnlinePlayers_ucid` varchar(255) DEFAULT NULL,
`pe_OnlinePlayers_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP `pe_OnlinePlayers_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `pe_OnlineStatus`; DROP TABLE IF EXISTS `pe_OnlineStatus`;
CREATE TABLE IF NOT EXISTS `pe_OnlineStatus` ( CREATE TABLE IF NOT EXISTS `pe_OnlineStatus` (
@@ -197,7 +197,7 @@ CREATE TABLE IF NOT EXISTS `pe_OnlineStatus` (
`pe_OnlineStatus_perunversion_winapp` varchar(255) DEFAULT NULL, `pe_OnlineStatus_perunversion_winapp` varchar(255) DEFAULT NULL,
`pe_OnlineStatus_perunversion_dcshook` varchar(255) DEFAULT NULL, `pe_OnlineStatus_perunversion_dcshook` varchar(255) DEFAULT NULL,
`pe_OnlineStatus_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP `pe_OnlineStatus_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `pe_DataMissionHashes` ADD FULLTEXT KEY `pe_DataMissionHashes_hash` (`pe_DataMissionHashes_hash`); ALTER TABLE `pe_DataMissionHashes` ADD FULLTEXT KEY `pe_DataMissionHashes_hash` (`pe_DataMissionHashes_hash`);

238
AUDIT.md Normal file
View File

@@ -0,0 +1,238 @@
# Audit de reprise — Perun for DCS World
> Revue menée pour décider d'une reprise du projet (upstream `szporwolik/perun`,
> **archivé le 2026-03-29**, licence MIT). Fork de travail : `DaKerboul/perun`,
> miroir Gitea : `git.kerboul.me/kerboul/perun`.
>
> **Méthode.** Revue à 5 casquettes (Architecte, Sécurité, Backend .NET, DCS/Lua,
> DevOps/DBA). **Périmètre audité** : le code réellement écrit par le projet —
> hook Lua (`01_DCS`), app C# WinForms (`02_Windows_App`), wrapper C++ (`03_*`,
> hors arbre Lua 5.1.5 vendoré), schéma MySQL (`04_MySQL`), exemple PHP
> (`05_Misc`). Les ~35 `.c`/`.h` de `lua-5.1.5/` sont du Lua amont vendoré et **ne
> sont pas audités**.
> Date : 2026-06-11.
---
## Synthèse — verdict
Projet **fonctionnellement riche et bien pensé sur le fond** (modèle de données
propre, couverture événementielle DCS complète, multi-instances, intégrations
SRS/LotATC). Mais **dette de sécurité critique** et code applicatif daté
(monolithes, threading artisanal, zéro test, zéro CI, Windows/.NET Framework
uniquement). C'est une **bonne base à reprendre**, à condition de traiter les
points P0 **avant** toute remise en production.
| # | Domaine | Gravité | Sévérité |
|---|---------|---------|----------|
| S1 | Injection SQL via données joueur (app C#) | **Critique** | 🔴 P0 |
| S2 | Port TCP sans authentification, bind `0.0.0.0` | **Critique** | 🔴 P0 |
| S3 | XSS stocké dans l'exemple PHP | Élevé | 🟠 P1 |
| S4 | Mot de passe MySQL stocké en clair | Élevé | 🟠 P1 |
| S5 | PII (IP, UCID) sans rétention ni consentement (RGPD) | Élevé | 🟠 P1 |
| B1 | Threading non synchronisé (buffer partagé) | Élevé | 🟠 P1 |
| B2 | `ExecuteReader` pour des INSERT/UPDATE, 1 connexion/frame | Moyen | 🟡 P2 |
| A1 | Monolithes, `dynamic`, aucun test, aucune CI | Moyen | 🟡 P2 |
| L1 | Fuite de variables globales dans le hook DCS | Moyen | 🟡 P2 |
| D1 | Lua 5.1.5 vendoré dans le repo, pas de build reproductible | Moyen | 🟡 P2 |
| D2 | Pas de FK, dépendance à `STRICT_TRANS_TABLES` off, pas de migrations | Faible | 🟢 P3 |
---
## 1. Architecte / Lead — structure & dette
**Points forts**
- Découpage fonctionnel clair en 5 dossiers numérotés, lisible d'emblée.
- Séparation nette des responsabilités : collecte (Lua) → transport (TCP/DLL) →
persistance (C#) → restitution (PHP). Le protocole de trames (IDs 1/2/3/50…101)
est documenté dans le README.
- Multi-instances supporté de bout en bout (champ `instance` partout).
**Points faibles**
- **Monolithes.** `DatabaseController.SendToMySql` fait ~290 lignes avec un `switch`
géant mêlant construction SQL, mapping métier et logging
(`01_Classes/DatabaseController.cs:12-304`). Idem `TCPController.StartListen`
(boucles imbriquées sur ~170 lignes).
- **`dynamic` partout** pour le JSON entrant (`DatabaseController.cs:25`,
`TCPController.cs:121`) : aucune validation de schéma, accès `TCPFrame.payload.x`
qui lèvent au moindre champ manquant → exceptions au lieu d'un rejet propre.
- **Plateforme verrouillée** : VS2017, .NET Framework 4.8, WinForms → Windows
uniquement, fin de vie. Or le serveur DCS est Windows, mais l'app de
persistance n'a aucune raison d'y être clouée (elle ne parle que TCP + MySQL).
- **TODO laissé en dur** (commentaire polonais) :
`DatabaseController.cs:92` « TUTAJ DODAC CATCH TBD » → gestion d'erreur inachevée.
- **Aucun test, aucune CI**, dérive de version (cf. §4 L-version).
**À faire**
- [ ] Extraire la construction SQL dans une couche dédiée (un handler par type de
trame) + DTO typés à la place de `dynamic`.
- [ ] Cibler **.NET 8** + un worker headless multiplateforme ; garder l'UI WinForms
en option (ou la remplacer par un petit panneau web/CLI).
- [ ] Introduire des tests unitaires (parsing de trames, génération SQL) et un
pipeline CI.
## 2. Sécurité (AppSec) — **bloquant pour la prod**
**S1 — Injection SQL via données contrôlées par le joueur. 🔴**
L'app paramètre *certaines* valeurs (`@PAR_*`) mais **en concatène des dizaines
d'autres** directement dans le SQL, dont des champs que n'importe quel joueur
maîtrise (UCID, nom, hash de mission, IP, datetime, tous les compteurs `ps_*`) :
- `DatabaseController.cs:128-132` (chat) : `ucid`, `missionhash`, `all`, `datetime`.
- `DatabaseController.cs:159-165` (stats) : `stat_ucid`, `stat_missionhash` et
~30 valeurs `stat_data_perun.ps_*` injectées telles quelles.
- `DatabaseController.cs:175-177` (login) : `login_ucid`, `login_ipaddr`, `login_datetime`.
- `DatabaseController.cs:50-51, 77-89, 205-206` : `instance`/`type` concaténés.
Un UCID/nom forgé (ou un module client modifié) permet l'exfiltration ou la
destruction de la base. C'est le **défaut n°1 à corriger**.
**Tout** passer en requêtes paramétrées (`MySqlParameter`), sans exception.
**S2 — Listener TCP non authentifié, exposé. 🔴**
`new TcpListener(IPAddress.Any, intListenPort)` (`TCPController.cs:51`) écoute sur
**toutes** les interfaces, **sans authentification ni allowlist**. Couplé à S1,
n'importe quel hôte joignant le port (48621 par défaut) injecte des trames
arbitraires → compromission complète de la base.
→ Par défaut **bind `127.0.0.1`** (hook et app sont quasi toujours sur la même
machine), + secret partagé/HMAC sur les trames, + allowlist d'IP.
**S3 — XSS stocké (PHP). 🟠**
`05_Misc/05_PHP_Example/index.php` réinjecte en HTML des données joueur **sans
échappement** : message de chat (`:110`), nom (`:92, :109, :143`),
contenu d'événement (`:127`). Un joueur dont le nom vaut `<script>…</script>`
exécute du JS dans le navigateur de l'admin. Les requêtes elles-mêmes sont
statiques (pas de SQLi côté PHP), le risque est l'**XSS**.
`htmlspecialchars()` systématique sur toute sortie ; corriger aussi le HTML
invalide (`<h1>` fermé par `</h2>` ligne 44).
**S4 — Identifiants MySQL en clair. 🟠**
Le mot de passe est stocké en `String` dans les user settings .NET
(`02_Forms/form_Main.cs:119`, clé `MYSQL_Password` de `app.config`) → écrit en
clair dans `user.config`.
→ Chiffrer via **DPAPI** (`ProtectedData`) ou déléguer à un gestionnaire de
secrets ; a minima ne jamais journaliser la chaîne de connexion.
**S5 — Données personnelles (RGPD). 🟠**
Le hook collecte et stocke **adresses IP** et **UCID** des joueurs
(`Perun-hook.lua:364-366``pe_DataPlayers_lastip`, `pe_LogLogins_ip`), sans
politique de rétention ni information des joueurs.
→ Définir une durée de rétention + purge, anonymiser/hacher l'IP si non
nécessaire, documenter (mention serveur + Discord).
## 3. Backend / .NET — qualité & robustesse
**B1 — Threading artisanal non synchronisé. 🟠**
Le thread TCP écrit dans `Globals.arrMySQLSendBuffer` (tableau fixe) pendant que
le thread d'envoi le lit, **sans verrou** (`TCPController.cs:130-142`). Buffer
plein = paquets **silencieusement perdus** (`:139-141`), scan linéaire O(n) par
paquet. Accès concurrents → corruption/race.
→ Remplacer par une `BlockingCollection<T>`/`Channel<T>` thread-safe et bornée.
**B2 — Accès base inefficace. 🟡**
- `ExecuteReader()` utilisé pour exécuter des lots d'INSERT/UPDATE
(`DatabaseController.cs:217`) : devrait être `ExecuteNonQuery()`.
- **Une nouvelle `MySqlConnection` ouverte/fermée par trame** (`:34, :292`) :
pas de réutilisation du pool, surcoût réseau par paquet.
- Tout est **synchrone bloquant** (pas d'`async`/`await`).
→ Connexion/pool réutilisé, requêtes asynchrones, `ExecuteNonQueryAsync`.
**Autres**
- Dépendance `Newtonsoft.Json` → migrable vers `System.Text.Json`.
- Gestion d'erreurs par numéros MySQL en dur (`:246-279`) : utile mais fragile,
à compléter (le catch manquant signalé en `:92`).
## 4. DCS / Lua / Intégration
**Points forts**
- Couverture événementielle DCS **très complète** : kill (avec catégorisation
PvP/AI), friendly fire, crash, eject, takeoff/landing (airfield/ship/FARP),
multicrew, change_slot, connect/disconnect, chat, MOTD. C'est le vrai actif du
projet (`Perun-hook.lua:725-879`).
- Comptage de stats maison car les stats natives DCS sont peu fiables (choix
assumé et pertinent).
**Points faibles**
- **L1 — Fuite de variables globales 🟡** : plusieurs variables sont assignées
sans `local` dans l'environnement *hook* (privilégié et partagé) — ex.
`_temp_killers`/`_temp_event_type` (`:754-756`), `_master_type`/`_master_slot`/
`_sub_slot` (`:813`), `_temp_airfield` (`:849`). Risque de collision avec
d'autres hooks installés sur le serveur.
- **L-version — dérive de version** : la version du hook est codée en dur
`"v0.12.1"` (`:30`) et vit séparément de la version de l'app (`Globals.VersionPerun`).
→ source unique de vérité (tag git → injecté au build).
- **Coût par frame** : `onSimulationFrame` fait du `table.concat`/JSON à chaque
frame ; sur serveur chargé, surveiller le budget temps (déjà mesuré en µs dans
les logs — bon réflexe à conserver/exposer).
- **Dépendance à une DLL compilée** (`perun.dll`, issue de `03_Perun_Lua_Wrapper`)
livrée hors repo : reproductibilité du build à fiabiliser (cf. D1).
- TCP **en clair**, pas de TLS (acceptable en loopback, à revoir si distant).
**À faire**
- [ ] `local` sur toutes les temporaires ; passe `luacheck`.
- [ ] Versionner hook + app depuis le tag git.
- [ ] Documenter/reproduire le build de `perun.dll` (CMake déjà présent).
## 5. DevOps / DBA / Release
**Base de données — plutôt saine.**
- InnoDB + `utf8mb4`/`unicode_ci`, PK/`AUTO_INCREMENT`, **clés UNIQUE**
pertinentes (UCID, hash, type, stats par mission+ucid+type) et index sur les
colonnes de tri (`datetime`, `instance`, `type`) — `04_MySQL/m1081_perun.sql`.
- **D2 🟢** : pas de **`FOREIGN KEY`** (intégrité référentielle non garantie),
dépendance documentée à **`STRICT_TRANS_TABLES` désactivé** (README:42,84) —
c'est-à-dire qu'on s'appuie sur la coercition/troncature silencieuse de MySQL,
ce qui masque des bugs. Nom de fichier cryptique (`m1081_perun.sql`).
- Pas d'outil de **migration** (un seul dump). → introduire des migrations
versionnées (Flyway/dbmate/sqitch) et, à terme, des FK + un mode strict assumé.
**Build / release / repo**
- **D1 🟡** : arbre **Lua 5.1.5 complet vendoré** dans `03_Perun_Lua_Wrapper/lua-5.1.5/`
→ gonfle le repo et l'audit. Le passer en sous-module / téléchargement au build.
- Build **manuel VS2017**, pas de CI, pas d'artefacts reproductibles.
- Contributions historiquement attendues sur la branche `dev` (README:135).
- Pas de `.gitignore` racine (un seul dans le wrapper).
**À faire**
- [ ] CI (build C# + `luacheck` + lint PHP + validation du schéma SQL).
- [ ] Migrations DB versionnées ; activer les FK progressivement.
- [ ] Sortir Lua amont du repo ; pipeline de build de la DLL.
- [ ] Releases taguées avec binaires (`perun.dll` + app) attachés.
---
## Feuille de route priorisée
**P0 — Sécurité bloquante (avant toute prod)**
1. Paramétrer **100 %** des requêtes SQL (S1).
2. Bind loopback par défaut + auth/allowlist sur le listener TCP (S2).
**P1 — Durcissement & conformité**
3. Échappement HTML de l'exemple PHP (S3).
4. Chiffrer le mot de passe MySQL — DPAPI (S4).
5. Rétention/anonymisation IP & UCID, doc RGPD (S5).
6. File thread-safe bornée à la place du buffer tableau (B1).
**P2 — Modernisation**
7. Découper les monolithes, DTO typés au lieu de `dynamic` (A1).
8. Connexion poolée + accès DB async + `ExecuteNonQuery` (B2).
9. `local` + `luacheck` sur le hook, version unifiée depuis git (L1).
10. Cibler .NET 8 / worker multiplateforme.
11. Tests + CI.
**P3 — Hygiène long terme**
12. Migrations DB, FK, mode SQL strict assumé (D2).
13. Désvendoriser Lua, build reproductible de la DLL (D1).
14. Releases taguées + artefacts.
## Plan de reprise suggéré
1. **Geler le comportement** : quelques tests de caractérisation sur le parsing de
trames et la génération SQL, pour refactorer sans régresser.
2. **Sprint sécu (P0)** sur une branche `security/sql-and-tcp`, puis tag
`v0.13.0-rc` testé en loopback.
3. **P1** en incréments livrables.
4. Décider de la cible app (garder WinForms vs worker .NET 8) **avant** d'attaquer
P2 — ça oriente tout le refactor.
> Adapté à ton contexte : tu fais déjà tourner Gitea + CI sur le cluster ; un
> pipeline build/lint Perun s'y intègre directement, et la communauté Commus DCS
> est un terrain de test naturel pour les stats.

View File

@@ -1,8 +1,14 @@
> [!IMPORTANT]
> This repository is no longer actively maintained.
>
> I have not hosted games or played DCS for a long time, so I decided to archive this project.
>
> The code will remain available for reference. Anyone interested is welcome to fork the repository and continue maintaining or improving it.
![Discord](https://img.shields.io/discord/641759751780302866) ![Discord](https://img.shields.io/discord/641759751780302866)
![alt text](https://img.shields.io/github/license/szporwolik/perun.svg "MIT") ![alt text](https://img.shields.io/github/license/szporwolik/perun.svg "MIT")
![alt text](https://img.shields.io/github/release-pre/szporwolik/perun.svg "Latest release") ![alt text](https://img.shields.io/github/release-pre/szporwolik/perun.svg "Latest release")
![alt text](https://img.shields.io/github/release-date-pre/szporwolik/perun.svg "Latest release date") ![alt text](https://img.shields.io/github/release-date-pre/szporwolik/perun.svg "Latest release date")
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=ZBW8R5L25A8QW&currency_code=EUR&source=url)
![Perun logo](https://i.imgur.com/PrIkqNA.png) ![Perun logo](https://i.imgur.com/PrIkqNA.png)
@@ -30,7 +36,7 @@ Core:
## Installing ## Installing
* Download latest [release](https://github.com/szporwolik/perun/releases) * Download latest [release](https://github.com/szporwolik/perun/releases)
* Copy contents of [01_DCS](https://github.com/szporwolik/perun/tree/master/01_DCS) to your DCS folder in your Saved Games * Copy contents of [01_DCS](https://github.com/szporwolik/perun/tree/master/01_DCS) to your DCS folder in your Saved Games
* From the downloaded release package, put ```perun.dll``` into your DCS folder in your Saved Games,```Mods/services/Perun/dll``` * From the downloaded release package, put ```perun.dll``` into your DCS folder in your Saved Games,```Mods/services/Perun/bin```
* You can change your configuration settings in DCS\Mods\services\Perun\lua\perun_config * You can change your configuration settings in DCS\Mods\services\Perun\lua\perun_config
* Create MySQL database using SQL script located in [04_MySQL](https://github.com/szporwolik/perun/tree/master/04_MySQL); note that you need just a one database per DCS server machine - multiple instances pushing data to the one database are supported * Create MySQL database using SQL script located in [04_MySQL](https://github.com/szporwolik/perun/tree/master/04_MySQL); note that you need just a one database per DCS server machine - multiple instances pushing data to the one database are supported
* Ensure that your MySQL config is not using STRICT_TRANS_TABLES * Ensure that your MySQL config is not using STRICT_TRANS_TABLES
@@ -77,6 +83,9 @@ That probably means that your database does not support JSON functions, you shal
### I keep getting 1364 MySQL error ### I keep getting 1364 MySQL error
Make sure that STRICT_TRANS_TABLES is disabled at your MySQL server, see: https://stackoverflow.com/questions/37964325/how-to-find-and-disable-mysql-strict-mode Make sure that STRICT_TRANS_TABLES is disabled at your MySQL server, see: https://stackoverflow.com/questions/37964325/how-to-find-and-disable-mysql-strict-mode
### Dynamic slots (DCS 2.9.6 onwards) are not tracked
Not yet handled. (Existing server-side events do not support this yet).
### Carrier landing are not tracked correctly ### Carrier landing are not tracked correctly
DCS API does not track carrier or FARP operations natively, so there is a trick to achive that. At this point of time to detect FARP operations, the FARPs shall have "FARP" string in the group name (set in mission editor). For carrier operations the string "SHIP" is required within group name. DCS API does not track carrier or FARP operations natively, so there is a trick to achive that. At this point of time to detect FARP operations, the FARPs shall have "FARP" string in the group name (set in mission editor). For carrier operations the string "SHIP" is required within group name.