public override async Task UserStateChanged(Murmur.User user) { using (var context = await FancyContext.Connect()) using (var transact = context.Database.BeginTransaction()) { var current = await context.Logs.Where(x => x.Who.Id == user.userid) .OrderByDescending(x => x.When).Select(x => x.Where.Id).FirstAsync(); if (current != user.channel) { context.Logs.Add(new LogEntry.ChannelSwitched { When = DateTimeOffset.Now, Who = context.Users.Attach(new User { Id = user.userid }), Where = await context.Channels.SingleAsync(x => x.ServerId == user.channel), }); await context.SaveChangesAsync(); } transact.Commit(); } }
public override async Task <Wrapped.AuthenticatorUpdateResult> SetInfo(int id, Dictionary <Murmur.UserInfo, string> info) { using (var context = await FancyContext.Connect()) using (var transact = context.Database.BeginTransaction(IsolationLevel.Serializable)) { var user = await context.Users.Include(x => x.Membership).SingleAsync(x => x.Id == id); foreach (var kv in info) { switch (kv.Key) { case Murmur.UserInfo.UserComment: user.Membership.Comment = kv.Value; break; default: System.Diagnostics.Trace.WriteLine(kv.Key, "Unhandled thing in SetInfo"); transact.Rollback(); return(Wrapped.AuthenticatorUpdateResult.Failure); } } await context.SaveChangesAsync(); transact.Commit(); return(Wrapped.AuthenticatorUpdateResult.Success); } }
public override async Task UserConnected(Murmur.User user) { using (var context = await FancyContext.Connect()) using (var transact = context.Database.BeginTransaction(IsolationLevel.Serializable)) { var userNotificationsQuery = from usr in context.Users.Include(x => x.PersistentGuest.Godfathers) .Include(x => x.GuestInvite.Inviter).Include(x => x.Membership) where usr.Id == user.userid join evt in context.Logs.OfType <LogEntry.Connected>() on usr.Id equals evt.Who.Id into connectedEvents let lastConnection = connectedEvents.Max(x => x.When) join notific in context.OfflineNotifications on usr.Id equals notific.Recipient.Id into notifications select new { usr, notifications = notifications.Where(x => x.When > lastConnection) }; var res = await userNotificationsQuery.SingleAsync(); foreach (var notify in res.notifications) { await Server.SendMessage(user.session, notify.Message); } if (res.usr.Membership == null) { var onlineUsers = await Server.GetUsers(); var godfathers = res.usr.PersistentGuest?.Godfathers?.Select(x => x.UserId) ?? new[] { res.usr.GuestInvite.Inviter.Id }; if (!godfathers.Intersect(onlineUsers.Select(x => x.Value.userid)).Any()) { await Server.KickUser(user.session, "Inviter not online."); return; } if (res.usr.GuestInvite != null) { // move guest to inviter var inviter = onlineUsers.Single(x => x.Value.userid == res.usr.GuestInvite.InviterId); user.channel = inviter.Value.channel; user.suppress = false; await Server.SetState(user); } } context.Logs.Add(new LogEntry.Connected { When = DateTimeOffset.Now, Who = res.usr, Where = await context.Channels.SingleAsync(x => x.ServerId == user.channel), }); await context.SaveChangesAsync(); transact.Commit(); } }
public override async Task UserTextMessage(Murmur.User user, Murmur.TextMessage message) { using (var context = await FancyContext.Connect()) using (var transact = context.Database.BeginTransaction()) { User senderEntity = null; if (user.userid > 0) { senderEntity = await context.Users.FindAsync(user.userid); } var qtext = message.text.Replace(""", "\""); var msg = CommandPattern.Matches(qtext).Cast <Match>().Select(m => m.Value).ToArray(); if (message.channels.Any()) { if (msg[0] == "@fancy-ng") { await CommandMgr.HandleCommand(SteamListener, Server, user, msg.Skip(1)); } if (senderEntity != null) { context.Logs.Add(new LogEntry.ChatMessage { When = DateTimeOffset.Now, Who = senderEntity, Where = await context.Channels.SingleAsync(x => x.ServerId == user.channel), Message = message.text }); } } if (senderEntity != null) { var messagesInTheLastSeconds = await context.Logs.OfType <LogEntry.ChatMessage>() .Where(x => x.Who.Id == senderEntity.Id && x.When > DbFunctions.AddSeconds(DateTimeOffset.Now, -5)).CountAsync(); if (messagesInTheLastSeconds >= 3) { await Server.KickUser(user.session, "Who are you, my evil twin?! [stop spamming]"); } } await context.SaveChangesAsync(); transact.Commit(); } }
public override async Task ChannelRemoved(Murmur.Channel chan) { using (var context = await FancyContext.Connect()) { var channel = await context.Channels.Where(x => x.ServerId == chan.id).SingleAsync(); channel.ServerId = null; context.ChannelInfoChanges.Add(new Channel.InfoChange { Channel = channel, Name = null, Description = null, When = DateTimeOffset.Now, }); await context.SaveChangesAsync(); } }
public override async Task <Wrapped.AuthenticatorUpdateResult> SetTexture(int id, byte[] texture) { using (var context = await FancyContext.Connect()) using (var transact = context.Database.BeginTransaction(IsolationLevel.Serializable)) { var entity = new Membership { UserId = id, Texture = texture }; context.Memberships.Attach(entity); context.Entry(entity).Property(x => x.Texture).IsModified = true; context.Configuration.ValidateOnSaveEnabled = false; try { await context.SaveChangesAsync(); } catch (Exception) { return(Wrapped.AuthenticatorUpdateResult.Failure); } transact.Commit(); return(Wrapped.AuthenticatorUpdateResult.Success); } }
public override async Task UserDisconnected(Murmur.User user) { Model.UserAttribute.CertificateCredentials cc; Fancyauth.GuestCredentials.TryRemove(user.userid, out cc); using (var context = await FancyContext.Connect()) using (var transact = context.Database.BeginTransaction()) { context.Logs.Add(new LogEntry.Disconnected { When = DateTimeOffset.Now, Who = context.Users.Attach(new User { Id = user.userid }), Where = await context.Channels.SingleAsync(x => x.ServerId == user.channel), }); await context.SaveChangesAsync(); transact.Commit(); } }
public override async Task ChannelStateChanged(Murmur.Channel chan) { using (var context = await FancyContext.Connect()) { var query = from channel in context.Channels where channel.ServerId == chan.id join ichange in context.ChannelInfoChanges on channel.Id equals ichange.Channel.Id into infoChanges select new { channel, parentId = channel.Parent.ServerId, name = infoChanges.OrderByDescending(x => x.When).Select(x => x.Name).Where(x => x != null).FirstOrDefault(), desc = infoChanges.OrderByDescending(x => x.When).Select(x => x.Description).Where(x => x != null).FirstOrDefault(), }; var res = await query.SingleAsync(); var infoChange = new Channel.InfoChange { Channel = res.channel, Name = chan.name == res.name ? null : chan.name, Description = chan.description == res.desc ? null : chan.description, When = DateTimeOffset.Now, }; if (res.parentId != chan.parent) { res.channel.Parent = await context.Channels.Where(x => x.ServerId == chan.parent).SingleAsync(); } if (infoChange.Name != null || infoChange.Description != null) { context.ChannelInfoChanges.Add(infoChange); } await context.SaveChangesAsync(); } }
public override async Task ChannelCreated(Murmur.Channel chan) { using (var context = await FancyContext.Connect()) using (var transact = context.Database.BeginTransaction()) { var dbchan = context.Channels.Add(new Channel { Temporary = chan.temporary, Parent = await context.Channels.Where(x => x.ServerId == chan.parent).SingleAsync(), ServerId = chan.id, }); context.ChannelInfoChanges.Add(new Channel.InfoChange { Channel = dbchan, Name = chan.name, Description = chan.description, When = DateTimeOffset.Now }); await context.SaveChangesAsync(); transact.Commit(); } }
public override async Task <Wrapped.AuthenticationResult> Authenticate(string name, string pw, byte[][] certificates, string certhash, bool certstrong) { string fingerprint = null; long? certSerial = null; string subCN = null; Org.BouncyCastle.X509.X509Certificate bouncyCert = null; if (certificates.Length > 0) { var certs = certificates.Select(x => new X509Certificate2(x)).ToArray(); var usercert = certs.Last(); var chain = new X509Chain(); foreach (var cert in certs) { chain.ChainPolicy.ExtraStore.Add(cert); } chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; chain.Build(usercert); bouncyCert = new X509CertificateParser().ReadCertificate(certificates.Last()); var subDN = bouncyCert.SubjectDN; subCN = subDN.GetValueList()[subDN.GetOidList().IndexOf(X509ObjectIdentifiers.CommonName)] as string; certSerial = bouncyCert.SerialNumber.LongValue; fingerprint = usercert.Thumbprint; } else { // oh @moritzuehling y u do dis to me Q_Q // (no certs at all) } CertificateCredentials creds = null; if (certSerial.HasValue) { creds = new CertificateCredentials { Fingerprint = fingerprint, CertSerial = certSerial.Value, } } ; User user; bool isGuest = false; using (var context = await FancyContext.Connect()) using (var transact = context.Database.BeginTransaction(IsolationLevel.Serializable)) { user = await context.Users.Include(x => x.Membership).Include(x => x.PersistentGuest) .Include(x => x.PersistentGuest.Godfathers).SingleOrDefaultAsync(x => x.CertCredentials.Any(y => y.Fingerprint == fingerprint)); if (user != null) { // Known user. Why? // * member // * persistent guest // // As this is the /authenticator/, we can't query online users because that would deadlock murmur's main thread. // As someone with CertificateCredentials is definitely allowed to connect, we just let them pass here and // kick them in OnUserConnected if they're missing a godfather. } else { // Unknown user. Why? // * temporary guest // * new cert for existing user // * new user // * random person on the internet // Let's first check for guest invites pw = pw.Trim(); var invite = await context.Invites.SingleOrDefaultAsync(x => (x.Code == pw) && (x.ExpirationDate > DateTimeOffset.Now)); if (invite != null) { // Try to match by name. user = await context.Users.Include(x => x.PersistentGuest).SingleOrDefaultAsync(x => x.Name == name); if (user != null) { if (user.GuestInvite == null) { // In case the name is already taken by a non-guest, we force them to a // random but different name so everyone can see they're a guest. name += "-guest-" + Guid.NewGuid().ToString(); user = null; } else { // The account once belonged to a guest? Nice. // But adjust to the new guest invite. user.GuestInvite = invite; // (Note that we don't care about the edge case where guests get ghost-kicked // by other guests because we simply don't care about guests.) } } if (user == null) { // Create a new user for this name if we need to. user = context.Users.Add(new User { Name = name, GuestInvite = invite, }); } isGuest = true; } else { // random person on the internet; has no signed or valid certificate if (!certstrong) { return(Wrapped.AuthenticationResult.Forbidden()); } // New cert for existing user? /* * not longer supported * foreach (System.Collections.ICollection sans in bouncyCert.GetSubjectAlternativeNames()) * { * var enm = sans.GetEnumerator(); * enm.MoveNext(); * enm.MoveNext(); * var val = enm.Current as string; * var match = Regex.Match(val ?? String.Empty, "^([^@]*)@user.mumble.ehvag.de$"); * if (match.Success) * { * var oldName = match.Groups[1].Captures[0].Value; * var existingUser = await context.Users.Include(x => x.CertCredentials).SingleOrDefaultAsync(x => x.Name == oldName && x.CertCredentials.CertSerial < certSerial); * if (existingUser != null) * { * existingUser.Name = subCN; * existingUser.CertCredentials.CertSerial = certSerial.Value; * existingUser.CertCredentials.Fingerprint = fingerprint; * user = existingUser; * break; * } * } * } */ if (user == null) { // no existing user found, so create new user user = context.Users.Add(new User { Name = subCN, CertCredentials = new List <CertificateCredentials> { creds }, Membership = new Membership() }); } } } // As stated above, we can't query mumble for connected users to reject persistent guests. // However, we can use the logs in the database as a heuristic to get currently connected users. if (user.PersistentGuest != null && !isGuest) { var godfathersQuery = from usr in context.Users from godfathership in usr.Membership.Godfatherships where godfathership.UserId == user.Id join e in context.Logs.OfType <LogEntry.Connected>() on usr.Id equals e.Who.Id into connectEvents join e in context.Logs.OfType <LogEntry.Disconnected>() on usr.Id equals e.Who.Id into disconnectEvents select new { Con = connectEvents.Max(x => x.When), Dis = disconnectEvents.Max(x => x.When) }; var godfatherConnected = await godfathersQuery.AnyAsync(l => l.Con > l.Dis); if (!godfatherConnected) { return(Wrapped.AuthenticationResult.Forbidden()); } } await context.SaveChangesAsync(); transact.Commit(); } if (isGuest && (creds != null)) { Fancyauth.GuestCredentials.AddOrUpdate(user.Id, creds, (k, c) => creds); } string[] groups = null; if (user.PersistentGuest != null) { groups = PersistentGuestGroups; } return(Wrapped.AuthenticationResult.Success(user.Id, user.Name, groups)); }
public async Task UpdateChannelModel(Server server) { var tree = await server.GetTree(); using (var context = await FancyContext.Connect()) using (var transact = context.Database.BeginTransaction()) { var allChanQuery = from channel in context.Channels join ichange in context.ChannelInfoChanges on channel.Id equals ichange.Channel.Id into infoChanges select new { channel, name = infoChanges.OrderByDescending(x => x.When).Select(x => x.Name).Where(x => x != null).FirstOrDefault(), desc = infoChanges.OrderByDescending(x => x.When).Select(x => x.Description).Where(x => x != null).FirstOrDefault(), }; var allChans = await allChanQuery.ToArrayAsync(); var hitChans = new List <Channel>(); var treewalk = new Queue <Murmur.Tree>(); treewalk.Enqueue(tree); while (treewalk.Any()) { var current = treewalk.Dequeue(); var dbChanBig = allChans.Where(x => x.channel.ServerId == current.c.id).SingleOrDefault(); var dbChan = dbChanBig == null ? null : dbChanBig.channel; if (dbChan == null) { dbChan = context.Channels.Add(new Channel { Temporary = current.c.temporary, Parent = current.c.parent == -1 ? null : hitChans.Where(x => x.ServerId == current.c.parent).Single(), ServerId = current.c.id, }); context.ChannelInfoChanges.Add(new Channel.InfoChange { Channel = dbChan, Name = current.c.name, Description = current.c.description, When = DateTimeOffset.Now }); } else if ((dbChanBig.name != current.c.name) || (dbChanBig.desc != current.c.description)) { // existing, but modified context.ChannelInfoChanges.Add(new Channel.InfoChange { Channel = dbChan, Name = current.c.name == dbChanBig.name ? null : current.c.name, Description = current.c.description == dbChanBig.desc ? null : current.c.description, When = DateTimeOffset.Now, }); } hitChans.Add(dbChan); foreach (var child in current.children) { treewalk.Enqueue(child); } } foreach (var channel in allChans.Select(x => x.channel).Except(hitChans)) { channel.ServerId = null; context.ChannelInfoChanges.Add(new Channel.InfoChange { Channel = channel, Name = null, Description = null, When = DateTimeOffset.Now, }); } await context.SaveChangesAsync(); transact.Commit(); } }