Пример #1
0
        /// <summary>
        /// Callback for a successful login
        /// </summary>
        /// <param name="sender">The GpcmClient that is logged in</param>
        private void GpcmClient_OnSuccessfulLogin(object sender)
        {
            // Wrap this in a try/catch
            try
            {
                GpcmClient oldC;
                GpcmClient client = sender as GpcmClient;

                // Remove connection from processing
                Processing.TryRemove(client.ConnectionId, out oldC);

                // Check to see if the client is already logged in, if so disconnect the old user
                if (Clients.TryRemove(client.PlayerId, out oldC))
                {
                    oldC.Disconnect(DisconnectReason.NewLoginDetected);
                    ServerManager.Log("Login Clash:   {0} - {1} - {2}", client.PlayerNick, client.PlayerId, client.RemoteEndPoint);
                }

                // Add current client to the dictionary
                if (!Clients.TryAdd(client.PlayerId, client))
                {
                    Program.ErrorLog.Write("ERROR: [GpcmServer._OnSuccessfulLogin] Unable to add client to HashSet.");
                }

                // Add player to database queue
                var status = new PlayerStatusUpdate(client, LoginStatus.Completed);
                PlayerStatusQueue.Enqueue(status);
            }
            catch (Exception E)
            {
                Program.ErrorLog.Write("ERROR: [GpcmServer._OnSuccessfulLogin] Exception was thrown, Generating exception log.");
                ExceptionHandler.GenerateExceptionLog(E);
            }
        }
Пример #2
0
        /// <summary>
        /// Logs the client out of the game client, and closes the stream
        /// </summary>
        /// <param name="reason">
        /// The disconnect reason code.
        /// </param>
        /// <remarks>
        /// If set the <paramref name="reason"/> is set to <see cref="DisconnectReason.ForcedServerShutdown"/>,
        /// 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
        /// <see cref="DisconnectReason.ForcedServerShutdown"/> for a planned server shutdown.
        /// </remarks>
        public void Disconnect(DisconnectReason reason)
        {
            // Update database session
            if (Status == LoginStatus.Completed && reason != DisconnectReason.ForcedServerShutdown)
            {
                try
                {
                    using (GamespyDatabase Database = new GamespyDatabase())
                        Database.Execute("UPDATE player SET online=0 WHERE id=" + PlayerId);
                }
                catch { }
            }

            // If connection is still alive, disconnect user
            try
            {
                Stream.OnDisconnect -= Stream_OnDisconnect;
                Stream.DataReceived -= Stream_DataReceived;
                Stream.Close(reason == DisconnectReason.ForcedServerShutdown);
            }
            catch { }

            // Set status and log
            if (Status == LoginStatus.Completed)
            {
                if (reason == DisconnectReason.NormalLogout)
                {
                    ServerManager.Log(
                        "Client Logout:  {0} - {1} - {2}",
                        PlayerNick,
                        PlayerId,
                        RemoteEndPoint
                        );
                }
                else
                {
                    ServerManager.Log(
                        "Client Disconnected:  {0} - {1} - {2}, Code={3}",
                        PlayerNick,
                        PlayerId,
                        RemoteEndPoint,
                        Enum.GetName(typeof(DisconnectReason), reason)
                        );
                }
            }

            // Preapare to be unloaded from memory
            Status = LoginStatus.Disconnected;
            Dispose();

            // Call disconnect event
            OnDisconnect?.Invoke(this);
        }
Пример #3
0
        /// <summary>
        /// Logs the client out of the game client, and closes the stream
        /// </summary>
        /// <param name="where">
        /// 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 => Keep Alive Packet failed to send (may not work with new async socket code)
        ///     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 => Stream Disconnected
        ///     9 => Forced server shutdown [No events called, database sessions are not updated, and EventArgs are disposed]
        ///     10 => Client challenge already sent
        /// </remarks>
        public void Disconnect(int where)
        {
            // Update database session
            if (Status == LoginStatus.Completed && where < 9)
            {
                try
                {
                    using (GamespyDatabase Database = new GamespyDatabase())
                        Database.Execute("UPDATE web_users SET game_session=0 WHERE pid=" + PlayerId);
                }
                catch { }
            }

            // If connection is still alive, disconnect user
            try
            {
                Stream.OnDisconnect -= Stream_OnDisconnect;
                Stream.DataReceived -= Stream_DataReceived;
                Stream.Close(where == 9);
            }
            catch { }

            // Set status and log
            if (Status == LoginStatus.Completed)
            {
                ServerManager.Log("Client Logout:  {0} - {1} - {2}, Code={3}", PlayerNick, PlayerId, RemoteEndPoint, where);
            }

            // Preapare to be unloaded from memory
            Status = LoginStatus.Disconnected;
            Dispose();

            // Call disconnect event
            if (OnDisconnect != null)
            {
                OnDisconnect(this);
            }
        }
