/// <summary> /// Main listner loop. Keeps an open stream between the client and server while /// the client is logged in / playing /// </summary> private void OnDataReceived(TCPStream stream, string message) { if (stream != Stream) { return; } // Read client message, and parse it into key value pairs string[] recieved = message.TrimStart('\\').Split('\\'); switch (recieved[0]) { case "inviteto": AddProducts(PresenceServer.ConvertToKeyValue(recieved)); break; case "newuser": CreateNewUser(PresenceServer.ConvertToKeyValue(recieved)); break; case "login": ProcessLogin(PresenceServer.ConvertToKeyValue(recieved)); break; case "getprofile": SendProfile(PresenceServer.ConvertToKeyValue(recieved)); break; case "updatepro": UpdateUser(PresenceServer.ConvertToKeyValue(recieved)); break; case "logout": Disconnect(DisconnectReason.NormalLogout); break; case "status": UpdateStatus(PresenceServer.ConvertToKeyValue(recieved)); break; case "ka": SendKeepAlive(); break; default: LogWriter.Log.Write("Received unknown request " + recieved[0], LogLevel.Debug); PresenceServer.SendError(stream, 0, "An invalid request was sended."); stream.Close(); break; } }
/// <summary> /// Whenever the "newuser" command is recieved, this method is called to /// add the new users information into the database /// </summary> /// <param name="Recv">Array of parms sent by the server</param> private void CreateNewUser(Dictionary <string, string> Recv) { // Make sure the user doesnt exist already try { // Check to see if user exists if (DatabaseUtility.UserExists(databaseDriver, Recv["nick"])) { Stream.SendAsync(@"\error\\err\516\fatal\\errmsg\This account name is already in use!\id\1\final\"); Disconnect(DisconnectReason.CreateFailedUsernameExists); return; } // We need to decode the Gamespy specific encoding for the password string Password = GamespyUtils.DecodePassword(Recv["passwordenc"]); string Cc = (RemoteEndPoint.AddressFamily == AddressFamily.InterNetwork) //? GeoIP.GetCountryCode(RemoteEndPoint.Address) //: "US"; ? "US" : "US"; // Attempt to create account. If Pid is 0, then we couldnt create the account. TODO: Handle Unique Nickname if ((PlayerId = DatabaseUtility.CreateUser(databaseDriver, Recv["nick"], Password, Recv["email"], Cc, Recv["nick"])) == 0) { PresenceServer.SendError(Stream, 516, "An error oncurred while creating the account!"); Disconnect(DisconnectReason.CreateFailedDatabaseError); return; } Stream.SendAsync(@"\nur\\userid\{0}\profileid\{0}\id\1\final\", PlayerId); } catch (Exception e) { // Check for invalid query params if (e is KeyNotFoundException) { PresenceServer.SendError(Stream, 516, "Invalid response received from the client!"); } else { PresenceServer.SendError(Stream, 516, "An error oncurred while creating the account!"); LogWriter.Log.Write("An error occured while trying to create a new User account :: " + e.Message, LogLevel.Error); } Disconnect(DisconnectReason.GeneralError); return; } }
/// <summary> /// This method is called when the client requests for the Account profile /// </summary> private void SendProfile(Dictionary <string, string> dict) { if (!dict.ContainsKey("profileid")) { PresenceServer.SendError(Stream, 1, "There was an error parsing an incoming request."); return; } uint targetPID = 0, messID = 0; if (!uint.TryParse(dict["profileid"], out targetPID)) { PresenceServer.SendError(Stream, 1, "There was an error parsing an incoming request."); return; } if (!uint.TryParse(dict["id"], out messID)) { PresenceServer.SendError(Stream, 1, "There was an error parsing an incoming request."); return; } string datatoSend = @"\pi\\profileid\" + targetPID + @"\mp\4"; // If the client want to access the public information // of another client if (targetPID != PlayerId) { uint publicMask = 0; var Query = DatabaseUtility.GetProfileInfo(databaseDriver, targetPID); if (Query == null) { PresenceServer.SendError(Stream, 4, "Unable to get profile information."); return; } if (!uint.TryParse(Query["publicmask"].ToString(), out publicMask)) { publicMask = (uint)PublicMasks.MASK_NONE; } datatoSend = string.Format(datatoSend + @"\nick\{0}\uniquenick\{1}\id\{2}", Query["nick"].ToString(), Query["uniquenick"].ToString(), messID); if (Query["email"].ToString().Length > 0 && publicMask != (uint)PublicMasks.MASK_NONE) { if ((publicMask & (uint)PublicMasks.MASK_EMAIL) > 0) { datatoSend += @"\email\" + Query["email"].ToString(); } } if (Query["lastname"].ToString().Length > 0 && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\lastname\" + Query["lastname"].ToString(); } if (Query["firstname"].ToString().Length > 0 && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\firstname\" + Query["firstname"].ToString(); } if (int.Parse(Query["icq"].ToString()) != 0 && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\icquin\" + int.Parse(Query["icq"].ToString()); } if (PlayerHomepage.Length > 0 && publicMask != (uint)PublicMasks.MASK_NONE) { if ((publicMask & (uint)PublicMasks.MASK_HOMEPAGE) > 0) { datatoSend += @"\homepage\" + Query["homepage"].ToString(); } } if (uint.Parse(Query["picture"].ToString()) != 0) { datatoSend += @"\pic\" + uint.Parse(Query["Show"].ToString()); } if (Query["aim"].ToString().Length > 0 && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\aim\" + Query["aim"].ToString(); } if (int.Parse(Query["occupationid"].ToString()) != 0 && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\occ\" + int.Parse(Query["occupationid"].ToString()); } if (Query["zipcode"].ToString().Length > 0 && publicMask != (uint)PublicMasks.MASK_NONE) { if ((publicMask & (uint)PublicMasks.MASK_ZIPCODE) > 0) { datatoSend += @"\zipcode\" + Query["zipcode"].ToString(); } } if (Query["countrycode"].ToString().Length > 0 && publicMask != (uint)PublicMasks.MASK_NONE) { if ((publicMask & (uint)PublicMasks.MASK_COUNTRYCODE) > 0) { datatoSend += @"\countrycode\" + Query["countrycode"].ToString(); } } if (ushort.Parse(Query["birthday"].ToString()) > 0 && ushort.Parse(Query["birthmonth"].ToString()) > 0 && ushort.Parse(Query["birthyear"].ToString()) > 0 && publicMask != (uint)PublicMasks.MASK_NONE) { if ((publicMask & (uint)PublicMasks.MASK_BIRTHDAY) > 0) { datatoSend += @"\birthday\" + (uint)((ushort.Parse(Query["birthday"].ToString()) << 24) | (ushort.Parse(Query["birthmonth"].ToString()) << 16) | ushort.Parse(Query["birthyear"].ToString())); } } if (Query["location"].ToString().Length > 0 && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\loc\" + Query["location"].ToString(); } if (publicMask != (uint)PublicMasks.MASK_NONE && (publicMask & (uint)PublicMasks.MASK_SEX) > 0) { PlayerSexType sexType; if (Enum.TryParse(Query["sex"].ToString(), out sexType)) { if (PlayerSex == PlayerSexType.FEMALE) { datatoSend += @"\sex\1"; } else if (PlayerSex == PlayerSexType.MALE) { datatoSend += @"\sex\0"; } } } if (float.Parse(Query["latitude"].ToString()) != 0.0f && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\lat\" + float.Parse(Query["latitude"].ToString()); } if (float.Parse(Query["longitude"].ToString()) != 0.0f && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\lon\" + float.Parse(Query["longitude"].ToString()); } if (int.Parse(Query["incomeid"].ToString()) != 0 && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\inc\" + int.Parse(Query["incomeid"].ToString()); } if (int.Parse(Query["industryid"].ToString()) != 0 && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\ind\" + int.Parse(Query["industryid"].ToString()); } if (int.Parse(Query["marriedid"].ToString()) != 0 && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\mar\" + int.Parse(Query["marriedid"].ToString()); } if (int.Parse(Query["childcount"].ToString()) != 0 && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\chc\" + int.Parse(Query["childcount"].ToString()); } if (int.Parse(Query["interests1"].ToString()) != 0 && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\i1\" + int.Parse(Query["interests1"].ToString()); } if (int.Parse(Query["ownership1"].ToString()) != 0 && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\o1\" + int.Parse(Query["ownership1"].ToString()); } if (int.Parse(Query["connectiontype"].ToString()) != 0 && publicMask != (uint)PublicMasks.MASK_NONE) { datatoSend += @"\conn\" + int.Parse(Query["connectiontype"].ToString()); } // SUPER NOTE: Please check the Signature of the PID, otherwise when it will be compared with other peers, it will break everything (See gpiPeer.c @ peerSig) datatoSend += @"\sig\" + GameSpyLib.Random.GenerateRandomString(33, GameSpyLib.Random.StringType.Hex) + @"\final\"; } else { // Since this is our profile, we have to see ALL informations that we can edit. This means that we don't need to check the public masks for sending // the data datatoSend = string.Format(datatoSend + @"\nick\{0}\uniquenick\{1}\email\{2}\id\{3}\pmask\{4}", PlayerNick, PlayerUniqueNick, PlayerEmail, /*(ProfileSent ? "5" : "2")*/ messID, PlayerPublicMask); if (PlayerLastName.Length > 0) { datatoSend += @"\lastname\" + PlayerLastName; } if (PlayerFirstName.Length > 0) { datatoSend += @"\firstname\" + PlayerFirstName; } if (PlayerICQ != 0) { datatoSend += @"\icquin\" + PlayerICQ; } if (PlayerHomepage.Length > 0) { datatoSend += @"\homepage\" + PlayerHomepage; } if (PlayerPicture != 0) { datatoSend += @"\pic\" + PlayerPicture; } if (PlayerAim.Length > 0) { datatoSend += @"\aim\" + PlayerAim; } if (PlayerOccupation != 0) { datatoSend += @"\occ\" + PlayerOccupation; } if (PlayerZIPCode.Length > 0) { datatoSend += @"\zipcode\" + PlayerZIPCode; } if (PlayerCountryCode.Length > 0) { datatoSend += @"\countrycode\" + PlayerCountryCode; } if (PlayerBirthday > 0 && PlayerBirthmonth > 0 && PlayerBirthyear > 0) { datatoSend += @"\birthday\" + (uint)((PlayerBirthday << 24) | (PlayerBirthmonth << 16) | PlayerBirthyear); } if (PlayerLocation.Length > 0) { datatoSend += @"\loc\" + PlayerLocation; } if (PlayerSex == PlayerSexType.FEMALE) { datatoSend += @"\sex\1"; } else if (PlayerSex == PlayerSexType.MALE) { datatoSend += @"\sex\0"; } if (PlayerLatitude != 0.0f) { datatoSend += @"\lat\" + PlayerLatitude; } if (PlayerLongitude != 0.0f) { datatoSend += @"\lon\" + PlayerLongitude; } if (PlayerIncomeID != 0) { datatoSend += @"\inc\" + PlayerIncomeID; } if (PlayerIndustryID != 0) { datatoSend += @"\ind\" + PlayerIndustryID; } if (PlayerMarried != 0) { datatoSend += @"\mar\" + PlayerMarried; } if (PlayerChildCount != 0) { datatoSend += @"\chc\" + PlayerChildCount; } if (PlayerInterests != 0) { datatoSend += @"\i1\" + PlayerInterests; } if (PlayerOwnership != 0) { datatoSend += @"\o1\" + PlayerOwnership; } if (PlayerConnectionType != 0) { datatoSend += @"\conn\" + PlayerConnectionType; } // SUPER NOTE: Please check the Signature of the PID, otherwise when it will be compared with other peers, it will break everything (See gpiPeer.c @ peerSig) datatoSend += @"\sig\" + GameSpyLib.Random.GenerateRandomString(33, GameSpyLib.Random.StringType.Hex) + @"\final\"; // Set that we send the profile initially if (!ProfileSent) { ProfileSent = true; } } Stream.SendAsync(datatoSend); }
/// <summary> /// This method verifies the login information sent by /// the client, and returns encrypted data for the client /// to verify as well /// </summary> public void ProcessLogin(Dictionary <string, string> Recv) { uint partnerID = 0; // Make sure we have all the required data to process this login if (!Recv.ContainsKey("challenge") || !Recv.ContainsKey("response")) { PresenceServer.SendError(Stream, 0, "Invalid response received from the client!"); Disconnect(DisconnectReason.InvalidLoginQuery); return; } // Parse the partnerid, required since it changes the challenge for Unique nick and User login if (Recv.ContainsKey("partnerid")) { if (!uint.TryParse(Recv["partnerid"], out partnerID)) { partnerID = 0; } } // Parse the 3 login types information if (Recv.ContainsKey("uniquenick")) { PlayerUniqueNick = Recv["uniquenick"]; } else if (Recv.ContainsKey("authtoken")) { PlayerAuthToken = Recv["authtoken"]; } else if (Recv.ContainsKey("user")) { // "User" is <nickname>@<email> string User = Recv["user"]; int Pos = User.IndexOf('@'); PlayerNick = User.Substring(0, Pos); PlayerEmail = User.Substring(Pos + 1); } // Dispose connection after use try { // Try and fetch the user from the database Dictionary <string, object> QueryResult; try { if (PlayerUniqueNick != null) { QueryResult = DatabaseUtility.GetUserFromUniqueNick(databaseDriver, Recv["uniquenick"]); } else if (PlayerAuthToken != null) { //TODO! Add the database entry PresenceServer.SendError(Stream, 0, "AuthToken is not supported yet"); return; } else { QueryResult = DatabaseUtility.GetUserFromNickname(databaseDriver, PlayerEmail, PlayerNick); } } catch (Exception) { PresenceServer.SendError(Stream, 4, "This request cannot be processed because of a database error."); return; } if (QueryResult == null) { if (PlayerUniqueNick != null) { PresenceServer.SendError(Stream, 265, "The unique nickname provided is incorrect!"); } else { PresenceServer.SendError(Stream, 265, "The nickname provided is incorrect!"); } Disconnect(DisconnectReason.InvalidUsername); return; } // Check if user is banned PlayerStatus currentPlayerStatus; UserStatus currentUserStatus; if (!Enum.TryParse(QueryResult["status"].ToString(), out currentPlayerStatus)) { PresenceServer.SendError(Stream, 265, "Invalid player data! Please contact an administrator."); Disconnect(DisconnectReason.InvalidPlayer); return; } if (!Enum.TryParse(QueryResult["userstatus"].ToString(), out currentUserStatus)) { PresenceServer.SendError(Stream, 265, "Invalid player data! Please contact an administrator."); Disconnect(DisconnectReason.InvalidPlayer); return; } // Check the status of the account. // If the single profile is banned, the account or the player status if (currentPlayerStatus == PlayerStatus.Banned) { PresenceServer.SendError(Stream, 265, "Your profile has been permanently suspended."); Disconnect(DisconnectReason.PlayerIsBanned); return; } if (currentUserStatus == UserStatus.Created) { PresenceServer.SendError(Stream, 265, "Your account is not verified. Please check your email inbox and verify the account."); Disconnect(DisconnectReason.PlayerIsBanned); return; } if (currentUserStatus == UserStatus.Banned) { PresenceServer.SendError(Stream, 265, "Your account has been permanently suspended."); Disconnect(DisconnectReason.PlayerIsBanned); return; } // Set player variables PlayerId = uint.Parse(QueryResult["profileid"].ToString()); PasswordHash = QueryResult["password"].ToString().ToLowerInvariant(); PlayerCountryCode = QueryResult["countrycode"].ToString(); PlayerFirstName = QueryResult["firstname"].ToString(); PlayerLastName = QueryResult["lastname"].ToString(); PlayerICQ = int.Parse(QueryResult["icq"].ToString()); PlayerHomepage = QueryResult["homepage"].ToString(); PlayerZIPCode = QueryResult["zipcode"].ToString(); PlayerLocation = QueryResult["location"].ToString(); PlayerAim = QueryResult["aim"].ToString(); PlayerOwnership = int.Parse(QueryResult["ownership1"].ToString()); PlayerOccupation = int.Parse(QueryResult["occupationid"].ToString()); PlayerIndustryID = int.Parse(QueryResult["industryid"].ToString()); PlayerIncomeID = int.Parse(QueryResult["incomeid"].ToString()); PlayerMarried = int.Parse(QueryResult["marriedid"].ToString()); PlayerChildCount = int.Parse(QueryResult["childcount"].ToString()); PlayerConnectionType = int.Parse(QueryResult["connectiontype"].ToString()); PlayerPicture = int.Parse(QueryResult["picture"].ToString()); PlayerInterests = int.Parse(QueryResult["interests1"].ToString()); PlayerBirthday = ushort.Parse(QueryResult["birthday"].ToString()); PlayerBirthmonth = ushort.Parse(QueryResult["birthmonth"].ToString()); PlayerBirthyear = ushort.Parse(QueryResult["birthyear"].ToString()); PlayerSexType playerSexType; if (!Enum.TryParse(QueryResult["sex"].ToString().ToUpper(), out playerSexType)) { PlayerSex = PlayerSexType.PAT; } else { PlayerSex = playerSexType; } PlayerLatitude = float.Parse(QueryResult["latitude"].ToString()); PlayerLongitude = float.Parse(QueryResult["longitude"].ToString()); PlayerPublicMask = uint.Parse(QueryResult["publicmask"].ToString()); string challengeData = ""; if (PlayerUniqueNick != null) { PlayerEmail = QueryResult["email"].ToString(); PlayerNick = QueryResult["nick"].ToString(); challengeData = PlayerUniqueNick; } else if (PlayerAuthToken != null) { PlayerEmail = QueryResult["email"].ToString(); PlayerNick = QueryResult["nick"].ToString(); PlayerUniqueNick = QueryResult["uniquenick"].ToString(); challengeData = PlayerAuthToken; } else { PlayerUniqueNick = QueryResult["uniquenick"].ToString(); challengeData = Recv["user"]; } // Use the GenerateProof method to compare with the "response" value. This validates the given password if (Recv["response"] == GenerateProof(Recv["challenge"], ServerChallengeKey, challengeData, PlayerAuthToken != null ? 0 : partnerID)) { // Create session key SessionKey = Crc.ComputeChecksum(PlayerUniqueNick); // Password is correct Stream.SendAsync( @"\lc\2\sesskey\{0}\proof\{1}\userid\{2}\profileid\{2}\uniquenick\{3}\lt\{4}__\id\1\final\", SessionKey, GenerateProof(ServerChallengeKey, Recv["challenge"], challengeData, PlayerAuthToken != null ? 0 : partnerID), // Do this again, Params are reversed! PlayerId, PlayerNick, GameSpyLib.Random.GenerateRandomString(22, GameSpyLib.Random.StringType.Hex) // Generate LT whatever that is (some sort of random string, 22 chars long) ); // Log Incoming Connections LogWriter.Log.Write("Client Login: {0} - {1} - {2}", LogLevel.Information, PlayerNick, PlayerId, RemoteEndPoint); // Update status last, and call success login LoginStatus = LoginStatus.Completed; PlayerStatus = PlayerStatus.Online; PlayerStatusString = "Online"; PlayerStatusLocation = ""; CompletedLoginProcess = true; OnSuccessfulLogin?.Invoke(this); OnStatusChanged?.Invoke(this); SendBuddies(); } else { // Log Incoming Connections LogWriter.Log.Write("Failed Login Attempt: {0} - {1} - {2}", LogLevel.Information, PlayerNick, PlayerId, RemoteEndPoint); // Password is incorrect with database value Stream.SendAsync(@"\error\\err\260\fatal\\errmsg\The password provided is incorrect.\id\1\final\"); Disconnect(DisconnectReason.InvalidPassword); } } catch (Exception ex) { LogWriter.Log.Write(ex.ToString(), LogLevel.Error); Disconnect(DisconnectReason.GeneralError); return; } }