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);
}