/// <summary> /// Event called when a complete message has been recieved /// </summary> /// <param name="Message"></param> private void Stream_DataReceived(string message) { // Read client message, and parse it into key value pairs string[] recieved = message.TrimStart('\\').Split('\\'); switch (recieved[0]) { case "newuser": CreateNewUser(ConvertToKeyValue(recieved)); break; case "login": ProcessLogin(ConvertToKeyValue(recieved)); break; case "getprofile": SendProfile(); break; case "updatepro": UpdateUser(ConvertToKeyValue(recieved)); break; case "logout": Disconnect(0); break; default: Stream.SendAsync(@"\error\\err\0\fatal\\errmsg\Invalid Query!\id\1\final\"); GpcmServer.Log("NOTICE: [GpcmClient.Stream_DataReceived] Unkown Message Passed: {0}", message); 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 { using (GamespyDatabase Database = new GamespyDatabase()) { // Check to see if user exists if (Database.UserExists(Recv["nick"])) { Stream.SendAsync(@"\error\\err\516\fatal\\errmsg\This account name is already in use!\id\1\final\"); Disconnect(5); return; } // We need to decode the Gamespy specific encoding for the password string Password = GamespyUtils.DecodePassword(Recv["passwordenc"]); string Cc = (RemoteEndPoint.AddressFamily == AddressFamily.InterNetwork) ? Ip2nation.GetCountryCode(RemoteEndPoint.Address) : Program.Config.ASP_LocalIpCountryCode; // Attempt to create account. If Pid is 0, then we couldnt create the account if ((PlayerId = Database.CreateUser(Recv["nick"], Password, Recv["email"], Cc)) == 0) { Stream.SendAsync(@"\error\\err\516\fatal\\errmsg\Error creating account!\id\1\final\"); Disconnect(6); return; } Stream.SendAsync(@"\nur\\userid\{0}\profileid\{0}\id\1\final\", PlayerId); } } catch (Exception e) { // Check for invalid query params if (e is KeyNotFoundException) { Stream.SendAsync(@"\error\\err\0\fatal\\errmsg\Invalid Query!\id\1\final\"); } else { Stream.SendAsync(@"\error\\err\516\fatal\\errmsg\Error creating account!\id\1\final\"); GpcmServer.Log("ERROR: [Gpcm.CreateNewUser] An error occured while trying to create a new User account :: " + e.Message); } Disconnect(7); return; } }
/// <summary> /// Logs the client out of the game client, and closes the stream /// </summary> /// <param name="code"> /// The disconnect code. If set to 9, the OnDisconect event will not be called, the database /// will not be updated to reset everyone's session code, and the EventArgs objects will NOT /// be returned to the IO pool. You should only set to 9 for a planned server shutdown. /// </param> /// <remarks> /// Codes: /// 0 => Client sends the "logout" command /// 1 => The login timer elapsed and the client wasnt logged in or this object was disposed, forcefully disconnected /// 2 => Invalid login query, or username was incorrect /// 3 => Incorrect Password /// 4 => An error occured while trying to login the client (could be database related) /// 5 => Cant create account, username exists already /// 6 => Error Creating new account in database /// 7 => Invalid query for account creation, or an exception was thrown while trying to create account /// 8 => Remote Connection closed the Stream or was un-readchable /// 9 => Forced server shutdown [No events called, database sessions are not updated, and EventArgs are disposed] /// </remarks> public void Disconnect(int code) { // Make sure we arent disposed if (Disposed) { return; } // Update database session if (Status == LoginStatus.Completed && code < 9) { try { using (GamespyDatabase Database = new GamespyDatabase()) Database.Execute("UPDATE accounts SET session=0 WHERE id=" + PlayerId); } catch { // We could be shutting this server down because of DB connection issues, don't do anything here. } } // Unregister for stream events and close the connection Stream.OnDisconnect -= Stream_OnDisconnect; Stream.DataReceived -= Stream_DataReceived; Stream.Close(code == 9); // Set status and log if (code == 1 && Status == LoginStatus.Processing) { GpcmServer.Log("Login Timeout: {0} - {1} - {2}", PlayerNick, PlayerId, RemoteEndPoint); } else if (Status != LoginStatus.Disconnected) { GpcmServer.Log("Client Logout: {0} - {1} - {2}, Code={3}", PlayerNick, PlayerId, RemoteEndPoint, code); } // Preapare to be unloaded from memory Status = LoginStatus.Disconnected; Disposed = true; // Call disconnect event if (OnDisconnect != null) { OnDisconnect(this); } }
/// <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) { // Make sure we have all the required data to process this login if (!Recv.ContainsKey("uniquenick") || !Recv.ContainsKey("challenge") || !Recv.ContainsKey("response")) { Stream.SendAsync(@"\error\\err\0\fatal\\errmsg\Invalid Query!\id\1\final\"); Disconnect(2); return; } // Warp this in a try/catch, incase database is offline or something try { using (GamespyDatabase Conn = new GamespyDatabase()) { // Try and fetch the user from the database Dictionary <string, object> User = Conn.GetUser(Recv["uniquenick"]); if (User == null) { Stream.SendAsync(@"\error\\err\265\fatal\\errmsg\The uniquenick provided is incorrect!\id\1\final\"); Disconnect(2); return; } // Set player variables PlayerId = Int32.Parse(User["id"].ToString()); PlayerNick = Recv["uniquenick"]; PlayerEmail = User["email"].ToString(); PlayerCountryCode = User["country"].ToString(); PasswordHash = User["password"].ToString(); // Use the GenerateProof method to compare with the "response" value. This validates the given password if (Recv["response"] == GenerateProof(Recv["challenge"], ServerChallengeKey)) { // Password is correct, Create session key and respond SessionKey = Crc.ComputeChecksum(PlayerNick); Stream.SendAsync( @"\lc\2\sesskey\{0}\proof\{1}\userid\{2}\profileid\{2}\uniquenick\{3}\lt\{4}__\id\1\final\", SessionKey, GenerateProof(ServerChallengeKey, Recv["challenge"]), // Do this again, Params are reversed! PlayerId, PlayerNick, GenerateRandomString(22) // Generate LT whatever that is (some sort of random string, 22 chars long) ); // Log, Update database, and call event GpcmServer.Log("Client Login: {0} - {1} - {2}", PlayerNick, PlayerId, RemoteEndPoint); Conn.Execute("UPDATE accounts SET lastip=@P0, session=@P1 WHERE id=@P2", RemoteEndPoint.Address, SessionKey, PlayerId); // Update status last, and call success login Status = LoginStatus.Completed; if (OnSuccessfulLogin != null) { OnSuccessfulLogin(this); } } else { // The proof string failed, so the password provided was incorrect GpcmServer.Log("Failed Login Attempt: {0} - {1} - {2}", PlayerNick, PlayerId, RemoteEndPoint); Stream.SendAsync(@"\error\\err\260\fatal\\errmsg\The password provided is incorrect.\id\1\final\"); Disconnect(3); } } } catch { Disconnect(4); return; } }