using SharpChat.Config; using SharpChat.Events; using SharpChat.Misuzu; using SharpChat.SockChat.PacketsS2C; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; namespace SharpChat.SockChat.PacketsC2S { public class AuthC2SPacketHandler : IC2SPacketHandler { public const string MOTD_FILE = @"welcome.txt"; private readonly DateTimeOffset Started; private readonly MisuzuClient Misuzu; private readonly ChannelInfo DefaultChannel; private readonly CachedValue MOTDHeaderFormat; private readonly CachedValue MaxMessageLength; private readonly CachedValue MaxConnections; public AuthC2SPacketHandler( DateTimeOffset started, MisuzuClient msz, ChannelInfo? defaultChannel, CachedValue motdHeaderFormat, CachedValue maxMsgLength, CachedValue maxConns ) { Started = started; Misuzu = msz; DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel)); MOTDHeaderFormat = motdHeaderFormat; MaxMessageLength = maxMsgLength; MaxConnections = maxConns; } public bool IsMatch(C2SPacketHandlerContext ctx) { return ctx.CheckPacketId("1"); } public void Handle(C2SPacketHandlerContext ctx) { string[] args = ctx.SplitText(3); string? authMethod = args.ElementAtOrDefault(1); if(string.IsNullOrWhiteSpace(authMethod)) { ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.FailReason.AuthInvalid)); ctx.Connection.Close(1000); return; } string? authToken = args.ElementAtOrDefault(2); if(string.IsNullOrWhiteSpace(authToken)) { ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.FailReason.AuthInvalid)); ctx.Connection.Close(1000); return; } if(authMethod.All(c => c is >= '0' and <= '9') && authToken.Contains(':')) { string[] tokenParts = authToken.Split(':', 2); authMethod = tokenParts[0]; authToken = tokenParts[1]; } Task.Run(async () => { MisuzuAuthInfo? fai; string ipAddr = ctx.Connection.RemoteAddress; try { fai = await Misuzu.AuthVerifyAsync(authMethod, authToken, ipAddr); } catch(Exception ex) { Logger.Write($"<{ctx.Connection.RemoteEndPoint}> Failed to authenticate: {ex}"); ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.FailReason.AuthInvalid)); ctx.Connection.Close(1000); #if DEBUG throw; #else return; #endif } if(fai == null) { Logger.Debug($"<{ctx.Connection.RemoteEndPoint}> Auth fail: "); ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.FailReason.Null)); ctx.Connection.Close(1000); return; } if(!fai.Success) { Logger.Debug($"<{ctx.Connection.RemoteEndPoint}> Auth fail: {fai.Reason}"); ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.FailReason.AuthInvalid)); ctx.Connection.Close(1000); return; } MisuzuBanInfo? fbi; try { fbi = await Misuzu.CheckBanAsync(fai.UserId.ToString(), ipAddr); } catch(Exception ex) { Logger.Write($"<{ctx.Connection.RemoteEndPoint}> Failed auth ban check: {ex}"); ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.FailReason.AuthInvalid)); ctx.Connection.Close(1000); #if DEBUG throw; #else return; #endif } if(fbi == null) { Logger.Debug($"<{ctx.Connection.RemoteEndPoint}> Ban check fail: "); ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.FailReason.Null)); ctx.Connection.Close(1000); return; } if(fbi.IsBanned && !fbi.HasExpired) { Logger.Write($"<{ctx.Connection.RemoteEndPoint}> User is banned."); ctx.Connection.Send(new AuthFailS2CPacket(fbi.ExpiresAt)); ctx.Connection.Close(1000); return; } if(ctx.Chat.Connections.GetCountForUser(fai.UserId) >= MaxConnections) { ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.FailReason.MaxSessions)); ctx.Connection.Close(1000); return; } await ctx.Chat.ContextAccess.WaitAsync(); try { UserInfo? user = ctx.Chat.Users.Get(fai.UserId); if(user == null) { ctx.Chat.Events.Dispatch( "user:add", fai.UserId, fai.UserName ?? string.Empty, fai.Colour, fai.Rank, string.Empty, fai.Permissions, new UserAddEventData(fai.IsSuper) ); user = ctx.Chat.Users.Get(fai.UserId); if(user == null) { Logger.Write($"<{ctx.Connection.RemoteEndPoint}> User didn't get added."); ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.FailReason.Null)); ctx.Connection.Close(1000); return; } } else { string? updName = !user.UserName.Equals(fai.UserName) ? fai.UserName : null; int? updColour = (updColour = fai.Colour.ToMisuzu()) != user.Colour.ToMisuzu() ? updColour : null; int? updRank = user.Rank != fai.Rank ? fai.Rank : null; UserPermissions? updPerms = user.Permissions != fai.Permissions ? fai.Permissions : null; bool? updSuper = user.IsSuper != fai.IsSuper ? fai.IsSuper : null; if(updName != null || updColour != null || updRank != null || updPerms != null || updSuper != null) ctx.Chat.Events.Dispatch( "user:update", user, new UserUpdateEventData( name: updName, colour: updColour, rank: updRank, perms: updPerms, isSuper: updSuper ) ); } ctx.Connection.BumpPing(); ctx.Chat.Connections.SetUser(ctx.Connection, user); if(!string.IsNullOrWhiteSpace(MOTDHeaderFormat.Value)) ctx.Connection.Send(new MOTDS2CPacket(Started, string.Format(MOTDHeaderFormat.Value, user.UserName))); if(File.Exists(MOTD_FILE)) { IEnumerable lines = File.ReadAllLines(MOTD_FILE).Where(x => !string.IsNullOrWhiteSpace(x)); string? line = lines.ElementAtOrDefault(RNG.Next(lines.Count())); if(!string.IsNullOrWhiteSpace(line)) ctx.Connection.Send(new MOTDS2CPacket(File.GetLastWriteTimeUtc(MOTD_FILE), line)); } ctx.Connection.Send(new AuthSuccessS2CPacket( user.UserId, SockChatUtility.GetUserName(user, ctx.Chat.UserStatuses.Get(user)), user.Colour, user.Rank, user.Permissions, DefaultChannel.Name, MaxMessageLength )); UserInfo[] chanUsers = ctx.Chat.Users.GetMany( ctx.Chat.ChannelsUsers.GetChannelUserIds(DefaultChannel) ); List chanUserEntries = new(); foreach(UserInfo chanUserInfo in chanUsers) if(chanUserInfo.UserId != user.UserId) chanUserEntries.Add(new( chanUserInfo.UserId, SockChatUtility.GetUserName(chanUserInfo, ctx.Chat.UserStatuses.Get(chanUserInfo)), chanUserInfo.Colour, chanUserInfo.Rank, chanUserInfo.Permissions, true )); ctx.Connection.Send(new UsersPopulateS2CPacket(chanUserEntries.ToArray())); ctx.Chat.Events.Dispatch( "user:connect", DefaultChannel, user, new UserConnectEventData(!ctx.Chat.ChannelsUsers.Has(DefaultChannel, user)) ); ctx.Chat.HandleChannelEventLog(DefaultChannel.Name, p => ctx.Connection.Send(p)); ChannelInfo[] chans = ctx.Chat.Channels.GetMany(isPublic: true, minRank: user.Rank); List chanEntries = new(); foreach(ChannelInfo chanInfo in chans) chanEntries.Add(new( chanInfo.Name, chanInfo.HasPassword, chanInfo.IsTemporary )); ctx.Connection.Send(new ChannelsPopulateS2CPacket(chanEntries.ToArray())); } finally { ctx.Chat.ContextAccess.Release(); } }).Wait(); } } }