From 4d281dac5abe8f88b29d159505fe7b9f5da729e6 Mon Sep 17 00:00:00 2001 From: Malloc of Kuzkycyziklistan Date: Thu, 14 Sep 2017 14:14:07 -0500 Subject: [PATCH] once as a kid i cried over a mario flash animation my mom looked very disappointed --- protocol.md | 10 +--- server/Configuration.cs | 10 ++++ server/Libraries/Glove/RandomContext.cs | 1 - server/MasterServerList.cs | 6 ++ server/Socks/MasterConnection.cs | 74 ++++++++++++++++++------- server/Socks/MasterIntraServer.cs | 6 +- server/Socks/Protocols/Packet.cs | 35 ++++++++++-- 7 files changed, 105 insertions(+), 37 deletions(-) diff --git a/protocol.md b/protocol.md index eae0c43..a840520 100644 --- a/protocol.md +++ b/protocol.md @@ -287,24 +287,18 @@ Communication between the master server and clients will be done over a WebSocke 2 - Session Id - Packed Unsigned Long - R1 - - - 3 Secret Bytes (16) R1 - 4 + 3 Server Address IPv4 String R1 - 5 + 4 Server Port Packed Unsigned Short R1 diff --git a/server/Configuration.cs b/server/Configuration.cs index d77831e..2ea9cf3 100644 --- a/server/Configuration.cs +++ b/server/Configuration.cs @@ -26,6 +26,16 @@ namespace SockScape { } }, + new SectionRules { + Name = "Mail", + Required = true, + RequiredFields = new[] { + "Host", + "UseTLS", + "Auth" + } + }, + new SectionRules { Name = "Database", Required = true, diff --git a/server/Libraries/Glove/RandomContext.cs b/server/Libraries/Glove/RandomContext.cs index 2a7a8b1..d935a9c 100644 --- a/server/Libraries/Glove/RandomContext.cs +++ b/server/Libraries/Glove/RandomContext.cs @@ -8,7 +8,6 @@ using System.Security.Cryptography; namespace Glove { public static class RNG { - // TODO add cryptographically secure rng private static readonly Random RandCtx = new Random(); private static readonly RNGCryptoServiceProvider CsRandCtx = new RNGCryptoServiceProvider(); diff --git a/server/MasterServerList.cs b/server/MasterServerList.cs index 28af288..e186e69 100644 --- a/server/MasterServerList.cs +++ b/server/MasterServerList.cs @@ -21,6 +21,12 @@ namespace SockScape { } } + public static Server Get(ushort id) { + lock(_Servers) { + return _Servers[id]; + } + } + public static void Write(Server server) { lock(_Servers) { if(HasId(server.Id) && !_Servers[server.Id].Address.Equals(server.Address)) diff --git a/server/Socks/MasterConnection.cs b/server/Socks/MasterConnection.cs index a17b306..7ac2faf 100644 --- a/server/Socks/MasterConnection.cs +++ b/server/Socks/MasterConnection.cs @@ -13,8 +13,8 @@ using SockScape.Encryption; namespace SockScape { class MasterConnection : Connection { - private Regex UsernameRegex = new Regex("[A-Z0-9_]", RegexOptions.IgnoreCase); - private Regex EmailRegex = new Regex("\\B[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\\B", RegexOptions.IgnoreCase); + private readonly Regex UsernameRegex = new Regex("[A-Z0-9_]", RegexOptions.IgnoreCase); + private readonly Regex EmailRegex = new Regex(@"\B[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\B", RegexOptions.IgnoreCase); private Key Key; public StreamCipher Encryptor { get; private set; } @@ -55,9 +55,10 @@ namespace SockScape { Encryptor = new StreamCipher(Key.PrivateKey); break; case kInterMasterId.LoginAttempt: - if(packet.RegionCount != 3 || !packet.CheckRegions(2, 2)) + if(packet.RegionCount != 3 || !packet.CheckRegionsMaxLength(0, 16, 255) || !packet.CheckRegionLengths(2, 2)) break; + Session session; using(var db = new ScapeDb()) { User user; if((user = db.Users.FirstOrDefault(x => x.Username == packet[0])) == null) { @@ -75,8 +76,8 @@ namespace SockScape { break; } - ushort server = packet[2].Raw.UnpackUInt16(); - if(!MasterServerList.HasId(server)) { + ushort serverId = packet[2].Raw.UnpackUInt16(); + if(!MasterServerList.HasId(serverId)) { SendEncrypted(new Packet(kInterMasterId.LoginAttempt, Convert.ToByte(false), "The world you have specified is offline.")); break; } @@ -86,32 +87,65 @@ namespace SockScape { db.SaveChanges(); } - db.Sessions.Add(new Session { + db.Sessions.Add(session = new Session { Id = user.Id, Secret = RNG.NextBytes(16), - ServerId = server, + ServerId = serverId, LastPing = DateTime.UtcNow }); + db.SaveChanges(); } + + var server = MasterServerList.Get((ushort)session.ServerId); + SendEncrypted(new Packet(kInterMasterId.LoginAttempt, Convert.ToByte(true), session.Secret, server.Address.ToString(), server.Port.Pack())); break; case kInterMasterId.RegistrationAttempt: - if(packet.RegionCount != 3) + if(packet.RegionCount != 3 || !packet.CheckAllMaxLength(0xFF)) break; - using(var db = new ScapeDb()) { - if(!packet[0].Raw.IsAsciiString()) { - SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "Your username cannot contain unicode characters.")); - break; - } - - if(packet[0].Raw.Length > 16 || !UsernameRegex.IsMatch(packet[0].Str)) { - SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "The username is max 16 characters and can only be letters, numbers, and underscores.")); - break; - } - - + if(!packet[0].Raw.IsAsciiString()) { + SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "Your username cannot contain unicode characters.")); + break; } + + string username = packet[0].Str.Trim(), + password = packet[1].Str.Trim(), + email = packet[2].Str.Trim(); + + if(username.Length > 16 || !UsernameRegex.IsMatch(username)) { + SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "The username is max 16 characters and can only be letters, numbers, and underscores.")); + break; + } + + if(!EmailRegex.IsMatch(email)) { + SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "The email address is malformed.")); + break; + } + + using(var db = new ScapeDb()) { + if(db.Users.FirstOrDefault(x => x.Username == username) != null) { + SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "This username is already in use.")); + break; + } + + if(db.Users.FirstOrDefault(x => x.Email == email) != null) { + SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "This email address is already in use.")); + break; + } + + // TODO email activation + db.Users.Add(new User { + Username = username, + Password = password.HashPassword(), + Email = email, + Joined = DateTime.UtcNow + }); + + db.SaveChanges(); + } + + SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(true), "Registration was successful.")); break; case kInterMasterId.ServerListing: SendEncrypted(MasterServerList.ReportPacket); diff --git a/server/Socks/MasterIntraServer.cs b/server/Socks/MasterIntraServer.cs index d253134..6f795f1 100644 --- a/server/Socks/MasterIntraServer.cs +++ b/server/Socks/MasterIntraServer.cs @@ -102,7 +102,7 @@ namespace SockScape { if(!IsClientConnected(client) || packet.RegionCount < 1) break; - if(!packet.CheckRegions(0, 1)) { + if(!packet.CheckRegionLengths(0, 1)) { NegativeAck(endPoint, encryptor, kIntraSlaveId.StatusUpdate, "Server count is malformed."); break; } @@ -114,7 +114,7 @@ namespace SockScape { } for(byte i = 0; i < serverCount; ++i) { - if(!packet.CheckRegions(1 + 3 * i, 2, 2, 2)) + if(!packet.CheckRegionLengths(1 + 3 * i, 2, 2, 2)) continue; MasterServerList.Write(new Server { @@ -132,7 +132,7 @@ namespace SockScape { } Prospects = Prospects.Where(x => x.Value.ReceiveDelta.TotalSeconds < 10) - .ToDictionary(x => x.Key, x => x.Value); + .ToDictionary(x => x.Key, x => x.Value); var expiredClients = Clients.Where(x => x.Value.ReceiveDelta.TotalSeconds > 60).Select(x => x.Value).ToList(); if(expiredClients.Count > 0) diff --git a/server/Socks/Protocols/Packet.cs b/server/Socks/Protocols/Packet.cs index 80b375c..320bd99 100644 --- a/server/Socks/Protocols/Packet.cs +++ b/server/Socks/Protocols/Packet.cs @@ -50,7 +50,7 @@ namespace SockScape { long bodyPtr = headerPtr; foreach(var regionLength in regionLengths) { - // FLAG this could fail if one region exceeds 2^31-1 in size, check later + // FLAG TODO this could fail if one region exceeds 2^31-1 in size, check later packet.Regions.Add(raw.Subset((int)bodyPtr, (int)regionLength)); bodyPtr += regionLength; } @@ -103,7 +103,7 @@ namespace SockScape { return this; } - public bool CheckRegions(int startIndex, params int[] lengths) { + public bool CheckRegionLengths(int startIndex, params int[] lengths) { if(startIndex + lengths.Length > RegionCount) return false; @@ -115,6 +115,30 @@ namespace SockScape { return true; } + public bool CheckRegionsMaxLength(int startIndex, params int[] lengths) { + if(startIndex + lengths.Length > RegionCount) + return false; + + for(int i = 0; i < lengths.Length; ++i) { + if(this[startIndex + i].Raw.Length > lengths[i]) + return false; + } + + return true; + } + + public bool CheckAllMaxLength(int length, int startIndex = 0) { + if(startIndex > RegionCount) + return false; + + for(int i = startIndex; i < RegionCount; ++i) { + if(this[i].Raw.Length > length) + return false; + } + + return true; + } + public byte[] GetBytes() { var header = new List(); header.AddRange(MagicNumber); @@ -162,8 +186,9 @@ namespace SockScape { } } + // FLAG TODO determine if you still need this thing, probably not public class PacketBuffer { - private short MaxSize; + private readonly short MaxSize; private Dictionary Buffer = new Dictionary(); @@ -174,9 +199,9 @@ namespace SockScape { public void Add(uint id, byte[] packet) { Buffer[id] = packet; - if(Buffer.Count > 10) + if(Buffer.Count > MaxSize) Buffer = - Buffer.Where(x => x.Key >= id - 10) + Buffer.Where(x => x.Key >= id - MaxSize) .ToDictionary(x => x.Key, x => x.Value); }