using System; using System.Data; using Snowlight.Storage; using Snowlight.Game.Characters; using Snowlight.Game.Sessions; using Snowlight.Game.Moderation; using Snowlight.Config; namespace Snowlight.Game.Sessions { /// /// Provides user authentication for single sign on tickets. /// public static class SingleSignOnAuthenticator { private static int mSuccessfulLoginCount; private static int mFailedLoginCount; private static object mAuthSyncRoot; /// /// Returns the total amount of successful authentication attempts. /// public static int SuccessfulLoginCount { get { return mSuccessfulLoginCount; } } /// /// Returns the total amount of failed authentication attempts. /// public static int FailedLoginCount { get { return mFailedLoginCount; } } /// /// Returns the total amount of authentication attempts. /// public static int TotalLoginCount { get { return (mSuccessfulLoginCount + mFailedLoginCount); } } /// /// Initializes the user authenticator. /// public static void Initialize() { mSuccessfulLoginCount = 0; mFailedLoginCount = 0; mAuthSyncRoot = new object(); } /// /// Attemps to authenticate an user using an SSO (Single Sign On) ticket. /// /// The ticket string. /// Character id on success, 0 on authentication failure. public static uint TryAuthenticate(SqlDatabaseClient MySqlClient, string Ticket, string RemoteAddress) { lock (mAuthSyncRoot) { // Remove any spacing from single sign on ticket Ticket = Ticket.Trim(); // Ensure the ticket meets the minimum length requirement if (Ticket.Length <= 5) { mFailedLoginCount++; Output.WriteLine("Login from " + RemoteAddress + " rejected: SSO ticket too short."); return 0; } // Debug string DebugTicket = (string)ConfigManager.GetValue("debug.sso"); if (DebugTicket.Length > 0 && Ticket == DebugTicket) return 1; // Check the database for a matching single sign on ticket uint UserId = 0; string LogName = string.Empty; MySqlClient.SetParameter("ticket", Ticket); DataRow Row = MySqlClient.ExecuteQueryRow("SELECT id,username FROM characters WHERE auth_ticket = @ticket LIMIT 1"); if (Row != null) { UserId = (uint)Row["id"]; LogName = (string)Row["username"]; RemoveTicket(MySqlClient, (uint)Row["id"], RemoteAddress); } // Check if ticket was OK + Check for user id bans if (UserId <= 0) { mFailedLoginCount++; Output.WriteLine("Login from " + RemoteAddress + " rejected: invalid SSO ticket."); return 0; } if (ModerationBanManager.IsUserIdBlacklisted(UserId)) { mFailedLoginCount++; Output.WriteLine("Login from " + RemoteAddress + " rejected: blacklisted IP address."); return 0; } // Disconnect any previous sessions for this account if (SessionManager.ContainsCharacterId(UserId)) { Session TargetSession = SessionManager.GetSessionByCharacterId(UserId); SessionManager.StopSession(TargetSession.Id); } // Mark as a successful login and continue Output.WriteLine("User " + LogName + " (ID " + UserId + ") has logged in from " + RemoteAddress + "."); mSuccessfulLoginCount++; return UserId; } } /// /// Removes the SSO (Single Sign On) ticket from the database after a successful login attempt. /// /// The character id to remove the ticket from. private static void RemoveTicket(SqlDatabaseClient MySqlClient, uint UserId, string AddressToLog) { MySqlClient.SetParameter("id", UserId); MySqlClient.SetParameter("lastip", AddressToLog); MySqlClient.SetParameter("lastonline", UnixTimestamp.GetCurrent()); MySqlClient.ExecuteNonQuery("UPDATE characters SET auth_ticket = '', last_ip = @lastip, timestamp_lastvisit = @lastonline WHERE id = @id LIMIT 1"); } } }