From 90201150cc0d08e612a6e6d1bfc601e488008208 Mon Sep 17 00:00:00 2001 From: Michael VanOverbeek Date: Fri, 29 Oct 2021 03:15:25 -0400 Subject: [PATCH 01/13] Server digest support for LBP 1 and LBP 3 --- .idea/.idea.ProjectLighthouse/.idea/.name | 1 + .../ClientConfigurationController.cs | 26 +++-- .../Controllers/LoginController.cs | 25 +++-- .../Controllers/MatchController.cs | 51 ++++++---- .../Controllers/MessageController.cs | 30 ++++-- .../Controllers/NewsController.cs | 24 +++-- .../Controllers/ResourcesController.cs | 42 ++++---- .../Controllers/UserController.cs | 89 ++++++++++++----- ProjectLighthouse/Database.cs | 41 +++++--- ProjectLighthouse/DigestUtils.cs | 39 ++++++++ .../Logging/LighthouseFileLogger.cs | 4 +- ProjectLighthouse/Program.cs | 44 +++++---- ProjectLighthouse/Startup.cs | 98 ++++++++++++++++--- 13 files changed, 368 insertions(+), 146 deletions(-) create mode 100644 .idea/.idea.ProjectLighthouse/.idea/.name create mode 100644 ProjectLighthouse/DigestUtils.cs diff --git a/.idea/.idea.ProjectLighthouse/.idea/.name b/.idea/.idea.ProjectLighthouse/.idea/.name new file mode 100644 index 00000000..cbe8482b --- /dev/null +++ b/.idea/.idea.ProjectLighthouse/.idea/.name @@ -0,0 +1 @@ +ProjectLighthouse \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/ClientConfigurationController.cs b/ProjectLighthouse/Controllers/ClientConfigurationController.cs index 7e5aefce..94010d42 100644 --- a/ProjectLighthouse/Controllers/ClientConfigurationController.cs +++ b/ProjectLighthouse/Controllers/ClientConfigurationController.cs @@ -2,36 +2,44 @@ using System.Diagnostics.CodeAnalysis; using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.AspNetCore.Mvc; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] [Produces("text/plain")] - public class ClientConfigurationController : ControllerBase { + public class ClientConfigurationController : ControllerBase + { [HttpGet("network_settings.nws")] [SuppressMessage("ReSharper", "StringLiteralTypo")] - public IActionResult NetworkSettings() { - return this.Ok("ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels true\nAllowModeratedPoppetItems true\nShowLevelBoos true\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 120.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 30.0\nTIMEOUT_DIVE_IN_TOTAL 1000000.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nCDNHostName localhost\nTelemetryServer localhost\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false"); + public IActionResult NetworkSettings() + { + return this.Ok( + "ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels true\nAllowModeratedPoppetItems true\nShowLevelBoos true\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 120.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 30.0\nTIMEOUT_DIVE_IN_TOTAL 1000000.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nCDNHostName localhost\nTelemetryServer localhost\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false"); } [HttpGet("t_conf")] [Produces("text/json")] - public IActionResult Conf() { + public IActionResult Conf() + { return this.Ok("[{\"StatusCode\":200}]"); } [HttpGet("farc_hashes")] - public IActionResult FarcHashes() { + public IActionResult FarcHashes() + { return this.Ok(); } [HttpGet("privacySettings")] [Produces("text/xml")] - public IActionResult PrivacySettings() { - PrivacySettings ps = new() { + public IActionResult PrivacySettings() + { + PrivacySettings ps = new() + { LevelVisibility = "all", ProfileVisibility = "all", }; - + return this.Ok(ps.Serialize()); } } diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index edf494d0..491c1bfb 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -5,34 +5,41 @@ using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.AspNetCore.Mvc; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/login")] [Produces("text/xml")] - public class LoginController : ControllerBase { + public class LoginController : ControllerBase + { private readonly Database database; - public LoginController(Database database) { + public LoginController(Database database) + { this.database = database; } - + [HttpPost] - public async Task Login() { + public async Task Login() + { string body = await new StreamReader(this.Request.Body).ReadToEndAsync(); LoginData loginData; - try { + try + { loginData = LoginData.CreateFromString(body); } - catch { + catch + { return this.BadRequest(); } Token? token = await this.database.AuthenticateUser(loginData); - if(token == null) return this.StatusCode(403, ""); + if (token == null) return this.StatusCode(403, ""); - return this.Ok(new LoginResult { + return this.Ok(new LoginResult + { AuthTicket = "MM_AUTH=" + token.UserToken, LbpEnvVer = ServerSettings.ServerName, }.Serialize()); diff --git a/ProjectLighthouse/Controllers/MatchController.cs b/ProjectLighthouse/Controllers/MatchController.cs index 6d34b60e..e4ccb354 100644 --- a/ProjectLighthouse/Controllers/MatchController.cs +++ b/ProjectLighthouse/Controllers/MatchController.cs @@ -11,57 +11,70 @@ using LBPUnion.ProjectLighthouse.Types.Profiles; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] [Produces("text/xml")] - public class MatchController : ControllerBase { + public class MatchController : ControllerBase + { private readonly Database database; - public MatchController(Database database) { + + public MatchController(Database database) + { this.database = database; } [HttpPost("match")] [Produces("text/json")] - public async Task Match() { + public async Task Match() + { User? user = await this.database.UserFromRequest(this.Request); - if(user == null) return this.StatusCode(403, ""); + if (user == null) return this.StatusCode(403, ""); #region Parse match data + // Example POST /match: [UpdateMyPlayerData,["Player":"FireGamer9872"]] - + string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); - if(bodyString.Contains("FindBestRoom")) { - return this.Ok("[{\"StatusCode\":200},{\"Players\":[{\"PlayerId\":\"literally1984\",\"matching_res\":0},{\"PlayerId\":\"jvyden\",\"matching_res\":1}],\"Slots\":[[5,0]],\"RoomState\":\"E_ROOM_IN_POD\",\"HostMood\":\"E_MOOD_EVERYONE\",\"LevelCompletionEstimate\":0,\"PassedNoJoinPoint\":0,\"MoveConnected\":false,\"Location\":[\"127.0.0.1\"],\"BuildVersion\":289,\"Language\":1,\"FirstSeenTimestamp\":1427331263756,\"LastSeenTimestamp\":1635112546000,\"GameId\":1,\"NatType\":2,\"Friends\":[],\"Blocked\":[],\"RecentlyLeft\":[],\"FailedJoin\":[]}]"); + if (bodyString.Contains("FindBestRoom")) + { + return this.Ok( + "[{\"StatusCode\":200},{\"Players\":[{\"PlayerId\":\"literally1984\",\"matching_res\":0},{\"PlayerId\":\"jvyden\",\"matching_res\":1}],\"Slots\":[[5,0]],\"RoomState\":\"E_ROOM_IN_POD\",\"HostMood\":\"E_MOOD_EVERYONE\",\"LevelCompletionEstimate\":0,\"PassedNoJoinPoint\":0,\"MoveConnected\":false,\"Location\":[\"127.0.0.1\"],\"BuildVersion\":289,\"Language\":1,\"FirstSeenTimestamp\":1427331263756,\"LastSeenTimestamp\":1635112546000,\"GameId\":1,\"NatType\":2,\"Friends\":[],\"Blocked\":[],\"RecentlyLeft\":[],\"FailedJoin\":[]}]"); } - - if(bodyString[0] != '[') return this.BadRequest(); + + if (bodyString[0] != '[') return this.BadRequest(); IMatchData? matchData; - try { + try + { matchData = MatchHelper.Deserialize(bodyString); } - catch(Exception e) { + catch (Exception e) + { Logger.Log("Exception while parsing MatchData: " + e); Logger.Log("Data: " + bodyString); return this.BadRequest(); } - if(matchData == null) return this.BadRequest(); - + if (matchData == null) return this.BadRequest(); + #endregion #region Update LastMatch + LastMatch? lastMatch = await this.database.LastMatches .Where(l => l.UserId == user.UserId).FirstOrDefaultAsync(); // below makes it not look like trash // ReSharper disable once ConvertIfStatementToNullCoalescingExpression - if(lastMatch == null) { - lastMatch = new LastMatch { + if (lastMatch == null) + { + lastMatch = new LastMatch + { UserId = user.UserId, }; this.database.LastMatches.Add(lastMatch); @@ -70,14 +83,16 @@ namespace LBPUnion.ProjectLighthouse.Controllers { lastMatch.Timestamp = TimestampHelper.Timestamp; await this.database.SaveChangesAsync(); + #endregion - + return this.Ok("[{\"StatusCode\":200}]"); } [HttpGet("playersInPodCount")] [HttpGet("totalPlayerCount")] - public async Task TotalPlayerCount() { + public async Task TotalPlayerCount() + { int recentMatches = await this.database.LastMatches .Where(l => TimestampHelper.Timestamp - l.Timestamp < 60) .CountAsync(); diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index ff6b8112..eba2834a 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -3,38 +3,48 @@ using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Types; using Microsoft.AspNetCore.Mvc; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] [Produces("text/plain")] - public class MessageController : ControllerBase { + public class MessageController : ControllerBase + { private readonly Database database; - public MessageController(Database database) { + + public MessageController(Database database) + { this.database = database; } [HttpGet("eula")] - public async Task Eula() { + public async Task Eula() + { User user = await this.database.UserFromRequest(this.Request); - return user == null ? this.StatusCode(403, "") : - this.Ok($"You are now logged in as user {user.Username} (id {user.UserId}).\n" + - "This is a private testing instance. Please do not make anything public for now, and keep in mind security isn't as tight as a full release would."); + return user == null + ? this.Ok("You aren't logged in, but you're connected to a private LBP server.") + : this.Ok($"You are now logged in as user {user.Username} (id {user.UserId}).\n" + + "This is a private testing instance. Please do not make anything public for now, and keep in mind security isn't as tight as a full release would."); } [HttpGet("announce")] - public IActionResult Announce() { + public IActionResult Announce() + { return this.Ok(""); } [HttpGet("notification")] - public IActionResult Notification() { + public IActionResult Notification() + { return this.Ok(); } + /// /// Filters chat messages sent by a user. /// [HttpPost("filter")] - public async Task Filter() { + public async Task Filter() + { return this.Ok(await new StreamReader(this.Request.Body).ReadToEndAsync()); } } diff --git a/ProjectLighthouse/Controllers/NewsController.cs b/ProjectLighthouse/Controllers/NewsController.cs index 5fa6e8f3..10a29254 100644 --- a/ProjectLighthouse/Controllers/NewsController.cs +++ b/ProjectLighthouse/Controllers/NewsController.cs @@ -1,18 +1,30 @@ +using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types.News; using Microsoft.AspNetCore.Mvc; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/news")] [Produces("text/xml")] - public class NewsController : ControllerBase { + public class NewsController : ControllerBase + { + [HttpGet("/developer_videos")] + public async Task DeveloperVideos() + { + return Ok(); + } + [HttpGet] - public IActionResult Get() { - string newsEntry = LbpSerializer.StringElement("item", new NewsEntry { + public IActionResult Get() + { + string newsEntry = LbpSerializer.StringElement("item", new NewsEntry + { Category = "no_category", Summary = "test summary", - Image = new NewsImage { + Image = new NewsImage + { Hash = "4947269c5f7061b27225611ee58a9a91a8031bbe", Alignment = "right", }, @@ -21,7 +33,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers { Text = "Test Text", Date = 1348755214000, }.Serialize()); - + return this.Ok(LbpSerializer.StringElement("news", newsEntry)); } } diff --git a/ProjectLighthouse/Controllers/ResourcesController.cs b/ProjectLighthouse/Controllers/ResourcesController.cs index 7b80c591..c7726935 100644 --- a/ProjectLighthouse/Controllers/ResourcesController.cs +++ b/ProjectLighthouse/Controllers/ResourcesController.cs @@ -10,60 +10,68 @@ using LBPUnion.ProjectLighthouse.Types.Files; using Microsoft.AspNetCore.Mvc; using IOFile = System.IO.File; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] [Produces("text/xml")] - public class ResourcesController : ControllerBase { + public class ResourcesController : ControllerBase + { [HttpPost("showModerated")] - public IActionResult ShowModerated() { + public IActionResult ShowModerated() + { return this.Ok(LbpSerializer.BlankElement("resources")); } [HttpPost("filterResources")] [HttpPost("showNotUploaded")] - public async Task FilterResources() { - string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); - - XmlSerializer serializer = new(typeof(ResourceList)); - ResourceList resourceList = (ResourceList)serializer.Deserialize(new StringReader(bodyString)); + public async Task FilterResources() + { + string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); - if(resourceList == null) return this.BadRequest(); + XmlSerializer serializer = new(typeof(ResourceList)); + ResourceList resourceList = (ResourceList) serializer.Deserialize(new StringReader(bodyString)); + + if (resourceList == null) return this.BadRequest(); string resources = resourceList.Resources .Where(s => !FileHelper.ResourceExists(s)) - .Aggregate("", (current, hash) => + .Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash)); return this.Ok(LbpSerializer.StringElement("resources", resources)); } [HttpGet("r/{hash}")] - public IActionResult GetResource(string hash) { + public IActionResult GetResource(string hash) + { string path = FileHelper.GetResourcePath(hash); - if(FileHelper.ResourceExists(hash)) { + if (FileHelper.ResourceExists(hash)) + { return this.File(IOFile.OpenRead(path), "application/octet-stream"); } + return this.NotFound(); } // TODO: check if this is a valid hash [HttpPost("upload/{hash}")] [AllowSynchronousIo] - public async Task UploadResource(string hash) { + public async Task UploadResource(string hash) + { string assetsDirectory = FileHelper.ResourcePath; string path = FileHelper.GetResourcePath(hash); - + FileHelper.EnsureDirectoryCreated(assetsDirectory); - if(FileHelper.ResourceExists(hash)) this.Ok(); // no reason to fail if it's already uploaded + if (FileHelper.ResourceExists(hash)) this.Ok(); // no reason to fail if it's already uploaded Logger.Log($"Processing resource upload (hash: {hash})"); LbpFile file = new(await BinaryHelper.ReadFromPipeReader(Request.BodyReader)); - if(!FileHelper.IsFileSafe(file)) return this.UnprocessableEntity(); - + if (!FileHelper.IsFileSafe(file)) return this.UnprocessableEntity(); + await IOFile.WriteAllBytesAsync(path, file.Data); return this.Ok(); } diff --git a/ProjectLighthouse/Controllers/UserController.cs b/ProjectLighthouse/Controllers/UserController.cs index 1c69c039..6fd0bac8 100644 --- a/ProjectLighthouse/Controllers/UserController.cs +++ b/ProjectLighthouse/Controllers/UserController.cs @@ -8,26 +8,37 @@ using LBPUnion.ProjectLighthouse.Types.Profiles; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] [Produces("text/xml")] - public class UserController : ControllerBase { + public class UserController : ControllerBase + { private readonly Database database; - public UserController(Database database) { + + public UserController(Database database) + { this.database = database; } [HttpGet("user/{username}")] - public async Task GetUser(string username) { + public async Task GetUser(string username) + { User user = await this.database.Users .Include(u => u.Location) .FirstOrDefaultAsync(u => u.Username == username); - if(user == null) return this.NotFound(); + if (user == null) return this.NotFound(); return this.Ok(user.Serialize()); } + [HttpGet("user/{username}/playlists")] + public async Task GetUserPlaylists(string username) + { + return this.Ok(); + } + // [HttpPost("user/{username}")] // public async Task CreateUser(string username) { // await new Database().CreateUser(username); @@ -35,12 +46,14 @@ namespace LBPUnion.ProjectLighthouse.Controllers { // } [HttpPost("updateUser")] - public async Task UpdateUser() { + public async Task UpdateUser() + { User user = await this.database.UserFromRequest(this.Request); - if(user == null) return this.StatusCode(403, ""); + if (user == null) return this.StatusCode(403, ""); - XmlReaderSettings settings = new() { + XmlReaderSettings settings = new() + { Async = true, // this is apparently not default }; @@ -62,39 +75,57 @@ namespace LBPUnion.ProjectLighthouse.Controllers { // // // if you find a way to make it not stupid feel free to replace this - using(XmlReader reader = XmlReader.Create(this.Request.Body, settings)) { - List path = new(); // you can think of this as a file path in the XML, like -> -> - while(await reader.ReadAsync()) { + using (XmlReader reader = XmlReader.Create(this.Request.Body, settings)) + { + List + path = new(); // you can think of this as a file path in the XML, like -> -> + while (await reader.ReadAsync()) + { // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault - switch(reader.NodeType) { + switch (reader.NodeType) + { case XmlNodeType.Element: path.Add(reader.Name); break; case XmlNodeType.Text: - switch(path[1]) { - case "biography": { + switch (path[1]) + { + case "biography": + { user.Biography = await reader.GetValueAsync(); break; } - case "location": { - locationChanged = true; // if we're here then we're probably about to change the location. + case "location": + { + locationChanged = + true; // if we're here then we're probably about to change the location. // ReSharper disable once ConvertIfStatementToSwitchStatement - if(path[2] == "x") { - user.Location.X = Convert.ToInt32(await reader.GetValueAsync()); // GetValue only returns a string, i guess we just hope its a number lol - } else if(path[2] == "y") { + if (path[2] == "x") + { + user.Location.X = + Convert.ToInt32( + await reader + .GetValueAsync()); // GetValue only returns a string, i guess we just hope its a number lol + } + else if (path[2] == "y") + { user.Location.Y = Convert.ToInt32(await reader.GetValueAsync()); } + break; } - case "icon": { + case "icon": + { user.IconHash = await reader.GetValueAsync(); break; } - case "planets": { + case "planets": + { user.PlanetHash = await reader.GetValueAsync(); break; } } + break; case XmlNodeType.EndElement: path.RemoveAt(path.Count - 1); @@ -102,19 +133,23 @@ namespace LBPUnion.ProjectLighthouse.Controllers { } } } - + // the way location on a user card works is stupid and will not save with the way below as-is, so we do the following: - if(locationChanged) { // only modify the database if we modify here - Location l = await this.database.Locations.Where(l => l.Id == user.LocationId).FirstOrDefaultAsync(); // find the location in the database again + if (locationChanged) + { + // only modify the database if we modify here + Location l = await this.database.Locations.Where(l => l.Id == user.LocationId) + .FirstOrDefaultAsync(); // find the location in the database again // set the location in the database to the one we modified above l.X = user.Location.X; l.Y = user.Location.Y; - + // now both are in sync, and will update in the database. } - - if(this.database.ChangeTracker.HasChanges()) await this.database.SaveChangesAsync(); // save the user to the database if we changed anything + + if (this.database.ChangeTracker.HasChanges()) + await this.database.SaveChangesAsync(); // save the user to the database if we changed anything return this.Ok(); } } diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 4dd23287..2bb4714c 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -8,8 +8,10 @@ using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse { - public class Database : DbContext { +namespace LBPUnion.ProjectLighthouse +{ + public class Database : DbContext + { public DbSet Users { get; set; } public DbSet Locations { get; set; } public DbSet Slots { get; set; } @@ -26,16 +28,18 @@ namespace LBPUnion.ProjectLighthouse { MySqlServerVersion.LatestSupportedServerVersion ); - public async Task CreateUser(string username) { + public async Task CreateUser(string username) + { User user; - if((user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync()) != null) + if ((user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync()) != null) return user; Location l = new(); // store to get id after submitting this.Locations.Add(l); // add to table await this.SaveChangesAsync(); // saving to the database returns the id and sets it on this entity - user = new User { + user = new User + { Username = username, LocationId = l.Id, Biography = username + " hasn't introduced themselves yet.", @@ -46,14 +50,16 @@ namespace LBPUnion.ProjectLighthouse { return user; } - - #nullable enable - public async Task AuthenticateUser(LoginData loginData) { + +#nullable enable + public async Task AuthenticateUser(LoginData loginData) + { // TODO: don't use psn name to authenticate - User user = await this.Users.FirstOrDefaultAsync(u => u.Username == loginData.Username) + User user = await this.Users.FirstOrDefaultAsync(u => u.Username == loginData.Username) ?? await this.CreateUser(loginData.Username); - Token token = new() { + Token token = new() + { UserToken = HashHelper.GenerateAuthToken(), UserId = user.UserId, }; @@ -64,21 +70,24 @@ namespace LBPUnion.ProjectLighthouse { return token; } - public async Task UserFromAuthToken(string authToken) { + public async Task UserFromAuthToken(string authToken) + { Token? token = await this.Tokens.FirstOrDefaultAsync(t => t.UserToken == authToken); - if(token == null) return null; + if (token == null) return null; return await this.Users .Include(u => u.Location) .FirstOrDefaultAsync(u => u.UserId == token.UserId); } - public async Task UserFromRequest(HttpRequest request) { - if(!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) { + public async Task UserFromRequest(HttpRequest request) + { + if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) + { return null; } - + return await this.UserFromAuthToken(mmAuth); } - #nullable disable +#nullable disable } } \ No newline at end of file diff --git a/ProjectLighthouse/DigestUtils.cs b/ProjectLighthouse/DigestUtils.cs new file mode 100644 index 00000000..392054d2 --- /dev/null +++ b/ProjectLighthouse/DigestUtils.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace LBPUnion.ProjectLighthouse +{ + public static class DigestUtils + { + public static async Task ComputeDigest(string path, string authCookie, Stream body, + string digestKey) + { + var memoryStream = new MemoryStream(); + + var pathBytes = Encoding.UTF8.GetBytes(path); + var cookieBytes = string.IsNullOrEmpty(authCookie) + ? Array.Empty() + : Encoding.UTF8.GetBytes(authCookie); + var keyBytes = Encoding.UTF8.GetBytes(digestKey); + + await body.CopyToAsync(memoryStream); + + var bodyBytes = memoryStream.ToArray(); + + using var sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); + sha1.AppendData(bodyBytes); + if (cookieBytes.Length > 0) + sha1.AppendData(cookieBytes); + sha1.AppendData(pathBytes); + sha1.AppendData(keyBytes); + + var digestBytes = sha1.GetHashAndReset(); + var digestString = Convert.ToHexString(digestBytes).ToLower(); + + return digestString; + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Logging/LighthouseFileLogger.cs b/ProjectLighthouse/Logging/LighthouseFileLogger.cs index 98cb4d60..1305986f 100644 --- a/ProjectLighthouse/Logging/LighthouseFileLogger.cs +++ b/ProjectLighthouse/Logging/LighthouseFileLogger.cs @@ -7,7 +7,9 @@ namespace LBPUnion.ProjectLighthouse.Logging { public class LighthouseFileLogger : LoggerBase { private static readonly string logsDirectory = Path.Combine(Environment.CurrentDirectory, "logs"); - public override void Send(LoggerLine line) { + public override void Send(LoggerLine line) + { + return; FileHelper.EnsureDirectoryCreated(logsDirectory); File.AppendAllText(Path.Combine(logsDirectory, line.LoggerLevel + ".log"), line.LineData + "\n"); diff --git a/ProjectLighthouse/Program.cs b/ProjectLighthouse/Program.cs index 8b2a6bf2..c9a49d9f 100644 --- a/ProjectLighthouse/Program.cs +++ b/ProjectLighthouse/Program.cs @@ -10,51 +10,59 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace LBPUnion.ProjectLighthouse { - public static class Program { - public static void Main(string[] args) { +namespace LBPUnion.ProjectLighthouse +{ + public static class Program + { + public static void Main(string[] args) + { // Log startup time Stopwatch startupStopwatch = new(); startupStopwatch.Start(); - + // Setup logging - + Logger.StartLogging(); LoggerLine.LogFormat = "[{0}] {1}"; Logger.AddLogger(new ConsoleLogger()); Logger.AddLogger(new LighthouseFileLogger()); - + Logger.Log("Welcome to Project Lighthouse!", LoggerLevelStartup.Instance); Logger.Log("Determining if the database is available...", LoggerLevelStartup.Instance); bool dbConnected = ServerSettings.DbConnected; - Logger.Log(dbConnected ? "Connected to the database." : "Database unavailable! Exiting.", LoggerLevelStartup.Instance); + Logger.Log(dbConnected ? "Connected to the database." : "Database unavailable! Exiting.", + LoggerLevelStartup.Instance); - if(dbConnected) { + if (dbConnected) + { Stopwatch migrationStopwatch = new(); migrationStopwatch.Start(); - + Logger.Log("Migrating database...", LoggerLevelDatabase.Instance); using Database database = new(); database.Database.Migrate(); - + migrationStopwatch.Stop(); Logger.Log($"Migration took {migrationStopwatch.ElapsedMilliseconds}ms.", LoggerLevelDatabase.Instance); - } else Environment.Exit(1); - + } + else Environment.Exit(1); + startupStopwatch.Stop(); - Logger.Log($"Ready! Startup took {startupStopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LoggerLevelStartup.Instance); + Logger.Log( + $"Ready! Startup took {startupStopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", + LoggerLevelStartup.Instance); CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => { - webBuilder.UseStartup(); - }) - .ConfigureLogging(logging => { + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }) + .ConfigureLogging(logging => + { logging.ClearProviders(); - logging.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); + logging.Services.TryAddEnumerable(ServiceDescriptor + .Singleton()); }); } } \ No newline at end of file diff --git a/ProjectLighthouse/Startup.cs b/ProjectLighthouse/Startup.cs index d83ff302..ec15f68f 100644 --- a/ProjectLighthouse/Startup.cs +++ b/ProjectLighthouse/Startup.cs @@ -1,5 +1,8 @@ +using System; using System.Diagnostics; using System.IO; +using System.Reflection.Metadata.Ecma335; +using System.Threading.Tasks; using Kettu; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Serialization; @@ -9,48 +12,115 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; -namespace LBPUnion.ProjectLighthouse { - public class Startup { - public Startup(IConfiguration configuration) { +namespace LBPUnion.ProjectLighthouse +{ + public class Startup + { + public Startup(IConfiguration configuration) + { this.Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) { + public void ConfigureServices(IServiceCollection services) + { services.AddControllers(); services.AddMvc(options => options.OutputFormatters.Add(new XmlOutputFormatter())); - + services.AddDbContext(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - if(env.IsDevelopment()) { + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + var computeDigests = true; + var serverDigestKey = Environment.GetEnvironmentVariable("SERVER_DIGEST_KEY"); + if (string.IsNullOrWhiteSpace(serverDigestKey)) + { + Logger.Log( + "SERVER_DIGEST_KEY environment variable wasn't set, so server digest headers won't be set. This will break LBP 1 and LBP 3." + ); + computeDigests = false; + } + + if (env.IsDevelopment()) + { app.UseDeveloperExceptionPage(); } // Logs every request and the response to it // Example: "200, 13ms: GET /LITTLEBIGPLANETPS3_XML/news" // Example: "404, 127ms: GET /asdasd?query=osucookiezi727ppbluezenithtopplayhdhr" - app.Use(async (context, next) => { + app.Use(async (context, next) => + { Stopwatch requestStopwatch = new(); requestStopwatch.Start(); - + context.Request.EnableBuffering(); // Allows us to reset the position of Request.Body for later logging + + // Client digest check. + var authCookie = null as string; + if (!context.Request.Cookies.TryGetValue("MM_AUTH", out authCookie)) + authCookie = string.Empty; + if (context.Request.Headers.TryGetValue("X-Digest-A", out var clientDigest)) + { + var digestPath = context.Request.Path; + var body = context.Request.Body; + + var digest = await DigestUtils.ComputeDigest(digestPath, authCookie, body, serverDigestKey); + + if (digest != clientDigest) + { + Logger.Log($"Client digest {clientDigest} does not match server digest {digest}."); + context.Abort(); + return; + } + else + { + context.Response.Headers.Add("X-Digest-B", digest); + context.Request.Body.Position = 0; + } + } + + // This does the same as above, but for the response stream. + using var responseBuffer = new MemoryStream(); + var oldResponseStream = context.Response.Body; + context.Response.Body = responseBuffer; + await next(); // Handle the request so we can get the status code from it + + // Compute the server digest hash. + if (computeDigests && context.Request.Headers.TryGetValue("X-Digest-A", out var a)) + { + responseBuffer.Position = 0; + + // Compute the digest for the response. + var serverDigest = await DigestUtils.ComputeDigest(context.Request.Path, authCookie, + responseBuffer, serverDigestKey); + context.Response.Headers.Add("X-Digest-A", serverDigest); + } + + // Copy the buffered response to the actual respose stream. + responseBuffer.Position = 0; + await responseBuffer.CopyToAsync(oldResponseStream); + + context.Response.Body = oldResponseStream; + requestStopwatch.Stop(); - + Logger.Log( $"{context.Response.StatusCode}, {requestStopwatch.ElapsedMilliseconds}ms: {context.Request.Method} {context.Request.Path}{context.Request.QueryString}", LoggerLevelHttp.Instance ); - - if(context.Request.Method == "POST") { + + if (context.Request.Method == "POST") + { context.Request.Body.Position = 0; Logger.Log(await new StreamReader(context.Request.Body).ReadToEndAsync(), LoggerLevelHttp.Instance); } @@ -58,9 +128,7 @@ namespace LBPUnion.ProjectLighthouse { app.UseRouting(); - app.UseEndpoints(endpoints => { - endpoints.MapControllers(); - }); + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } } \ No newline at end of file From 37abf75071ca87cc0f91a85bde521977e988ea0a Mon Sep 17 00:00:00 2001 From: Michael VanOverbeek Date: Sat, 30 Oct 2021 20:54:33 -0400 Subject: [PATCH 02/13] Add Server Digest support. Note that the server digest key must be added to an environment variable. --- .../ClientConfigurationController.cs | 4 +- .../Controllers/MessageController.cs | 12 ++++-- .../Controllers/StoreController.cs | 17 +++++++++ ProjectLighthouse/Helpers/MatchHelper.cs | 20 ++++++---- ProjectLighthouse/Startup.cs | 37 +++++++++++-------- 5 files changed, 63 insertions(+), 27 deletions(-) create mode 100644 ProjectLighthouse/Controllers/StoreController.cs diff --git a/ProjectLighthouse/Controllers/ClientConfigurationController.cs b/ProjectLighthouse/Controllers/ClientConfigurationController.cs index 94010d42..dc3d90a7 100644 --- a/ProjectLighthouse/Controllers/ClientConfigurationController.cs +++ b/ProjectLighthouse/Controllers/ClientConfigurationController.cs @@ -13,8 +13,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers [SuppressMessage("ReSharper", "StringLiteralTypo")] public IActionResult NetworkSettings() { + var hostname = this.Request.Host; return this.Ok( - "ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels true\nAllowModeratedPoppetItems true\nShowLevelBoos true\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 120.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 30.0\nTIMEOUT_DIVE_IN_TOTAL 1000000.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nCDNHostName localhost\nTelemetryServer localhost\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false"); + "ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels true\nAllowModeratedPoppetItems true\nShowLevelBoos true\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 120.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 30.0\nTIMEOUT_DIVE_IN_TOTAL 1000000.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nCDNHostName localhost\nTelemetryServer localhost\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false\n" + + $"TelemetryServer {hostname}\nCDNHostName {hostname}"); } [HttpGet("t_conf")] diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index eba2834a..f3125d13 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -7,7 +7,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers { [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] - [Produces("text/plain")] + // [Produces("text/plain")] public class MessageController : ControllerBase { private readonly Database database; @@ -22,15 +22,19 @@ namespace LBPUnion.ProjectLighthouse.Controllers { User user = await this.database.UserFromRequest(this.Request); return user == null - ? this.Ok("You aren't logged in, but you're connected to a private LBP server.") + ? this.Forbid() : this.Ok($"You are now logged in as user {user.Username} (id {user.UserId}).\n" + "This is a private testing instance. Please do not make anything public for now, and keep in mind security isn't as tight as a full release would."); } [HttpGet("announce")] - public IActionResult Announce() + public async Task Announce() { - return this.Ok(""); + User user = await this.database.UserFromRequest(this.Request); + return user == null + ? this.Forbid() + : this.Ok($"You are now logged in as user {user.Username} (id {user.UserId}).\n" + + "This is a private testing instance. Please do not make anything public for now, and keep in mind security isn't as tight as a full release would."); } [HttpGet("notification")] diff --git a/ProjectLighthouse/Controllers/StoreController.cs b/ProjectLighthouse/Controllers/StoreController.cs new file mode 100644 index 00000000..f3977a7a --- /dev/null +++ b/ProjectLighthouse/Controllers/StoreController.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace LBPUnion.ProjectLighthouse.Controllers +{ + [ApiController] + [Route("LITTLEBIGPLANETPS3_XML/")] + [Produces("text/xml")] + public class StoreController : Controller + { + [HttpGet("promotions")] + public async Task Promotions() + { + return Ok(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/MatchHelper.cs b/ProjectLighthouse/Helpers/MatchHelper.cs index 22d34ad0..e7e66895 100644 --- a/ProjectLighthouse/Helpers/MatchHelper.cs +++ b/ProjectLighthouse/Helpers/MatchHelper.cs @@ -3,14 +3,18 @@ using System.Linq; using System.Text.Json; using LBPUnion.ProjectLighthouse.Types.Match; -namespace LBPUnion.ProjectLighthouse.Helpers { - public static class MatchHelper { - public static IMatchData? Deserialize(string data) { +namespace LBPUnion.ProjectLighthouse.Helpers +{ + public static class MatchHelper + { + public static IMatchData? Deserialize(string data) + { string matchType = ""; int i = 1; - while(true) { - if(data[i] == ',') break; + while (true) + { + if (data[i] == ',') break; matchType += data[i]; i++; @@ -21,8 +25,10 @@ namespace LBPUnion.ProjectLighthouse.Helpers { return Deserialize(matchType, matchData); } - public static IMatchData? Deserialize(string matchType, string matchData) { - return matchType switch { + public static IMatchData? Deserialize(string matchType, string matchData) + { + return matchType switch + { "UpdateMyPlayerData" => JsonSerializer.Deserialize(matchData), "UpdatePlayersInRoom" => JsonSerializer.Deserialize(matchData), _ => null, diff --git a/ProjectLighthouse/Startup.cs b/ProjectLighthouse/Startup.cs index ec15f68f..d3210b67 100644 --- a/ProjectLighthouse/Startup.cs +++ b/ProjectLighthouse/Startup.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using System.IO; using System.Reflection.Metadata.Ecma335; +using System.Runtime.InteropServices.ComTypes; using System.Threading.Tasks; using Kettu; using LBPUnion.ProjectLighthouse.Logging; @@ -61,33 +62,36 @@ namespace LBPUnion.ProjectLighthouse Stopwatch requestStopwatch = new(); requestStopwatch.Start(); + // Log all headers. + foreach (var header in context.Request.Headers) + Logger.Log($"{header.Key}: {header.Value}"); + context.Request.EnableBuffering(); // Allows us to reset the position of Request.Body for later logging // Client digest check. var authCookie = null as string; if (!context.Request.Cookies.TryGetValue("MM_AUTH", out authCookie)) authCookie = string.Empty; - if (context.Request.Headers.TryGetValue("X-Digest-A", out var clientDigest)) + var digestPath = context.Request.Path; + var body = context.Request.Body; + + var clientRequestDigest = await DigestUtils.ComputeDigest(digestPath, authCookie, body, serverDigestKey); + + // Check the digest we've just calculated against the X-Digest-A header if the game set the header. They should match. + if (context.Request.Headers.TryGetValue("X-Digest-A", out var sentDigest)) { - var digestPath = context.Request.Path; - var body = context.Request.Body; - - var digest = await DigestUtils.ComputeDigest(digestPath, authCookie, body, serverDigestKey); - - if (digest != clientDigest) + if (clientRequestDigest != sentDigest) { - Logger.Log($"Client digest {clientDigest} does not match server digest {digest}."); + context.Response.StatusCode = 403; context.Abort(); return; } - else - { - context.Response.Headers.Add("X-Digest-B", digest); - context.Request.Body.Position = 0; - } } - // This does the same as above, but for the response stream. + context.Response.Headers.Add("X-Digest-B", clientRequestDigest); + context.Request.Body.Position = 0; + + // This does the same as above, but for the response stream. using var responseBuffer = new MemoryStream(); var oldResponseStream = context.Response.Body; context.Response.Body = responseBuffer; @@ -95,7 +99,7 @@ namespace LBPUnion.ProjectLighthouse await next(); // Handle the request so we can get the status code from it // Compute the server digest hash. - if (computeDigests && context.Request.Headers.TryGetValue("X-Digest-A", out var a)) + if (computeDigests) { responseBuffer.Position = 0; @@ -105,6 +109,9 @@ namespace LBPUnion.ProjectLighthouse context.Response.Headers.Add("X-Digest-A", serverDigest); } + // Set the X-Original-Content-Length header to the length of the response buffer. + context.Response.Headers.Add("X-Original-Content-Length", responseBuffer.Length.ToString()); + // Copy the buffered response to the actual respose stream. responseBuffer.Position = 0; From ec43fd9ed79cd0bfba7db7428e7c6e654c352c09 Mon Sep 17 00:00:00 2001 From: Michael VanOverbeek Date: Sun, 31 Oct 2021 13:29:41 -0400 Subject: [PATCH 03/13] Move /developer_videos to a DeveloperController because it shouldn't be under /news. --- .../Controllers/DeveloperController.cs | 16 ++++++++++++++++ ProjectLighthouse/Controllers/NewsController.cs | 6 ------ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 ProjectLighthouse/Controllers/DeveloperController.cs diff --git a/ProjectLighthouse/Controllers/DeveloperController.cs b/ProjectLighthouse/Controllers/DeveloperController.cs new file mode 100644 index 00000000..223ad66a --- /dev/null +++ b/ProjectLighthouse/Controllers/DeveloperController.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace LBPUnion.ProjectLighthouse.Controllers +{ + [ApiController] + [Route("LITTLEBIGPLANETPS3_XML/")] + public class DeveloperController : Controller + { + [HttpGet("/developer_videos")] + public IActionResult DeveloperVideos() + { + return this.Ok(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/NewsController.cs b/ProjectLighthouse/Controllers/NewsController.cs index 10a29254..a3f118a3 100644 --- a/ProjectLighthouse/Controllers/NewsController.cs +++ b/ProjectLighthouse/Controllers/NewsController.cs @@ -10,12 +10,6 @@ namespace LBPUnion.ProjectLighthouse.Controllers [Produces("text/xml")] public class NewsController : ControllerBase { - [HttpGet("/developer_videos")] - public async Task DeveloperVideos() - { - return Ok(); - } - [HttpGet] public IActionResult Get() { From 9fd517009210b1ed7363560950ee7e3d49c73772 Mon Sep 17 00:00:00 2001 From: Michael VanOverbeek Date: Sun, 31 Oct 2021 13:32:58 -0400 Subject: [PATCH 04/13] Apply suggested change for /promotions - remove async qualifier --- ProjectLighthouse/Controllers/StoreController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse/Controllers/StoreController.cs b/ProjectLighthouse/Controllers/StoreController.cs index f3977a7a..1fe7862b 100644 --- a/ProjectLighthouse/Controllers/StoreController.cs +++ b/ProjectLighthouse/Controllers/StoreController.cs @@ -9,7 +9,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers public class StoreController : Controller { [HttpGet("promotions")] - public async Task Promotions() + public IActionResult Promotions() { return Ok(); } From 829967776747e81fcf98d328027105182202257d Mon Sep 17 00:00:00 2001 From: Michael VanOverbeek Date: Sun, 31 Oct 2021 13:37:34 -0400 Subject: [PATCH 05/13] Apply more suggested formatting changes --- ProjectLighthouse/Controllers/UserController.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse/Controllers/UserController.cs b/ProjectLighthouse/Controllers/UserController.cs index 6fd0bac8..666f7d87 100644 --- a/ProjectLighthouse/Controllers/UserController.cs +++ b/ProjectLighthouse/Controllers/UserController.cs @@ -34,7 +34,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers } [HttpGet("user/{username}/playlists")] - public async Task GetUserPlaylists(string username) + public IActionResult GetUserPlaylists(string username) { return this.Ok(); } @@ -135,9 +135,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers } // the way location on a user card works is stupid and will not save with the way below as-is, so we do the following: - if (locationChanged) + if (locationChanged) // only modify the database if we modify here { - // only modify the database if we modify here Location l = await this.database.Locations.Where(l => l.Id == user.LocationId) .FirstOrDefaultAsync(); // find the location in the database again From dc92cc0319f10b169f2a79ce3618548553b0c4e0 Mon Sep 17 00:00:00 2001 From: Michael VanOverbeek Date: Sun, 31 Oct 2021 13:40:59 -0400 Subject: [PATCH 06/13] Remove that return statement I forgot to remove. --- ProjectLighthouse/Logging/LighthouseFileLogger.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ProjectLighthouse/Logging/LighthouseFileLogger.cs b/ProjectLighthouse/Logging/LighthouseFileLogger.cs index 1305986f..82863a72 100644 --- a/ProjectLighthouse/Logging/LighthouseFileLogger.cs +++ b/ProjectLighthouse/Logging/LighthouseFileLogger.cs @@ -9,7 +9,6 @@ namespace LBPUnion.ProjectLighthouse.Logging { public override void Send(LoggerLine line) { - return; FileHelper.EnsureDirectoryCreated(logsDirectory); File.AppendAllText(Path.Combine(logsDirectory, line.LoggerLevel + ".log"), line.LineData + "\n"); From a2b6908c07d1857839d4bb4a07956220da90b982 Mon Sep 17 00:00:00 2001 From: Michael VanOverbeek Date: Sun, 31 Oct 2021 13:44:52 -0400 Subject: [PATCH 07/13] Move ComputeDigest to HashHelper --- ProjectLighthouse/DigestUtils.cs | 39 ------------------------- ProjectLighthouse/Helpers/HashHelper.cs | 30 +++++++++++++++++++ ProjectLighthouse/Startup.cs | 5 ++-- 3 files changed, 33 insertions(+), 41 deletions(-) delete mode 100644 ProjectLighthouse/DigestUtils.cs diff --git a/ProjectLighthouse/DigestUtils.cs b/ProjectLighthouse/DigestUtils.cs deleted file mode 100644 index 392054d2..00000000 --- a/ProjectLighthouse/DigestUtils.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; - -namespace LBPUnion.ProjectLighthouse -{ - public static class DigestUtils - { - public static async Task ComputeDigest(string path, string authCookie, Stream body, - string digestKey) - { - var memoryStream = new MemoryStream(); - - var pathBytes = Encoding.UTF8.GetBytes(path); - var cookieBytes = string.IsNullOrEmpty(authCookie) - ? Array.Empty() - : Encoding.UTF8.GetBytes(authCookie); - var keyBytes = Encoding.UTF8.GetBytes(digestKey); - - await body.CopyToAsync(memoryStream); - - var bodyBytes = memoryStream.ToArray(); - - using var sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); - sha1.AppendData(bodyBytes); - if (cookieBytes.Length > 0) - sha1.AppendData(cookieBytes); - sha1.AppendData(pathBytes); - sha1.AppendData(keyBytes); - - var digestBytes = sha1.GetHashAndReset(); - var digestString = Convert.ToHexString(digestBytes).ToLower(); - - return digestString; - } - } -} \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/HashHelper.cs b/ProjectLighthouse/Helpers/HashHelper.cs index b2ea531e..72c7724a 100644 --- a/ProjectLighthouse/Helpers/HashHelper.cs +++ b/ProjectLighthouse/Helpers/HashHelper.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Security.Cryptography; using System.Text; +using System.Threading.Tasks; namespace LBPUnion.ProjectLighthouse.Helpers { [SuppressMessage("ReSharper", "UnusedMember.Global")] @@ -45,5 +47,33 @@ namespace LBPUnion.ProjectLighthouse.Helpers { return BCryptHash(Sha256Hash(bytes)); } + + public static async Task ComputeDigest(string path, string authCookie, Stream body, + string digestKey) + { + var memoryStream = new MemoryStream(); + + var pathBytes = Encoding.UTF8.GetBytes(path); + var cookieBytes = string.IsNullOrEmpty(authCookie) + ? Array.Empty() + : Encoding.UTF8.GetBytes(authCookie); + var keyBytes = Encoding.UTF8.GetBytes(digestKey); + + await body.CopyToAsync(memoryStream); + + var bodyBytes = memoryStream.ToArray(); + + using var sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); + sha1.AppendData(bodyBytes); + if (cookieBytes.Length > 0) + sha1.AppendData(cookieBytes); + sha1.AppendData(pathBytes); + sha1.AppendData(keyBytes); + + var digestBytes = sha1.GetHashAndReset(); + var digestString = Convert.ToHexString(digestBytes).ToLower(); + + return digestString; + } } } \ No newline at end of file diff --git a/ProjectLighthouse/Startup.cs b/ProjectLighthouse/Startup.cs index d3210b67..9ab07443 100644 --- a/ProjectLighthouse/Startup.cs +++ b/ProjectLighthouse/Startup.cs @@ -5,6 +5,7 @@ using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices.ComTypes; using System.Threading.Tasks; using Kettu; +using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Serialization; using Microsoft.AspNetCore.Builder; @@ -75,7 +76,7 @@ namespace LBPUnion.ProjectLighthouse var digestPath = context.Request.Path; var body = context.Request.Body; - var clientRequestDigest = await DigestUtils.ComputeDigest(digestPath, authCookie, body, serverDigestKey); + var clientRequestDigest = await HashHelper.ComputeDigest(digestPath, authCookie, body, serverDigestKey); // Check the digest we've just calculated against the X-Digest-A header if the game set the header. They should match. if (context.Request.Headers.TryGetValue("X-Digest-A", out var sentDigest)) @@ -104,7 +105,7 @@ namespace LBPUnion.ProjectLighthouse responseBuffer.Position = 0; // Compute the digest for the response. - var serverDigest = await DigestUtils.ComputeDigest(context.Request.Path, authCookie, + var serverDigest = await HashHelper.ComputeDigest(context.Request.Path, authCookie, responseBuffer, serverDigestKey); context.Response.Headers.Add("X-Digest-A", serverDigest); } From a903741276682d50860ccd599a8205d2aeab4394 Mon Sep 17 00:00:00 2001 From: Michael VanOverbeek Date: Sun, 31 Oct 2021 13:47:50 -0400 Subject: [PATCH 08/13] Go against Rider's wishes and not use var --- ProjectLighthouse/Helpers/HashHelper.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ProjectLighthouse/Helpers/HashHelper.cs b/ProjectLighthouse/Helpers/HashHelper.cs index 72c7724a..865a04f3 100644 --- a/ProjectLighthouse/Helpers/HashHelper.cs +++ b/ProjectLighthouse/Helpers/HashHelper.cs @@ -51,27 +51,27 @@ namespace LBPUnion.ProjectLighthouse.Helpers { public static async Task ComputeDigest(string path, string authCookie, Stream body, string digestKey) { - var memoryStream = new MemoryStream(); + MemoryStream memoryStream = new MemoryStream(); - var pathBytes = Encoding.UTF8.GetBytes(path); - var cookieBytes = string.IsNullOrEmpty(authCookie) + byte[] pathBytes = Encoding.UTF8.GetBytes(path); + byte[] cookieBytes = string.IsNullOrEmpty(authCookie) ? Array.Empty() : Encoding.UTF8.GetBytes(authCookie); - var keyBytes = Encoding.UTF8.GetBytes(digestKey); + byte[] keyBytes = Encoding.UTF8.GetBytes(digestKey); await body.CopyToAsync(memoryStream); - var bodyBytes = memoryStream.ToArray(); + byte[] bodyBytes = memoryStream.ToArray(); - using var sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); + using IncrementalHash sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); sha1.AppendData(bodyBytes); if (cookieBytes.Length > 0) sha1.AppendData(cookieBytes); sha1.AppendData(pathBytes); sha1.AppendData(keyBytes); - var digestBytes = sha1.GetHashAndReset(); - var digestString = Convert.ToHexString(digestBytes).ToLower(); + byte[] digestBytes = sha1.GetHashAndReset(); + string digestString = Convert.ToHexString(digestBytes).ToLower(); return digestString; } From 099ecc55689afb743c918bc8b5f8367f2b2585f2 Mon Sep 17 00:00:00 2001 From: Michael VanOverbeek Date: Sun, 31 Oct 2021 13:51:40 -0400 Subject: [PATCH 09/13] I have no idea how to describe this change, just read the diff --- ProjectLighthouse/Controllers/MessageController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index f3125d13..41c3c77f 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -7,7 +7,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers { [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] - // [Produces("text/plain")] + [Produces("text/plain")] public class MessageController : ControllerBase { private readonly Database database; From e6e80225663f61e1b285a4b60f56e045863a5c4e Mon Sep 17 00:00:00 2001 From: Michael VanOverbeek Date: Sun, 31 Oct 2021 13:55:51 -0400 Subject: [PATCH 10/13] Rider really hates me now. More explicit types. --- ProjectLighthouse/Startup.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ProjectLighthouse/Startup.cs b/ProjectLighthouse/Startup.cs index 9ab07443..6d0637a9 100644 --- a/ProjectLighthouse/Startup.cs +++ b/ProjectLighthouse/Startup.cs @@ -40,8 +40,8 @@ namespace LBPUnion.ProjectLighthouse // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - var computeDigests = true; - var serverDigestKey = Environment.GetEnvironmentVariable("SERVER_DIGEST_KEY"); + bool computeDigests = true; + string serverDigestKey = Environment.GetEnvironmentVariable("SERVER_DIGEST_KEY"); if (string.IsNullOrWhiteSpace(serverDigestKey)) { Logger.Log( @@ -70,13 +70,13 @@ namespace LBPUnion.ProjectLighthouse context.Request.EnableBuffering(); // Allows us to reset the position of Request.Body for later logging // Client digest check. - var authCookie = null as string; + string authCookie; if (!context.Request.Cookies.TryGetValue("MM_AUTH", out authCookie)) authCookie = string.Empty; - var digestPath = context.Request.Path; - var body = context.Request.Body; + string digestPath = context.Request.Path; + Stream body = context.Request.Body; - var clientRequestDigest = await HashHelper.ComputeDigest(digestPath, authCookie, body, serverDigestKey); + string clientRequestDigest = await HashHelper.ComputeDigest(digestPath, authCookie, body, serverDigestKey); // Check the digest we've just calculated against the X-Digest-A header if the game set the header. They should match. if (context.Request.Headers.TryGetValue("X-Digest-A", out var sentDigest)) @@ -93,8 +93,8 @@ namespace LBPUnion.ProjectLighthouse context.Request.Body.Position = 0; // This does the same as above, but for the response stream. - using var responseBuffer = new MemoryStream(); - var oldResponseStream = context.Response.Body; + using MemoryStream responseBuffer = new MemoryStream(); + Stream oldResponseStream = context.Response.Body; context.Response.Body = responseBuffer; await next(); // Handle the request so we can get the status code from it @@ -105,7 +105,7 @@ namespace LBPUnion.ProjectLighthouse responseBuffer.Position = 0; // Compute the digest for the response. - var serverDigest = await HashHelper.ComputeDigest(context.Request.Path, authCookie, + string serverDigest = await HashHelper.ComputeDigest(context.Request.Path, authCookie, responseBuffer, serverDigestKey); context.Response.Headers.Add("X-Digest-A", serverDigest); } From 76ed7a736dd22c747d1a6330e0c766c7cc3cb596 Mon Sep 17 00:00:00 2001 From: Michael VanOverbeek Date: Sun, 31 Oct 2021 13:59:23 -0400 Subject: [PATCH 11/13] Change the server digest warning message --- ProjectLighthouse/Startup.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ProjectLighthouse/Startup.cs b/ProjectLighthouse/Startup.cs index 6d0637a9..c122871f 100644 --- a/ProjectLighthouse/Startup.cs +++ b/ProjectLighthouse/Startup.cs @@ -45,7 +45,8 @@ namespace LBPUnion.ProjectLighthouse if (string.IsNullOrWhiteSpace(serverDigestKey)) { Logger.Log( - "SERVER_DIGEST_KEY environment variable wasn't set, so server digest headers won't be set. This will break LBP 1 and LBP 3." + "The SERVER_DIGEST_KEY environment variable wasn't set, so digest headers won't be set or verified. This will prevent LBP 1 and LBP 3 from working. " + + "To increase security, it is recommended that you find and set this variable." ); computeDigests = false; } From 874477008e17d19a7d235bef09315ebce6025bab Mon Sep 17 00:00:00 2001 From: Michael VanOverbeek Date: Sun, 31 Oct 2021 14:09:57 -0400 Subject: [PATCH 12/13] Fix issue where the server will 403 you if SERVER_DIGEST_KEY env var is unset. --- ProjectLighthouse/Startup.cs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/ProjectLighthouse/Startup.cs b/ProjectLighthouse/Startup.cs index c122871f..86058342 100644 --- a/ProjectLighthouse/Startup.cs +++ b/ProjectLighthouse/Startup.cs @@ -77,23 +77,27 @@ namespace LBPUnion.ProjectLighthouse string digestPath = context.Request.Path; Stream body = context.Request.Body; - string clientRequestDigest = await HashHelper.ComputeDigest(digestPath, authCookie, body, serverDigestKey); - - // Check the digest we've just calculated against the X-Digest-A header if the game set the header. They should match. - if (context.Request.Headers.TryGetValue("X-Digest-A", out var sentDigest)) + if (computeDigests) { - if (clientRequestDigest != sentDigest) + string clientRequestDigest = + await HashHelper.ComputeDigest(digestPath, authCookie, body, serverDigestKey); + + // Check the digest we've just calculated against the X-Digest-A header if the game set the header. They should match. + if (context.Request.Headers.TryGetValue("X-Digest-A", out var sentDigest)) { - context.Response.StatusCode = 403; - context.Abort(); - return; + if (clientRequestDigest != sentDigest) + { + context.Response.StatusCode = 403; + context.Abort(); + return; + } } + + context.Response.Headers.Add("X-Digest-B", clientRequestDigest); + context.Request.Body.Position = 0; } - context.Response.Headers.Add("X-Digest-B", clientRequestDigest); - context.Request.Body.Position = 0; - - // This does the same as above, but for the response stream. + // This does the same as above, but for the response stream. using MemoryStream responseBuffer = new MemoryStream(); Stream oldResponseStream = context.Response.Body; context.Response.Body = responseBuffer; From 97f27525eea84fc06b18400c622b698a0406a7f3 Mon Sep 17 00:00:00 2001 From: Michael VanOverbeek Date: Sun, 31 Oct 2021 14:11:39 -0400 Subject: [PATCH 13/13] More nit-picky formatting fixes --- ProjectLighthouse/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse/Startup.cs b/ProjectLighthouse/Startup.cs index 86058342..ebd94c55 100644 --- a/ProjectLighthouse/Startup.cs +++ b/ProjectLighthouse/Startup.cs @@ -141,7 +141,7 @@ namespace LBPUnion.ProjectLighthouse app.UseRouting(); - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + app.UseEndpoints(endpoints => endpoints.MapControllers()); } } } \ No newline at end of file