Пример #4
0
        /// <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(DisconnectReason.InvalidLoginQuery);
                return;
            }

            // Dispose connection after use
            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(DisconnectReason.InvalidUsername);
                        return;
                    }

                    // Check if user is banned
                    bool banned = Int32.Parse(User["permban"].ToString()) > 0;
                    if (banned)
                    {
                        Stream.SendAsync(@"\error\\err\265\fatal\\errmsg\You account has been permanently suspended.\id\1\final\");
                        Disconnect(DisconnectReason.PlayerIsBanned);
                        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().ToLowerInvariant();

                    // Use the GenerateProof method to compare with the "response" value. This validates the given password
                    if (Recv["response"] == GenerateProof(Recv["challenge"], ServerChallengeKey))
                    {
                        // Create session key
                        SessionKey = Crc.ComputeChecksum(PlayerNick);

                        // 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"]), // Do this again, Params are reversed!
                            PlayerId,
                            PlayerNick,
                            GenerateRandomString(22) // Generate LT whatever that is (some sort of random string, 22 chars long)
                            );

                        // Log Incoming Connections
                        ServerManager.Log("Client Login:   {0} - {1} - {2}", PlayerNick, PlayerId, RemoteEndPoint);

                        // Update status last, and call success login
                        Status = LoginStatus.Completed;
                        CompletedLoginProcess = true;
                        OnSuccessfulLogin?.Invoke(this);
                    }
                    else
                    {
                        // Log Incoming Connections
                        ServerManager.Log("Failed Login Attempt: {0} - {1} - {2}", 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)
            {
                ExceptionHandler.GenerateExceptionLog(ex);
                Disconnect(DisconnectReason.GeneralError);
                return;
            }
        }
Пример #5
0
        /// <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;
            }

            // Dispose connection after use
            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["pid"].ToString());
                    PlayerNick        = Recv["uniquenick"];
                    PlayerEmail       = User["email"].ToString();
                    PlayerCountryCode = User["game_country"].ToString();
                    PasswordHash      = User["password"].ToString().ToLowerInvariant();

                    // Use the GenerateProof method to compare with the "response" value. This validates the given password
                    if (Recv["response"] == GenerateProof(Recv["challenge"], ServerChallengeKey))
                    {
                        // Create session key
                        SessionKey = Crc.ComputeChecksum(PlayerNick);

                        // 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"]), // Do this again, Params are reversed!
                            PlayerId,
                            PlayerNick,
                            GenerateRandomString(22) // Generate LT whatever that is (some sort of random string, 22 chars long)
                            );

                        // Log Incoming Connections
                        ServerManager.Log("Client Login:   {0} - {1} - {2}", PlayerNick, PlayerId, RemoteEndPoint);
                        Conn.Execute(
                            "UPDATE web_users SET last_game_ip=@P0, game_session=1, game_tstamp=@P1 WHERE pid=@P2",
                            RemoteEndPoint.Address,
                            DateTime.UtcNow.ToUnixTimestamp(),
                            PlayerId
                            );

                        // Update status last, and call success login
                        Status = LoginStatus.Completed;
                        if (OnSuccessfulLogin != null)
                        {
                            OnSuccessfulLogin(this);
                        }
                    }
                    else
                    {
                        // Log Incoming Connections
                        ServerManager.Log("Failed Login Attempt: {0} - {1} - {2}", 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(3);
                    }
                }
            }
            catch (Exception ex)
            {
                ExceptionHandler.GenerateExceptionLog(ex);
                Disconnect(4);
                return;
            }
        }
Пример #6
0
        /// <summary>
        /// Creates a new instance of <see cref="GpcmServer"/>
        /// </summary>
        /// <param name="bindTo"></param>
        public GpcmServer(IPEndPoint bindTo) : base(bindTo, MaxConnections)
        {
            // Register for events
            GpcmClient.OnSuccessfulLogin += GpcmClient_OnSuccessfulLogin;
            GpcmClient.OnDisconnect      += GpcmClient_OnDisconnect;

            // Setup timer. Every 15 seconds should be sufficient
            if (PollTimer == null || !PollTimer.Enabled)
            {
                PollTimer          = new System.Timers.Timer(15000);
                PollTimer.Elapsed += (s, e) =>
                {
                    // Send keep alive to all connected clients
                    if (Clients.Count > 0)
                    {
                        Parallel.ForEach(Clients.Values, client => client.SendKeepAlive());
                    }

                    // Disconnect hanging connections
                    if (Processing.Count > 0)
                    {
                        Parallel.ForEach(Processing.Values, client => CheckTimeout(client));
                    }
                };
                PollTimer.Start();
            }

            // Setup timer. Every 5 seconds should be sufficient
            if (StatusTimer == null || !StatusTimer.Enabled)
            {
                StatusTimer          = new System.Timers.Timer(5000);
                StatusTimer.Elapsed += (s, e) =>
                {
                    // Return if we are empty
                    if (PlayerStatusQueue.IsEmpty)
                    {
                        return;
                    }

                    // Open database connection
                    using (Database.GamespyDatabase db = new Database.GamespyDatabase())
                        using (var transaction = db.BeginTransaction())
                        {
                            try
                            {
                                var timestamp = DateTime.UtcNow.ToUnixTimestamp();
                                PlayerStatusUpdate result;
                                while (PlayerStatusQueue.TryDequeue(out result))
                                {
                                    // Skip if this player never finished logging in
                                    if (!result.Client.CompletedLoginProcess)
                                    {
                                        continue;
                                    }

                                    // Only update record under these two status'
                                    if (result.Status != LoginStatus.Completed && result.Status != LoginStatus.Disconnected)
                                    {
                                        continue;
                                    }

                                    // Update player record
                                    db.Execute(
                                        "UPDATE player SET online=@P0, lastip=@P1, lastonline=@P2 WHERE id=@P3",
                                        (result.Status == LoginStatus.Disconnected) ? 0 : 1,
                                        result.Client.RemoteEndPoint.Address,
                                        timestamp,
                                        result.Client.PlayerId
                                        );
                                }

                                transaction.Commit();
                            }
                            catch (Exception ex)
                            {
                                ServerManager.Log("[Gpcm..ctor] StatusTimer: " + ex.Message);
                                transaction.Rollback();
                            }
                        }
                };
                StatusTimer.Start();
            }

            // Set connection handling
            base.ConnectionEnforceMode = EnforceMode.DuringPrepare;
            base.FullErrorMessage      = Config.GetValue("Settings", "LoginServerFullMessage").Replace("\"", "");

            // Begin accepting connections
            base.StartAcceptAsync();
        }