Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
PersistenceLayer.cs
1using System;
3using System.IO;
4using System.Text;
5using System.Threading.Tasks;
6using System.Xml;
7using Waher.Content;
10using Waher.Events;
23using Waher.Script;
27using Waher.Security;
32
34{
35 public delegate Task PushNotificationEventHandler(PushNotificationToken Token, object Content);
36
39 {
43 public const string OAuthApiKeyName = "OAUTH";
44
45 private static readonly Dictionary<CaseInsensitiveString, Dictionary<string, Dictionary<string, string>>> functionsPerTabIdPerFqnPerBareJid = new Dictionary<CaseInsensitiveString, Dictionary<string, Dictionary<string, string>>>();
46
47 private readonly Dictionary<CaseInsensitiveString, Account> accounts = new Dictionary<CaseInsensitiveString, Account>();
48 private readonly Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, RosterItem>> rosters = new Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, RosterItem>>();
49 private readonly Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Block>> blocks = new Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Block>>();
50 private XmppServer server = null;
51
52 public PersistenceLayer()
53 {
54 }
55
60 {
61 get => this.server;
62 set
63 {
64 if (this.server is null)
65 this.server = value;
66 else if (this.server != value)
67 throw new InvalidOperationException("Not allowed to change server reference in runtime.");
68 }
69 }
70
71 public string Domain => this.server.Domain;
73
74 #region API keys
75
81 internal async Task<ApiKey> GetApiKey(string ApiKey)
82 {
83 return await Database.FindFirstDeleteRest<ApiKey>(new FilterFieldEqualTo("Key", ApiKey));
84 }
85
91 public async Task<string> GetApiKeySecret(string ApiKey)
92 {
93 return (await this.GetApiKey(ApiKey))?.Secret;
94 }
95
96 #endregion
97
98 #region Accounts
99
100 internal async Task<Account> GetAccountEx(CaseInsensitiveString UserName)
101 {
102 lock (this.accounts)
103 {
104 if (this.accounts.TryGetValue(UserName, out Account Account))
105 return Account;
106 }
107
108 foreach (Account Account in await Database.Find<Account>(new FilterFieldEqualTo("UserName", UserName)))
109 {
110 lock (this.accounts)
111 {
112 this.accounts[Account.UserName] = Account;
113 }
114
115 return Account;
116 }
117
118 return null;
119 }
120
132 public Task<KeyValuePair<Networking.XMPP.Server.IAccount, string[]>> CreateAccount(string ApiKey, CaseInsensitiveString UserName, string Password,
133 CaseInsensitiveString EMail, CaseInsensitiveString PhoneNr, string RemoteEndPoint)
134 {
135 return this.CreateAccount(ApiKey, UserName, Password, EMail, PhoneNr, RemoteEndPoint, true);
136 }
137
150 public async Task<KeyValuePair<Networking.XMPP.Server.IAccount, string[]>> CreateAccount(
151 string ApiKey, CaseInsensitiveString UserName, string Password,
153 string RemoteEndPoint, bool Enabled)
154 {
155 ApiKey ApiKeyObject = null;
156 Account Account = await this.GetAccountEx(UserName);
157 if (!(Account is null))
158 return new KeyValuePair<Networking.XMPP.Server.IAccount, string[]>(null, await GetAlternativeAccountNames(UserName));
159
160 IUser User = await Users.Source.TryGetUser(UserName);
161 if (!(User is null))
162 return new KeyValuePair<Networking.XMPP.Server.IAccount, string[]>(null, await GetAlternativeAccountNames(UserName));
163
164 if (!(XmppServerModule.PubSub is null))
165 {
166 PubSubNode Node = await XmppServerModule.PubSub.GetNodeAsync(string.Empty, UserName, null, XmppAddress.Empty, null);
167 if (!(Node is null))
168 return new KeyValuePair<Networking.XMPP.Server.IAccount, string[]>(null, await GetAlternativeAccountNames(UserName));
169 }
170
171 try
172 {
173 string s = string.IsNullOrEmpty(Gateway.RootFolder) ? UserName.Value :
174 Path.Combine(Gateway.RootFolder, UserName.Value);
175
176 if (File.Exists(s) || Directory.Exists(s))
177 return new KeyValuePair<Networking.XMPP.Server.IAccount, string[]>(null, await GetAlternativeAccountNames(UserName));
178 }
179 catch (Exception)
180 {
181 // Ignore.
182 }
183
184 if (string.IsNullOrEmpty(ApiKey))
185 {
186 Account = new Account()
187 {
188 ApiKey = string.Empty,
189 UserName = UserName,
190 Password = Password,
191 EMail = EMail,
192 EMailVerified = null,
193 PhoneNr = PhoneNr,
194 PhoneNrVerified = null,
195 Created = DateTime.UtcNow,
196 Enabled = Enabled
197 };
198
199 await Database.Insert(Account);
201 }
202 else
203 {
204 foreach (ApiKey Key in await Database.Find<ApiKey>(new FilterFieldEqualTo("Key", ApiKey)))
205 {
206 if (Key.NrCreated - Key.NrDeleted >= Key.MaxAccounts)
207 return new KeyValuePair<Networking.XMPP.Server.IAccount, string[]>(null, null);
208
209 ApiKeyObject = Key;
210
211 Account = new Account()
212 {
213 ApiKey = ApiKey,
214 UserName = UserName,
215 Password = Password,
216 EMail = EMail,
217 EMailVerified = null,
218 PhoneNr = PhoneNr,
219 PhoneNrVerified = null,
220 Created = DateTime.UtcNow,
221 Enabled = Enabled
222 };
223
224 await Database.Insert(Account);
226
227 Key.NrCreated++;
228 await Database.Update(Key);
229
230 break;
231 }
232 }
233
234 if (!(Account is null) && !(XmppServerModule.Instance is null))
235 await AccountCreated(Account, ApiKeyObject, this.server.Domain, RemoteEndPoint);
236
237 return new KeyValuePair<Networking.XMPP.Server.IAccount, string[]>(Account, null);
238 }
239
240 internal static async Task<string[]> GetAlternativeAccountNames(string UserName)
241 {
242 List<string> Suggestions = new List<string>();
243 int NrDigits = 2;
244 int Max = 100;
245
246 while (Suggestions.Count < 3 && NrDigits < 9)
247 {
248 string AlternativeName = UserName + Gateway.NextInteger(Max).ToString("D" + NrDigits.ToString());
249
250 Account AlternativeAccount = await Database.FindFirstIgnoreRest<Account>(
251 new FilterFieldEqualTo("UserName", AlternativeName));
252
253 if (!(AlternativeAccount is null))
254 {
255 NrDigits++;
256 Max *= 10;
257 continue;
258 }
259
260 if (!(XmppServerModule.PubSub is null))
261 {
262 PubSubNode Node = await XmppServerModule.PubSub.GetNodeAsync(string.Empty, AlternativeName, null, XmppAddress.Empty, null);
263 if (!(Node is null))
264 {
265 NrDigits++;
266 Max *= 10;
267 continue;
268 }
269 }
270
271 try
272 {
273 string s = Path.Combine(Gateway.RootFolder, AlternativeName);
274
275 if (File.Exists(s) || Directory.Exists(s))
276 {
277 NrDigits++;
278 Max *= 10;
279 continue;
280 }
281 }
282 catch (Exception)
283 {
284 // Ignore.
285 }
286
287 Suggestions.Add(AlternativeName);
288 }
289
290 Suggestions.Sort();
291
292 return Suggestions.ToArray();
293 }
294
302 public static async Task AccountCreated(Account Account, ApiKey ApiKeyObject,
303 string Domain, string RemoteEndPoint)
304 {
305 await Account.LoggedIn(RemoteEndPoint);
306
307 KeyValuePair<string, object>[] Tags = await LoginAuditor.Annotate(RemoteEndPoint,
308 new KeyValuePair<string, object>("ApiKey", Account.ApiKey),
309 new KeyValuePair<string, object>("Created", Account.Created),
310 new KeyValuePair<string, object>("Updated", Account.Updated),
311 new KeyValuePair<string, object>("EMail", Account.EMail?.Value),
312 new KeyValuePair<string, object>("PhoneNr", Account.PhoneNr?.Value),
313 new KeyValuePair<string, object>("Enabled", Account.Enabled),
314 new KeyValuePair<string, object>("ObjectId", Account.ObjectId),
315 new KeyValuePair<string, object>("RemoteEndpoint", RemoteEndPoint));
316
317 StringBuilder sb = new StringBuilder();
318 string WhoIsInfo = string.Empty;
319
320 sb.Append("Account created.");
321 await LoginAuditor.AppendWhoIsInfo(sb, RemoteEndPoint);
322
323 Log.Notice(sb.ToString(), Account.UserName, string.Empty, "AccountCreated", EventLevel.Medium, Tags);
324
326 {
327 StringBuilder Markdown = new StringBuilder();
328 string UserNameEncoded = MarkdownDocument.Encode(Account.UserName.Value);
329 DateTime UtcNow = DateTime.Now;
330
331 Markdown.AppendLine("XMPP Account created:");
332 Markdown.AppendLine();
333 Markdown.AppendLine("| Account Information ||");
334 Markdown.AppendLine("|:-----|:------|");
335 Markdown.Append("| User Name: | [");
336 Markdown.Append(UserNameEncoded);
337 Markdown.Append("](");
338 Markdown.Append(Gateway.GetUrl("/Account.md?UserName=" + UserNameEncoded));
339 Markdown.AppendLine(") |");
340 Markdown.Append("| JID: | [");
341 Markdown.Append(UserNameEncoded);
342 Markdown.Append('@');
343 Markdown.Append(Domain);
344 Markdown.Append("](xmpp:");
345 Markdown.Append(UserNameEncoded);
346 Markdown.Append('@');
347 Markdown.Append(Domain);
348 Markdown.Append(")");
349 Markdown.AppendLine(" |");
350
352 {
353 Markdown.Append("| e-Mail: | <mailto:");
354 Markdown.Append(Account.EMail.Value);
355 Markdown.AppendLine("> |");
356 }
357
359 {
360 Markdown.Append("| Phone Nr: | <tel:");
361 Markdown.Append(Account.PhoneNr.Value);
362 Markdown.AppendLine("> |");
363 }
364
365 await XmppServerModule.AppendRemoteEndPointToTable(Markdown, RemoteEndPoint);
366
367 Markdown.Append("| Date (UTC) | ");
368 Markdown.Append(MarkdownDocument.Encode(UtcNow.ToShortDateString()));
369 Markdown.AppendLine(" |");
370 Markdown.Append("| Time (UTC) | ");
371 Markdown.Append(MarkdownDocument.Encode(UtcNow.ToLongTimeString()));
372 Markdown.AppendLine(" |");
373
374 if (!(ApiKeyObject is null))
375 {
376 Markdown.AppendLine();
377 Markdown.AppendLine("| API Key information ||");
378 Markdown.AppendLine("|:-----|:------|");
379 Markdown.Append("| API Key: | [");
380 Markdown.Append(ApiKeyObject.Key);
381 Markdown.Append("](");
382 Markdown.Append(Gateway.GetUrl("/ApiKey.md?Key=" + ApiKeyObject.Key));
383 Markdown.AppendLine(") |");
384 Markdown.Append("| Owner: | ");
385 Markdown.Append(MarkdownDocument.Encode(ApiKeyObject.Owner));
386 Markdown.AppendLine(" |");
387 Markdown.Append("| e-Mail: | <");
388 Markdown.Append(ApiKeyObject.EMail.Value);
389 Markdown.AppendLine("> |");
390 Markdown.Append("| Accounts created: | ");
391 Markdown.Append(ApiKeyObject.NrCreated.ToString());
392 Markdown.AppendLine(" |");
393 Markdown.Append("| Accounts deleted: | ");
394 Markdown.Append(ApiKeyObject.NrDeleted.ToString());
395 Markdown.AppendLine(" |");
396 Markdown.Append("| Accounts left: | ");
397 Markdown.Append((ApiKeyObject.MaxAccounts - ApiKeyObject.NrCreated + ApiKeyObject.NrDeleted).ToString());
398 Markdown.AppendLine(" |");
399 }
400
401 if (!string.IsNullOrEmpty(WhoIsInfo))
402 {
403 Markdown.AppendLine();
404 Markdown.AppendLine();
405 Markdown.AppendLine("WHOIS Information:");
406 Markdown.AppendLine();
407 Markdown.AppendLine("```");
408 Markdown.AppendLine(WhoIsInfo);
409 Markdown.AppendLine("```");
410 }
411
412 await Gateway.SendNotification(Markdown.ToString());
413 }
414 }
415
422 public async Task<bool> DeleteAccount(CaseInsensitiveString UserName, string RemoteEndPoint)
423 {
424 ApiKey ApiKeyObject = null;
425 Account Account = await this.GetAccountEx(UserName);
426 if (Account is null)
427 return false;
428
430
431 await Database.Delete(Account);
432 await Database.Delete(Login);
433
435
436 lock (this.accounts)
437 {
438 this.accounts.Remove(UserName);
439 }
440
441 if (!string.IsNullOrEmpty(Account.ApiKey))
442 {
443 foreach (ApiKey Key in await Database.Find<ApiKey>(new FilterFieldEqualTo("Key", Account.ApiKey)))
444 {
445 ApiKeyObject = Key;
446 Key.NrDeleted++;
447 await Database.Update(Key);
448
449 break;
450 }
451 }
452
453 KeyValuePair<string, object>[] Tags = await LoginAuditor.Annotate(Login.RemoteEndPoint,
454 new KeyValuePair<string, object>("ApiKey", Account.ApiKey),
455 new KeyValuePair<string, object>("Created", Account.Created),
456 new KeyValuePair<string, object>("Updated", Account.Updated),
457 new KeyValuePair<string, object>("EMail", Account.EMail?.Value),
458 new KeyValuePair<string, object>("PhoneNr", Account.PhoneNr?.Value),
459 new KeyValuePair<string, object>("Enabled", Account.Enabled),
460 new KeyValuePair<string, object>("LastLogin", Login.LastLogin),
461 new KeyValuePair<string, object>("ObjectId", Account.ObjectId),
462 new KeyValuePair<string, object>("RemoteEndpoint", Login.RemoteEndPoint));
463
464 Log.Notice("Account deleted.", Account.UserName, string.Empty, "AccountDeleted", EventLevel.Medium, Tags);
465
466 if (!(XmppServerModule.Instance is null) &&
468 {
469 StringBuilder Markdown = new StringBuilder();
470 string UserNameEncoded = MarkdownDocument.Encode(UserName);
471 DateTime UtcNow = DateTime.UtcNow;
472
473 Markdown.AppendLine("XMPP Account deleted:");
474 Markdown.AppendLine();
475 Markdown.AppendLine("| Account Information ||");
476 Markdown.AppendLine("|:-----|:------|");
477 Markdown.Append("| User Name: | ");
478 Markdown.Append(UserNameEncoded);
479 Markdown.AppendLine(" |");
480 Markdown.Append("| JID: | ");
481 Markdown.Append(UserNameEncoded);
482 Markdown.Append('@');
483 Markdown.Append(this.server.Domain);
484 Markdown.AppendLine(" |");
485
487 {
488 Markdown.Append("| e-Mail: | <mailto:");
489 Markdown.Append(Account.EMail.Value);
490 Markdown.AppendLine("> |");
491 }
492
494 {
495 Markdown.Append("| Phone Nr: | <tel:");
496 Markdown.Append(Account.PhoneNr.Value);
497 Markdown.AppendLine("> |");
498 }
499
500 await XmppServerModule.AppendRemoteEndPointToTable(Markdown, RemoteEndPoint);
501
502 Markdown.Append("| Date (UTC) | ");
503 Markdown.Append(MarkdownDocument.Encode(UtcNow.ToShortDateString()));
504 Markdown.AppendLine(" |");
505 Markdown.Append("| Time (UTC) | ");
506 Markdown.Append(MarkdownDocument.Encode(UtcNow.ToLongTimeString()));
507 Markdown.AppendLine(" |");
508
509 if (!(ApiKeyObject is null))
510 {
511 Markdown.AppendLine();
512 Markdown.AppendLine("| API Key information ||");
513 Markdown.AppendLine("|:-----|:------|");
514 Markdown.Append("| API Key: | [");
515 Markdown.Append(Account.ApiKey);
516 Markdown.Append("](");
517 Markdown.Append(Gateway.GetUrl("/ApiKey.md?Key=" + Account.ApiKey));
518 Markdown.AppendLine(") |");
519 Markdown.Append("| Owner: | ");
520 Markdown.Append(MarkdownDocument.Encode(ApiKeyObject.Owner));
521 Markdown.AppendLine(" |");
522 Markdown.Append("| e-Mail: | <");
523 Markdown.Append(ApiKeyObject.EMail);
524 Markdown.AppendLine("> |");
525 Markdown.Append("| Accounts created: | ");
526 Markdown.Append(ApiKeyObject.NrCreated.ToString());
527 Markdown.AppendLine(" |");
528 Markdown.Append("| Accounts deleted: | ");
529 Markdown.Append(ApiKeyObject.NrDeleted.ToString());
530 Markdown.AppendLine(" |");
531 Markdown.Append("| Accounts left: | ");
532 Markdown.Append((ApiKeyObject.MaxAccounts - ApiKeyObject.NrCreated + ApiKeyObject.NrDeleted).ToString());
533 Markdown.AppendLine(" |");
534 }
535
536 await Gateway.SendNotification(Markdown.ToString());
537 }
538
539 return true;
540 }
541
548 public async Task<bool> ChangePassword(CaseInsensitiveString UserName, string Password)
549 {
550 Account Account = await this.GetAccountEx(UserName);
551 if (Account is null)
552 return false;
553
554 if (Account.Password != Password)
555 {
556 Account.Password = Password;
557 Account.Updated = DateTime.UtcNow;
558
559 await Database.Update(Account);
560
561 Log.Informational("Password updated.", Account.UserName, string.Empty, "PasswordUpdated", EventLevel.Medium);
562 }
563
564 return true;
565 }
566
573 public async void AccountLogin(CaseInsensitiveString UserName, string RemoteEndPoint)
574 {
575 try
576 {
577 Account Account = await this.GetAccountEx(UserName);
578 if (!(Account is null))
579 await Account.LoggedIn(RemoteEndPoint);
580 }
581 catch (Exception ex)
582 {
583 Log.Exception(ex);
584 }
585 }
586
587 #endregion
588
589 #region Roster
590
596 public async Task<IEnumerable<IRosterItem>> GetRoster(CaseInsensitiveString UserName)
597 {
598 Dictionary<CaseInsensitiveString, RosterItem> Roster;
599
600 lock (this.rosters)
601 {
602 if (this.rosters.TryGetValue(UserName, out Roster))
603 return this.ToArrayLocked(Roster);
604 }
605
606 if (await this.GetAccountEx(UserName) is null)
607 return null;
608
609 Roster = new Dictionary<CaseInsensitiveString, RosterItem>();
610
611 foreach (RosterItem Item in await Database.Find<RosterItem>(new FilterFieldEqualTo("UserName", UserName)))
612 {
613 if (Roster.ContainsKey(Item.BareJid))
614 await Database.Delete(Item);
615 else
616 Roster[Item.BareJid] = Item;
617 }
618
619 lock (this.rosters)
620 {
621 this.rosters[UserName] = Roster;
622 return this.ToArrayLocked(Roster);
623 }
624 }
625
626 private IRosterItem[] ToArrayLocked(Dictionary<CaseInsensitiveString, RosterItem> Roster)
627 {
628 IRosterItem[] Result;
629
630 int i = 0;
631 int c = Roster.Count;
632
633 Result = new IRosterItem[c];
634 foreach (RosterItem Item in Roster.Values)
635 Result[i++] = Item;
636
637 return Result;
638 }
639
646 public async Task<IRosterItem> GetRosterItem(CaseInsensitiveString UserName, CaseInsensitiveString Jid)
647 {
648 lock (this.rosters)
649 {
650 if (this.rosters.TryGetValue(UserName, out Dictionary<CaseInsensitiveString, RosterItem> Roster) &&
651 Roster.TryGetValue(Jid, out RosterItem Item))
652 {
653 return Item;
654 }
655 }
656
657 if (await this.GetRoster(UserName) is null)
658 return null;
659
660 lock (this.rosters)
661 {
662 if (this.rosters.TryGetValue(UserName, out Dictionary<CaseInsensitiveString, RosterItem> Roster) &&
663 Roster.TryGetValue(Jid, out RosterItem Item))
664 {
665 return Item;
666 }
667 }
668
669 return null;
670 }
671
682 public async Task<IRosterItem> SetRosterItem(CaseInsensitiveString UserName, CaseInsensitiveString Jid, string Name, SubscriptionStatus? Subscription, bool? PendingSubscription,
683 string[] Groups)
684 {
685 Dictionary<CaseInsensitiveString, RosterItem> Roster;
686 RosterItem Item;
687 bool Load = false;
688
689 lock (this.rosters)
690 {
691 if (this.rosters.TryGetValue(UserName, out Roster))
692 {
693 if (!Roster.TryGetValue(Jid, out Item))
694 Item = null;
695 }
696 else
697 {
698 Item = null;
699 Load = true;
700 }
701 }
702
703 if (Load)
704 {
705 if (await this.GetRoster(UserName) is null)
706 return null;
707
708 lock (this.rosters)
709 {
710 if (this.rosters.TryGetValue(UserName, out Roster))
711 {
712 if (!Roster.TryGetValue(Jid, out Item))
713 Item = null;
714 }
715 else
716 return null;
717 }
718 }
719
720 if (!(Item is null))
721 {
722 Item.Name = Name;
723 Item.Groups = Groups;
724
725 if (Subscription.HasValue)
726 Item.Subscription = Subscription.Value;
727
728 if (PendingSubscription.HasValue)
729 Item.PendingSubscription = PendingSubscription.Value;
730
731 await Database.Update(Item);
732
733 Log.Informational("Roster item updated.", Item.BareJid, UserName, "RosterItemUpdated", EventLevel.Minor,
734 new KeyValuePair<string, object>("Name", Item.Name),
735 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
736 new KeyValuePair<string, object>("PendingSubscription", Item.PendingSubscription),
737 new KeyValuePair<string, object>("Subscription", Item.Subscription),
738 new KeyValuePair<string, object>("NrGroups", Item.Groups is null ? 0 : Item.Groups.Length));
739
740 return Item;
741 }
742
743 lock (this.rosters)
744 {
745 Item = new RosterItem()
746 {
747 UserName = UserName,
748 BareJid = Jid,
749 Name = Name,
750 Groups = Groups,
753 };
754
755 Roster[Jid] = Item;
756
757 Log.Informational("Roster item created.", Item.BareJid, UserName, "RosterItemCreated", EventLevel.Minor,
758 new KeyValuePair<string, object>("Name", Item.Name),
759 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
760 new KeyValuePair<string, object>("PendingSubscription", Item.PendingSubscription),
761 new KeyValuePair<string, object>("Subscription", Item.Subscription),
762 new KeyValuePair<string, object>("NrGroups", Item.Groups is null ? 0 : Item.Groups.Length));
763 }
764
765 await Database.Insert(Item);
766
767 return Item;
768 }
769
776 public async Task<bool> RemoveRosterItem(CaseInsensitiveString UserName, CaseInsensitiveString Jid)
777 {
778 Dictionary<CaseInsensitiveString, RosterItem> Roster;
779 RosterItem Item = null;
780
781 lock (this.rosters)
782 {
783 if (this.rosters.TryGetValue(UserName, out Roster))
784 {
785 if (Roster.TryGetValue(Jid, out Item))
786 Roster.Remove(Jid);
787 else
788 return false;
789 }
790 }
791
792 if (Item is null)
793 {
794 if (await this.GetRoster(UserName) is null)
795 return false;
796
797 lock (this.rosters)
798 {
799 if (this.rosters.TryGetValue(UserName, out Roster))
800 {
801 if (Roster.TryGetValue(Jid, out Item))
802 Roster.Remove(Jid);
803 else
804 return false;
805 }
806 }
807 }
808
809 await Database.Delete(Item);
810
811 Log.Informational("Roster item deleted.", Item.BareJid, UserName, "RosterItemDeleted", EventLevel.Minor,
812 new KeyValuePair<string, object>("Name", Item.Name),
813 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
814 new KeyValuePair<string, object>("PendingSubscription", Item.PendingSubscription),
815 new KeyValuePair<string, object>("Subscription", Item.Subscription),
816 new KeyValuePair<string, object>("NrGroups", Item.Groups is null ? 0 : Item.Groups.Length));
817
818 return true;
819 }
820
821 #endregion
822
823 #region Blocks
824
834 public async Task<bool> AddBlock(CaseInsensitiveString UserName, CaseInsensitiveString BareJid, BlockingReason Reason, string Text, string TextLanguage)
835 {
836 Dictionary<CaseInsensitiveString, Block> Blocks;
837 Block Item = null;
838 bool Load = false;
839 bool Found = false;
840
841 lock (this.blocks)
842 {
843 if (this.blocks.TryGetValue(UserName, out Blocks))
844 Found = Blocks.TryGetValue(BareJid, out Item);
845 else
846 Load = true;
847 }
848
849 if (Load)
850 {
851 if (await this.GetBlockList(UserName) is null)
852 return false;
853
854 lock (this.blocks)
855 {
856 if (this.blocks.TryGetValue(UserName, out Blocks))
857 Found = Blocks.TryGetValue(BareJid, out Item);
858 else
859 return false;
860 }
861 }
862
863 if (Found && !(Item is null))
864 {
865 Item.Reason = Reason;
866 Item.Text = Text;
867 Item.Language = TextLanguage;
868
869 await Database.Update(Item);
870
871 Log.Informational("Block updated.", Item.Jid, UserName, "BlockUpdated", EventLevel.Minor,
872 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
873 new KeyValuePair<string, object>("Reason", Item.Reason),
874 new KeyValuePair<string, object>("Text", Item.Text),
875 new KeyValuePair<string, object>("Language", Item.Language));
876
877 return true;
878 }
879
880 lock (this.blocks)
881 {
882 Item = new Block()
883 {
884 UserName = UserName,
885 Jid = BareJid,
886 Reason = Reason,
887 Text = Text,
888 Language = TextLanguage
889 };
890
891 Blocks[BareJid] = Item;
892 }
893
894 await Database.Insert(Item);
895
896 Log.Informational("Block added.", Item.Jid, UserName, "BlockAdded", EventLevel.Minor,
897 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
898 new KeyValuePair<string, object>("Reason", Item.Reason),
899 new KeyValuePair<string, object>("Text", Item.Text),
900 new KeyValuePair<string, object>("Language", Item.Language));
901
902 return true;
903 }
904
912 {
913 Dictionary<CaseInsensitiveString, Block> Blocks;
914 Block Item = null;
915
916 lock (this.blocks)
917 {
918 if (this.blocks.TryGetValue(UserName, out Blocks))
919 {
920 if (Blocks.TryGetValue(BareJid, out Item) && !(Item is null))
921 Blocks[BareJid] = null;
922 else
923 return false;
924 }
925 }
926
927 if (Item is null)
928 {
929 if (await this.GetBlockList(UserName) is null)
930 return false;
931
932 lock (this.blocks)
933 {
934 if (this.blocks.TryGetValue(UserName, out Blocks))
935 {
936 if (Blocks.TryGetValue(BareJid, out Item) && !(Item is null))
937 Blocks[BareJid] = null;
938 else
939 return false;
940 }
941 }
942 }
943
944 await Database.Delete(Item);
945
946 Log.Informational("Block removed.", Item.Jid, UserName, "BlockRemoved", EventLevel.Minor,
947 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
948 new KeyValuePair<string, object>("Reason", Item.Reason),
949 new KeyValuePair<string, object>("Text", Item.Text),
950 new KeyValuePair<string, object>("Language", Item.Language));
951
952 return true;
953 }
954
960 public async Task<bool> ClearBlocks(CaseInsensitiveString UserName)
961 {
962 Dictionary<CaseInsensitiveString, Block> Blocks;
963
964 lock (this.blocks)
965 {
966 if (this.blocks.TryGetValue(UserName, out Blocks))
967 this.blocks.Remove(UserName);
968 }
969
970 if (Blocks is null)
971 {
972 if (await this.GetBlockList(UserName) is null)
973 return false;
974
975 lock (this.blocks)
976 {
977 if (this.blocks.TryGetValue(UserName, out Blocks))
978 {
979 if (this.blocks.TryGetValue(UserName, out Blocks))
980 this.blocks.Remove(UserName);
981 else
982 return false;
983 }
984 }
985 }
986
987 foreach (Block Item in Blocks.Values)
988 {
989 await Database.Delete(Item);
990
991 Log.Informational("Block removed.", Item.Jid, UserName, "BlockRemoved", EventLevel.Minor,
992 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
993 new KeyValuePair<string, object>("Reason", Item.Reason),
994 new KeyValuePair<string, object>("Text", Item.Text),
995 new KeyValuePair<string, object>("Language", Item.Language));
996 }
997
998 return true;
999 }
1000
1006 public async Task<IEnumerable<CaseInsensitiveString>> GetBlockList(CaseInsensitiveString UserName)
1007 {
1008 Dictionary<CaseInsensitiveString, Block> Blocks;
1009
1010 lock (this.blocks)
1011 {
1012 if (this.blocks.TryGetValue(UserName, out Blocks))
1013 return this.ToArrayLocked(Blocks);
1014 }
1015
1016 if (await this.GetAccountEx(UserName) is null)
1017 return null;
1018
1019 Blocks = new Dictionary<CaseInsensitiveString, Block>();
1020
1021 foreach (Block Item in await Database.Find<Block>(new FilterFieldEqualTo("UserName", UserName)))
1022 Blocks[Item.Jid] = Item;
1023
1024 lock (this.blocks)
1025 {
1026 this.blocks[UserName] = Blocks;
1027 return this.ToArrayLocked(Blocks);
1028 }
1029 }
1030
1031 private CaseInsensitiveString[] ToArrayLocked(Dictionary<CaseInsensitiveString, Block> Blocks)
1032 {
1033 List<CaseInsensitiveString> Result = new List<CaseInsensitiveString>();
1034
1035 foreach (Block Item in Blocks.Values)
1036 {
1037 if (!(Item is null))
1038 Result.Add(Item.Jid);
1039 }
1040
1041 return Result.ToArray();
1042 }
1043
1044 private async Task<Block> GetBlock(CaseInsensitiveString UserName, CaseInsensitiveString Jid)
1045 {
1046 lock (this.blocks)
1047 {
1048 if (this.blocks.TryGetValue(UserName, out Dictionary<CaseInsensitiveString, Block> Blocks) &&
1049 Blocks.TryGetValue(Jid, out Block Block))
1050 {
1051 return Block;
1052 }
1053 }
1054
1055 if (await this.GetBlockList(UserName) is null)
1056 return null;
1057
1058 lock (this.blocks)
1059 {
1060 if (this.blocks.TryGetValue(UserName, out Dictionary<CaseInsensitiveString, Block> Blocks))
1061 {
1062 if (Blocks.TryGetValue(Jid, out Block Block))
1063 return Block;
1064 else
1065 Blocks[Jid] = null;
1066 }
1067 }
1068
1069 return null;
1070 }
1071
1078 public async Task<bool> IsBlocked(CaseInsensitiveString FromBareJid, CaseInsensitiveString ToBareJid)
1079 {
1080 int i = ToBareJid.IndexOf('@');
1081 if (i < 0)
1082 return false;
1083
1084 CaseInsensitiveString Domain = ToBareJid.Substring(i + 1);
1085 if (!this.server.IsServerDomain(Domain, true))
1086 return false;
1087
1088 CaseInsensitiveString ToUserName = ToBareJid.Substring(0, i);
1089 Block Block = await this.GetBlock(ToUserName, FromBareJid);
1090
1091 return !(Block is null);
1092 }
1093
1094 #endregion
1095
1096 #region Avatars
1097
1103 public async Task<Tuple<string, byte[]>> GetAvatar(CaseInsensitiveString UserName)
1104 {
1105 foreach (Avatar Avatar in await Database.Find<Avatar>(new FilterFieldEqualTo("UserName", UserName)))
1106 return new Tuple<string, byte[]>(Avatar.ContentType, Avatar.Data);
1107
1108 return null;
1109 }
1110
1118 public async Task<bool> SetAvatar(CaseInsensitiveString UserName, string ContentType, byte[] Data)
1119 {
1120 Account Account = await this.GetAccountEx(UserName);
1121 if (Account is null)
1122 return false;
1123
1124 foreach (Avatar Avatar in await Database.Find<Avatar>(new FilterFieldEqualTo("UserName", UserName)))
1125 {
1126 Avatar.ContentType = ContentType;
1127 Avatar.Data = Data;
1128
1129 await Database.Update(Avatar);
1130
1131 Log.Informational("Avatar updated.", UserName, string.Empty, "AvatarUpdated", EventLevel.Minor,
1132 new KeyValuePair<string, object>("ObjectId", Avatar.ObjectId),
1133 new KeyValuePair<string, object>("ContentType", Avatar.ContentType),
1134 new KeyValuePair<string, object>("Bytes", Avatar.Data.Length));
1135
1136 return true;
1137 }
1138
1139 Avatar Avatar2 = new Avatar()
1140 {
1141 UserName = UserName,
1142 ContentType = ContentType,
1143 Data = Data
1144 };
1145
1146 await Database.Insert(Avatar2);
1147
1148 Log.Informational("Avatar created.", UserName, string.Empty, "AvatarCreated", EventLevel.Minor,
1149 new KeyValuePair<string, object>("ObjectId", Avatar2.ObjectId),
1150 new KeyValuePair<string, object>("ContentType", Avatar2.ContentType),
1151 new KeyValuePair<string, object>("Bytes", Avatar2.Data.Length));
1152
1153 return true;
1154 }
1155
1156 #endregion
1157
1158 #region vCard
1159
1165 public async Task<string> GetVCard(CaseInsensitiveString UserName)
1166 {
1167 foreach (VCard VCard in await Database.Find<VCard>(new FilterFieldEqualTo("UserName", UserName)))
1168 return VCard.VCardXml;
1169
1170 return null;
1171 }
1172
1179 public async Task<bool> SetVCard(CaseInsensitiveString UserName, string VCard)
1180 {
1181 Account Account = await this.GetAccountEx(UserName);
1182 if (Account is null)
1183 return false;
1184
1185 foreach (VCard VCard2 in await Database.Find<VCard>(new FilterFieldEqualTo("UserName", UserName)))
1186 {
1187 VCard2.VCardXml = VCard;
1188 await Database.Update(VCard2);
1189
1190 Log.Informational("vCard updated.", UserName, string.Empty, "vCardUpdated", EventLevel.Minor,
1191 new KeyValuePair<string, object>("ObjectId", VCard2.ObjectId));
1192
1193 return true;
1194 }
1195
1196 VCard VCard3 = new VCard()
1197 {
1198 UserName = UserName,
1199 VCardXml = VCard
1200 };
1201
1202 await Database.Insert(VCard3);
1203
1204 Log.Informational("vCard created.", UserName, string.Empty, "vCardCreated", EventLevel.Minor,
1205 new KeyValuePair<string, object>("ObjectId", VCard3.ObjectId));
1206
1207 return true;
1208 }
1209
1210 #endregion
1211
1212 #region Settings
1213
1220 public Task<bool> IsPermitted(CaseInsensitiveString BareJid, string Setting)
1221 {
1222 return RuntimeSettings.GetAsync(BareJid + " " + Setting, true);
1223 }
1224
1229 public async Task<byte[]> GetDialbackSecret()
1230 {
1231 string s = await RuntimeSettings.GetAsync("Dialback Secret", string.Empty);
1232
1233 if (string.IsNullOrEmpty(s))
1234 {
1235 byte[] Data = Gateway.NextBytes(32);
1236
1237 s = Convert.ToBase64String(Data, Base64FormattingOptions.None);
1238
1239 await RuntimeSettings.SetAsync("Dialback Secret", s);
1240 }
1241
1242 return Convert.FromBase64String(s);
1243 }
1244
1245 #endregion
1246
1247 #region Offline messages
1248
1269 internal static bool RegisterEventsTab(CaseInsensitiveString BareJid,
1270 string MessageType, string LocalName, string Namespace, string Function,
1271 string TabId)
1272 {
1273 string Fqn = EventHandlerKey(MessageType, LocalName, Namespace);
1274 bool Unregistering = string.IsNullOrEmpty(Function);
1275
1276 lock (functionsPerTabIdPerFqnPerBareJid)
1277 {
1278 if (!functionsPerTabIdPerFqnPerBareJid.TryGetValue(BareJid, out Dictionary<string, Dictionary<string, string>> FunctionsPerTabIdPerFqn))
1279 {
1280 if (Unregistering)
1281 return false;
1282
1283 FunctionsPerTabIdPerFqn = new Dictionary<string, Dictionary<string, string>>();
1284 functionsPerTabIdPerFqnPerBareJid[BareJid] = FunctionsPerTabIdPerFqn;
1285 }
1286
1287 if (!FunctionsPerTabIdPerFqn.TryGetValue(Fqn, out Dictionary<string, string> FunctionsPerTabId))
1288 {
1289 if (Unregistering)
1290 return false;
1291
1292 FunctionsPerTabId = new Dictionary<string, string>();
1293 FunctionsPerTabIdPerFqn[Fqn] = FunctionsPerTabId;
1294 }
1295
1296 if (Unregistering)
1297 {
1298 if (!FunctionsPerTabId.Remove(TabId))
1299 return false;
1300
1301 if (FunctionsPerTabId.Count > 0)
1302 return true;
1303
1304 if (!FunctionsPerTabIdPerFqn.Remove(Fqn))
1305 return false;
1306
1307 if (FunctionsPerTabIdPerFqn.Count > 0)
1308 return true;
1309
1310 return functionsPerTabIdPerFqnPerBareJid.Remove(BareJid);
1311 }
1312 else if (FunctionsPerTabId.TryGetValue(TabId, out string s) && s == Function)
1313 return false;
1314 else
1315 {
1316 FunctionsPerTabId[TabId] = Function;
1317 return true;
1318 }
1319 }
1320 }
1321
1322 private static string EventHandlerKey(string MessageType, string LocalName, string Namespace)
1323 {
1324 StringBuilder sb = new StringBuilder();
1325
1326 sb.Append(Namespace);
1327 sb.Append('#');
1328 sb.Append(LocalName);
1329 sb.Append('?');
1330 sb.Append(MessageType);
1331
1332 return sb.ToString();
1333 }
1334
1344 internal static bool UnregisterEventsTab(CaseInsensitiveString BareJid,
1345 string MessageType, string LocalName, string Namespace, string TabId)
1346 {
1347 return RegisterEventsTab(BareJid, MessageType, LocalName, Namespace, string.Empty, TabId);
1348 }
1349
1357 internal static ClientPushRecord[] GetEventTabs(CaseInsensitiveString BareJid,
1358 string Type, string XmlContent, XmlElement StanzaElement)
1359 {
1360 lock (functionsPerTabIdPerFqnPerBareJid)
1361 {
1362 XmlElement FirstContent = null;
1363
1364 if (!functionsPerTabIdPerFqnPerBareJid.TryGetValue(BareJid, out Dictionary<string, Dictionary<string, string>> FunctionsPerTabIdPerFqn))
1365 return null;
1366
1367 if (StanzaElement is null)
1368 {
1369 try
1370 {
1371 StringBuilder Xml = new StringBuilder();
1372
1373 Xml.Append("<message xmlns=\"");
1374 Xml.Append(ClientConnection.C2SNamespace);
1375 Xml.Append('>');
1376 Xml.Append(XmlContent);
1377 Xml.Append("</message>");
1378
1379 XmlDocument Doc = XML.ParseXml(XmlContent);
1380 StanzaElement = Doc.DocumentElement;
1381 }
1382 catch (Exception)
1383 {
1384 return null;
1385 }
1386 }
1387
1388 Dictionary<string, string> FunctionsPerTabId;
1389 List<ClientPushRecord> Result = null;
1390 string Key;
1391
1392 foreach (XmlNode N in StanzaElement.ChildNodes)
1393 {
1394 if (N is XmlElement E)
1395 {
1396 FirstContent ??= E;
1397
1398 Key = EventHandlerKey(Type, E.LocalName, E.NamespaceURI);
1399 if (FunctionsPerTabIdPerFqn.TryGetValue(Key, out FunctionsPerTabId))
1400 {
1401 Result ??= new List<ClientPushRecord>();
1402
1403 foreach (KeyValuePair<string, string> P in FunctionsPerTabId)
1404 {
1405 Result.Add(new ClientPushRecord()
1406 {
1407 Type = Type,
1408 LocalName = E.LocalName,
1409 Namespace = E.NamespaceURI,
1410 Function = P.Value,
1411 TabId = P.Key,
1412 Data = E
1413 });
1414 }
1415 }
1416
1417 Key = EventHandlerKey(Type, string.Empty, E.NamespaceURI);
1418 if (FunctionsPerTabIdPerFqn.TryGetValue(Key, out FunctionsPerTabId))
1419 {
1420 Result ??= new List<ClientPushRecord>();
1421
1422 foreach (KeyValuePair<string, string> P in FunctionsPerTabId)
1423 {
1424 Result.Add(new ClientPushRecord()
1425 {
1426 Type = Type,
1427 LocalName = string.Empty,
1428 Namespace = E.NamespaceURI,
1429 Function = P.Key,
1430 TabId = P.Value,
1431 Data = E
1432 });
1433 }
1434 }
1435
1436 Key = EventHandlerKey(string.Empty, string.Empty, E.NamespaceURI);
1437 if (FunctionsPerTabIdPerFqn.TryGetValue(Key, out FunctionsPerTabId))
1438 {
1439 Result ??= new List<ClientPushRecord>();
1440
1441 foreach (KeyValuePair<string, string> P in FunctionsPerTabId)
1442 {
1443 Result.Add(new ClientPushRecord()
1444 {
1445 Type = string.Empty,
1446 LocalName = string.Empty,
1447 Namespace = E.NamespaceURI,
1448 Function = P.Key,
1449 TabId = P.Value,
1450 Data = E
1451 });
1452 }
1453 }
1454 }
1455 }
1456
1457 if (!(FirstContent is null))
1458 {
1459 Key = EventHandlerKey(string.Empty, string.Empty, string.Empty);
1460 if (FunctionsPerTabIdPerFqn.TryGetValue(Key, out FunctionsPerTabId))
1461 {
1462 Result ??= new List<ClientPushRecord>();
1463
1464 foreach (KeyValuePair<string, string> P in FunctionsPerTabId)
1465 {
1466 Result.Add(new ClientPushRecord()
1467 {
1468 Type = string.Empty,
1469 LocalName = string.Empty,
1470 Namespace = string.Empty,
1471 Function = P.Key,
1472 TabId = P.Value,
1473 Data = FirstContent
1474 });
1475 }
1476 }
1477 }
1478
1479 return Result?.ToArray();
1480 }
1481 }
1482
1483 internal class ClientPushRecord
1484 {
1485 public string Type;
1486 public string LocalName;
1487 public string Namespace;
1488 public string Function;
1489 public string TabId;
1490 public XmlElement Data;
1491 }
1492
1499 public async Task<IEnumerable<IOfflineMessage>> GetOfflineMessages(CaseInsensitiveString ToUserName, int Max)
1500 {
1501 return await Database.Find<OfflineMessage>(0, Max, new FilterFieldEqualTo("ToUserName", ToUserName), "Timestamp");
1502 }
1503
1508 public async Task DeleteOfflineMessages(IEnumerable<IOfflineMessage> Messages)
1509 {
1510 await Database.Delete(Messages);
1511 }
1512
1518 public async Task<int> DeleteOfflineMessages(CaseInsensitiveString ToUserName)
1519 {
1520 return await Database.Delete<OfflineMessage>(new FilterFieldEqualTo("ToUserName", ToUserName));
1521 }
1522
1533 public async Task<bool> StoreOfflineMessage(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml)
1534 {
1536 ClientPushRecord[] ClientPushRecords = GetEventTabs(BareJid, Type, ContentXml, null);
1537
1538 if (!(ClientPushRecords is null) && await PushMessageToClients(BareJid, ClientPushRecords))
1539 return true;
1540
1541 OfflineMessage Msg = new OfflineMessage()
1542 {
1543 ToUserName = To.Account,
1544 Type = Type,
1545 Id = Id,
1546 To = BareJid,
1547 From = From.Address,
1548 Language = Language,
1549 ContentXml = ContentXml,
1550 Timestamp = DateTime.UtcNow
1551 };
1552
1553 await Database.Insert(Msg);
1554
1556 if (!(Token is null))
1557 {
1558 PushNotificationRule Rule = await XmppServer.TryGetPushNotificationRule(BareJid, Type, string.Empty, string.Empty);
1559 if (!(Rule is null))
1560 {
1561 XmlElement StanzaElement = this.RecreateStanza(ContentXml);
1562 if (!(StanzaElement is null))
1563 await this.ProcessOfflineRule(Rule, StanzaElement, Token, Type, Id, To.Address.Value, From.Address.Value);
1564 }
1565 else if (!string.IsNullOrEmpty(ContentXml))
1566 {
1567 XmlElement StanzaElement = this.RecreateStanza(ContentXml);
1568 if (!(StanzaElement is null))
1569 await this.ProcessOfflineStanza(BareJid, Type, StanzaElement, Token, Id, To.Address.Value, From.Address.Value);
1570 }
1571 }
1572
1573 return true;
1574 }
1575
1576 private static async Task<bool> PushMessageToClients(CaseInsensitiveString BareJid,
1577 ClientPushRecord[] ClientPushRecords)
1578 {
1579 if (ClientPushRecords is null)
1580 return false;
1581
1582 bool Sent = false;
1583
1584 foreach (ClientPushRecord Rec in ClientPushRecords)
1585 {
1586 if (await ClientEvents.PushEvent(new string[] { Rec.TabId }, Rec.Function, JSON.Encode(Rec.Data, false), true) > 0)
1587 Sent = true;
1588 else
1589 UnregisterEventsTab(BareJid, Rec.Type, Rec.LocalName, Rec.Namespace, Rec.TabId);
1590 }
1591
1592 return Sent;
1593 }
1594
1595 private XmlElement RecreateStanza(string ContentXml)
1596 {
1597 if (string.IsNullOrEmpty(ContentXml))
1598 return null;
1599
1600 try
1601 {
1602 XmlDocument Doc = XML.ParseXml("<message xmlns='jabber:client'>" + ContentXml + "</message>");
1603 return Doc.DocumentElement;
1604 }
1605 catch (Exception ex)
1606 {
1607 Log.Exception(ex);
1608 return null;
1609 }
1610 }
1611
1612 private async Task ProcessOfflineStanza(CaseInsensitiveString BareJid, string Type, XmlElement StanzaElement,
1613 PushNotificationToken Token, string Id, string To, string From)
1614 {
1615 foreach (XmlNode N in StanzaElement.ChildNodes)
1616 {
1617 if (N is XmlElement E)
1618 {
1619 PushNotificationRule Rule = await XmppServer.TryGetPushNotificationRule(BareJid, Type, E.LocalName, E.NamespaceURI);
1620 if (!(Rule is null))
1621 {
1622 await this.ProcessOfflineRule(Rule, StanzaElement, Token, Type, Id, To, From);
1623 return;
1624 }
1625 }
1626 }
1627 }
1628
1629 private bool NoXPath(ScriptNode Node, out ScriptNode NewNode, object State)
1630 {
1631 NewNode = null;
1632 return !(Node is Script.Persistence.Functions.XPath);
1633 }
1634
1635 private async Task ProcessOfflineRule(PushNotificationRule Rule, XmlElement StanzaElement, PushNotificationToken Token,
1636 string Type, string Id, string To, string From)
1637 {
1638 try
1639 {
1640 if (!string.IsNullOrEmpty(Type) && !StanzaElement.HasAttribute("type"))
1641 StanzaElement.SetAttribute("type", Type);
1642
1643 if (!string.IsNullOrEmpty(Id) && !StanzaElement.HasAttribute("id"))
1644 StanzaElement.SetAttribute("id", Id);
1645
1646 if (!string.IsNullOrEmpty(To) && !StanzaElement.HasAttribute("to"))
1647 StanzaElement.SetAttribute("to", To);
1648
1649 if (!string.IsNullOrEmpty(From) && !StanzaElement.HasAttribute("from"))
1650 StanzaElement.SetAttribute("from", From);
1651
1652 Variables v = new Variables();
1653 Expression ContentExpression = Rule.ContentExpression;
1654 Expression PatternMatchingExpression = Rule.PatternMatchingExpression;
1655 bool HasXPath = !((ContentExpression?.ForAll(this.NoXPath, null, SearchMethod.TreeOrder) ?? true) &&
1656 (PatternMatchingExpression?.ForAll(this.NoXPath, null, SearchMethod.TreeOrder) ?? true));
1657
1658 if (!string.IsNullOrEmpty(Rule.MessageVariable))
1659 v[Rule.MessageVariable] = StanzaElement;
1660
1661 if (!(PatternMatchingExpression is null))
1662 {
1663 Dictionary<string, IElement> AlreadyFound = new Dictionary<string, IElement>();
1664
1665 if (PatternMatchingExpression.Root.PatternMatch(new ObjectValue(StanzaElement), AlreadyFound) != PatternMatchResult.Match)
1666 return;
1667
1668 foreach (KeyValuePair<string, IElement> P in AlreadyFound)
1669 v[P.Key] = P.Value;
1670 }
1671
1672 object Content;
1673
1674 if (ContentExpression is null)
1675 Content = StanzaElement;
1676 else
1677 Content = await ContentExpression.EvaluateAsync(v);
1678
1679 PushNotificationEventHandler h = this.OnPushNotification;
1680 if (!(h is null))
1681 await h(Token, Content);
1682
1683 PushNotificationConfiguration.PushNotification(Token, Content, Rule);
1684 }
1685 catch (Exception ex)
1686 {
1687 Log.Exception(ex);
1688 }
1689 }
1690
1694 public event PushNotificationEventHandler OnPushNotification;
1695
1706 public async Task<bool> StoreOfflineMessage(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza)
1707 {
1709 ClientPushRecord[] ClientPushRecords = GetEventTabs(BareJid, Type, Stanza.Content, Stanza.StanzaElement);
1710
1711 if (!(ClientPushRecords is null) && await PushMessageToClients(BareJid, ClientPushRecords))
1712 return true;
1713
1714 OfflineMessage Msg = new OfflineMessage()
1715 {
1716 ToUserName = To.Account,
1717 Type = Type,
1718 Id = Id,
1719 To = BareJid,
1720 From = From.Address,
1721 Language = Language,
1722 ContentXml = Stanza.Content,
1723 Timestamp = DateTime.UtcNow
1724 };
1725
1726 await Database.Insert(Msg);
1727
1729 if (!(Token is null))
1730 {
1731 PushNotificationRule Rule = await XmppServer.TryGetPushNotificationRule(BareJid, Type, string.Empty, string.Empty);
1732 if (!(Rule is null))
1733 await this.ProcessOfflineRule(Rule, Stanza.StanzaElement, Token, Type, Id, To.Address.Value, From.Address.Value);
1734 else if (Stanza.HasContent)
1735 await this.ProcessOfflineStanza(BareJid, Type, Stanza.StanzaElement, Token, Id, To.Address.Value, From.Address.Value);
1736 }
1737
1738 return true;
1739 }
1740
1746 public async Task<int> DeleteOfflineMessages(DateTime OlderThan)
1747 {
1748 return await Database.Delete<OfflineMessage>(new FilterFieldLesserOrEqualTo("Timestamp", OlderThan));
1749 }
1750
1756 async Task<Networking.SASL.IAccount> ISaslPersistenceLayer.GetAccount(CaseInsensitiveString UserName)
1757 {
1758 return await this.GetAccountEx(UserName);
1759 }
1760
1766 async Task<Networking.XMPP.Server.IAccount> IXmppServerPersistenceLayer.GetAccount(CaseInsensitiveString UserName)
1767 {
1768 return await this.GetAccountEx(UserName);
1769 }
1770
1776 {
1777 lock (this.accounts)
1778 {
1779 this.accounts.Remove(UserName);
1780 }
1781
1782 return Task.CompletedTask;
1783 }
1784
1785 public byte[] GetRandomNumbers(int NrBytes)
1786 {
1787 return XmppServer.GetRandomNumbers(NrBytes);
1788 }
1789
1790 #endregion
1791
1792 #region FTP folder
1793
1799 public async Task<string> GetRootFolder(string UserName)
1800 {
1801 Account Account = await this.GetAccountEx(UserName);
1802 if (!Account.Enabled)
1803 return null;
1804
1805 return Account?.FtpRootFolder;
1806 }
1807
1814 public async Task<long> GetMaxStorage(string UserName)
1815 {
1816 Account Account = await this.GetAccountEx(UserName);
1817 if (!Account.Enabled)
1818 return 0;
1819
1820 return Account.FtpMaxStorage ?? -1;
1821 }
1822
1823 #endregion
1824
1825 #region Dynamic Client Registration
1826
1832 public async Task<IUser> TryGetUser(string UserName)
1833 {
1834 IUser Result = await Users.Source.TryGetUser(UserName);
1835 if (!(Result is null))
1836 return Result;
1837
1838 Account Account = await this.GetAccountEx(UserName);
1839 if (!(Account is null))
1840 return new AccountUser(Account);
1841
1842 return null;
1843 }
1844
1851 public async Task<IRegistration> RegisterUser(IRegistrationRequest RegistrationRequest)
1852 {
1853 int Count = 0;
1854
1855 foreach (OAuthClientInformation ClientInfo in await Database.Find<OAuthClientInformation>(
1856 new FilterFieldEqualTo("RemoteEndPoint", RegistrationRequest.RemoteEndPoint)))
1857 {
1858 Count++;
1859 }
1860
1861 if (Count >= 2) // TODO: Make limit configurable. Also, depend on type of client (public or confidential).
1862 {
1863 Log.Warning("Client registration rejected. Too many clients registered " +
1864 "from same IP address.", RegistrationRequest.RemoteEndPoint,
1865 string.Empty, "ClientRegistrationRejected", EventLevel.Minor,
1866 new KeyValuePair<string, object>("RemoteEndPoint", RegistrationRequest.RemoteEndPoint),
1867 new KeyValuePair<string, object>("Count", Count),
1868 new KeyValuePair<string, object>("PublicClient", RegistrationRequest.PublicClient),
1869 new KeyValuePair<string, object>("ConfidentialClient", RegistrationRequest.ConfidentialClient),
1870 new KeyValuePair<string, object>("ClientName", RegistrationRequest.ClientName),
1871 new KeyValuePair<string, object>("SoftwareId", RegistrationRequest.SoftwareId),
1872 new KeyValuePair<string, object>("SoftwareVersion", RegistrationRequest.SoftwareVersion),
1873 new KeyValuePair<string, object>("ClientUri", RegistrationRequest.ClientUri?.OriginalString),
1874 new KeyValuePair<string, object>("LogoUri", RegistrationRequest.LogoUri?.OriginalString),
1875 new KeyValuePair<string, object>("TosUri", RegistrationRequest.TosUri?.OriginalString),
1876 new KeyValuePair<string, object>("PolicyUri", RegistrationRequest.PolicyUri?.OriginalString));
1877
1878 return null;
1879 }
1880
1881 string EMail = null;
1882 string PhoneNr = null;
1883 string Password = RandomPassword.CreateRandomPassword();
1884 string UserName;
1885
1886 if (!(RegistrationRequest.Contacts is null))
1887 {
1888 foreach (string Contact in RegistrationRequest.Contacts)
1889 {
1890 if (Contact.IndexOf('@') >= 0)
1891 {
1892 if (string.IsNullOrEmpty(EMail))
1893 EMail = Contact;
1894 }
1895 else
1896 {
1897 if (string.IsNullOrEmpty(PhoneNr))
1898 PhoneNr = Contact;
1899 }
1900 }
1901 }
1902
1903 while (true)
1904 {
1905 UserName = Guid.NewGuid().ToString();
1906 KeyValuePair<Networking.XMPP.Server.IAccount, string[]> P =
1907 await this.CreateAccount(OAuthApiKeyName, UserName, Password, EMail, PhoneNr,
1908 RegistrationRequest.RemoteEndPoint, false);
1909
1910 if (P.Key is null)
1911 {
1912 if (P.Value is null)
1913 return null;
1914 else
1915 {
1916 return new AccountRegistration()
1917 {
1918 ClientId = UserName,
1919 ClientSecret = Password,
1920 ClientSecretExpiresAt = null
1921 };
1922 }
1923 }
1924 }
1925 }
1926
1927 private class AccountRegistration : IRegistration
1928 {
1929 public string ClientId { get; internal set; }
1930 public string ClientSecret { get; internal set; }
1931 public DateTime? ClientSecretExpiresAt { get; internal set; }
1932 }
1933
1941 public async Task<IRegistration> UpdateUser(string UserName, IRegistrationRequest UpdateRequest)
1942 {
1943 Account Account = await this.GetAccountEx(UserName);
1944 if (Account is null || Account.ApiKey != OAuthApiKeyName)
1945 return null;
1946
1947 if (!string.IsNullOrEmpty(UpdateRequest.ClientSecret) &&
1948 Account.Password != UpdateRequest.ClientSecret)
1949 {
1950 Account.Password = UpdateRequest.ClientSecret;
1951 await Database.Update(Account);
1952 }
1953
1954 return new AccountRegistration()
1955 {
1956 ClientId = UserName,
1957 ClientSecret = UpdateRequest.ClientSecret,
1958 ClientSecretExpiresAt = null
1959 };
1960 }
1961
1968 public async Task<bool> DeleteUser(string UserName, string RemoteEndPoint)
1969 {
1970 Account Account = await this.GetAccountEx(UserName);
1971 if (Account is null || Account.ApiKey != OAuthApiKeyName)
1972 return false;
1973
1974 return await this.DeleteAccount(UserName, RemoteEndPoint);
1975 }
1976
1977 #endregion
1978 }
1979}
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:537
Contains a markdown document. This markdown document class supports original markdown,...
static string Encode(string s)
Encodes all special characters in a string so that it can be included in a markdown document without ...
Helps with common XML-related tasks.
Definition: XML.cs:21
static XmlDocument ParseXml(string Xml)
Parses an XML Document from its string representation.
Definition: XML.cs:713
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
static void Warning(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a warning event.
Definition: Log.cs:576
static void Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
Definition: Log.cs:344
static void Notice(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a notice event.
Definition: Log.cs:460
The ClientEvents class allows applications to push information asynchronously to web clients connecte...
Definition: ClientEvents.cs:51
static Task< int > PushEvent(string[] TabIDs, string Type, object Data)
Puses an event to a set of Tabs, given their Tab IDs.
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:147
static LoginAuditor LoginAuditor
Current Login Auditor. Should be used by modules accepting user logins, to protect the system from un...
Definition: Gateway.cs:3860
static byte[] NextBytes(int NrBytes)
Generates an array of random bytes.
Definition: Gateway.cs:4335
static Task SendNotification(Graph Graph)
Sends a graph as a notification message to configured notification recipients.
Definition: Gateway.cs:4677
static string GetUrl(string LocalResource)
Gets a URL for a resource.
Definition: Gateway.cs:5079
static int NextInteger(int Max)
Returns a non-negative random integer that is less than the specified maximum.
Definition: Gateway.cs:4311
static string RootFolder
Web root folder.
Definition: Gateway.cs:3142
static string CreateRandomPassword(int NrBytes, int NrBuckets)
Creates a random password.
Contains information about an OAuth client, as defined in RFC 7591.
Authentication done by the LOGIN authentication mechanism. https://tools.ietf.org/html/draft-murchiso...
Definition: Login.cs:15
Abstract base class for XMPP client connections
Expression PatternMatchingExpression
Parsed pattern-matching expression
string MessageVariable
Variable to put the Message XML in, before pattern matching or content script is executed.
Contains information about a stanza.
Definition: Stanza.cs:9
string Content
Literal XML content.
Definition: Stanza.cs:53
XmlElement StanzaElement
Stanza element.
Definition: Stanza.cs:113
bool HasContent
If the stanza has content.
Definition: Stanza.cs:80
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
CaseInsensitiveString Address
XMPP Address
Definition: XmppAddress.cs:37
CaseInsensitiveString BareJid
Bare JID
Definition: XmppAddress.cs:45
static readonly XmppAddress Empty
Empty address.
Definition: XmppAddress.cs:31
CaseInsensitiveString Account
Account
Definition: XmppAddress.cs:124
static byte[] GetRandomNumbers(int NrBytes)
Generates a set of random numbers.
Definition: XmppServer.cs:679
static async Task< PushNotificationToken > TryGetPushNotificationToken(CaseInsensitiveString BareJid)
Tries to get a push notification token for a client, if one exists.
Definition: XmppServer.cs:6957
static async Task< PushNotificationRule > TryGetPushNotificationRule(CaseInsensitiveString BareJid, string MessageType, string LocalName, string Namespace)
Tries to get a push notification token for a client, if one exists.
Definition: XmppServer.cs:7268
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
int IndexOf(CaseInsensitiveString value, StringComparison comparisonType)
Reports the zero-based index of the first occurrence of the specified string in the current System....
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
CaseInsensitiveString Substring(int startIndex, int length)
Retrieves a substring from this instance. The substring starts at a specified character position and ...
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:1211
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:1291
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field lesser or equal to a given value.
Static class managing persistent counters.
static Task< long > IncrementCounter(CaseInsensitiveString Key)
Increments a counter.
Static class managing persistent settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
static async Task< bool > SetAsync(string Key, string Value)
Sets a string-valued setting.
Class managing a script expression.
Definition: Expression.cs:41
ScriptNode Root
Root script node.
Definition: Expression.cs:4496
async Task< object > EvaluateAsync(Variables Variables)
Evaluates the expression, using the variables provided in the Variables collection....
Definition: Expression.cs:4461
bool ForAll(ScriptNodeEventHandler Callback, object State, bool DepthFirst)
Calls the callback method for all script nodes defined for the expression.
Definition: Expression.cs:5456
Base class for all funcions.
Definition: Function.cs:7
Base class for all nodes in a parsed script tree.
Definition: ScriptNode.cs:69
virtual PatternMatchResult PatternMatch(IElement CheckAgainst, Dictionary< string, IElement > AlreadyFound)
Performs a pattern match operation.
Definition: ScriptNode.cs:169
Collection of variables.
Definition: Variables.cs:25
Class that monitors login events, and help applications determine malicious intent....
Definition: LoginAuditor.cs:26
static async Task< string > AppendWhoIsInfo(StringBuilder Markdown, string RemoteEndPoint)
Appends WHOIS information to a Markdown document.
static async Task< KeyValuePair< string, object >[]> Annotate(string RemoteEndPoint, params KeyValuePair< string, object >[] Tags)
Annotates a remote endpoint.
Corresponds to a user in the system.
Definition: User.cs:24
Maintains the collection of all users in the system.
Definition: Users.cs:24
static IUserSource Source
User source.
Definition: Users.cs:37
Contains information about a broker account.
Definition: Account.cs:41
CaseInsensitiveString EMail
E-mail address associated with account.
Definition: Account.cs:176
CaseInsensitiveString UserName
User Name of account
Definition: Account.cs:141
bool Enabled
If account is enabled
Definition: Account.cs:354
async Task< AccountLogin > GetAccountLogin()
Gets the object with the associated account login status information.
Definition: Account.cs:572
string Password
Password of account
Definition: Account.cs:151
CaseInsensitiveString PhoneNr
Phone number associated with account.
Definition: Account.cs:185
string FtpRootFolder
FTP Root Folder, if account has access to FTP.
Definition: Account.cs:373
const string AccountDeletedCounterName
Name of counter that counts the number of accounts deleted.
Definition: Account.cs:50
const string AccountCreatedCounterName
Name of counter that counts the number of accounts created.
Definition: Account.cs:45
DateTime? Updated
When account was created associated with account holder.
Definition: Account.cs:326
DateTime Created
When account was created associated with account holder.
Definition: Account.cs:316
async Task LoggedIn(string RemoteEndPoint)
Registers a log-in event on the account.
Definition: Account.cs:558
string ApiKey
Reference to API Key used to create object.
Definition: Account.cs:132
static async Task AccountCreated(Account Account, ApiKey ApiKeyObject, string Domain, string RemoteEndPoint)
Notifies operators of a new account being created.
async Task< bool > ClearBlocks(CaseInsensitiveString UserName)
Remove all blocks for an account.
Task AccountUpdated(CaseInsensitiveString UserName)
Called when account has been updated.
async Task< bool > ChangePassword(CaseInsensitiveString UserName, string Password)
Changes the password of an account.
async Task< bool > SetAvatar(CaseInsensitiveString UserName, string ContentType, byte[] Data)
Sets the Avatar of an account.
async Task< byte[]> GetDialbackSecret()
Gets the Dialback secret, as defined in XEP-0185.
async Task< long > GetMaxStorage(string UserName)
Gets the maximum storage allowed for a user.
async Task< IUser > TryGetUser(string UserName)
Tries to get a user with a given user name.
Task< bool > IsPermitted(CaseInsensitiveString BareJid, string Setting)
Checks if a feature is permitted for a Bare JID.
async Task< int > DeleteOfflineMessages(DateTime OlderThan)
Deletes offline messages that are older than a specific timestamp.
async Task< KeyValuePair< Networking.XMPP.Server.IAccount, string[]> > CreateAccount(string ApiKey, CaseInsensitiveString UserName, string Password, CaseInsensitiveString EMail, CaseInsensitiveString PhoneNr, string RemoteEndPoint, bool Enabled)
Creates an account.
async Task< string > GetRootFolder(string UserName)
Gets the root folder of a user.
async Task< IRosterItem > GetRosterItem(CaseInsensitiveString UserName, CaseInsensitiveString Jid)
Gets a roster item for an account.
async Task< IRegistration > RegisterUser(IRegistrationRequest RegistrationRequest)
Registers a new user.
async void AccountLogin(CaseInsensitiveString UserName, string RemoteEndPoint)
Successful login to account registered.
async Task< bool > AddBlock(CaseInsensitiveString UserName, CaseInsensitiveString BareJid, BlockingReason Reason, string Text, string TextLanguage)
Blocks an account.
async Task< int > DeleteOfflineMessages(CaseInsensitiveString ToUserName)
Deletes offline messages for a given account.
async Task< IEnumerable< CaseInsensitiveString > > GetBlockList(CaseInsensitiveString UserName)
Gets the entire block list for an account.
async Task< bool > DeleteUser(string UserName, string RemoteEndPoint)
Deletes an existing user.
async Task< string > GetVCard(CaseInsensitiveString UserName)
Gets the vCard of an account.
async Task< IRosterItem > SetRosterItem(CaseInsensitiveString UserName, CaseInsensitiveString Jid, string Name, SubscriptionStatus? Subscription, bool? PendingSubscription, string[] Groups)
Sets a roster item in a users roster.
const string OAuthApiKeyName
API Key name for clients registered via OAUTH.
PushNotificationEventHandler OnPushNotification
Event raised when a push notification is performed.
async Task< bool > StoreOfflineMessage(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza)
Tries to save an offline message.
async Task DeleteOfflineMessages(IEnumerable< IOfflineMessage > Messages)
Deletes offline messages.
Task< KeyValuePair< Networking.XMPP.Server.IAccount, string[]> > CreateAccount(string ApiKey, CaseInsensitiveString UserName, string Password, CaseInsensitiveString EMail, CaseInsensitiveString PhoneNr, string RemoteEndPoint)
Creates an account.
async Task< bool > Unblock(CaseInsensitiveString UserName, CaseInsensitiveString BareJid)
Unblocks an account.
async Task< bool > DeleteAccount(CaseInsensitiveString UserName, string RemoteEndPoint)
Deletes an account.
async Task< bool > StoreOfflineMessage(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml)
Tries to save an offline message.
async Task< IEnumerable< IRosterItem > > GetRoster(CaseInsensitiveString UserName)
Gets the roster of an account.
async Task< IEnumerable< IOfflineMessage > > GetOfflineMessages(CaseInsensitiveString ToUserName, int Max)
Gets the oldest offline messages stored for a given bare JID, up to a maximum count.
async Task< Tuple< string, byte[]> > GetAvatar(CaseInsensitiveString UserName)
Gets the Avatar of an account.
async Task< bool > SetVCard(CaseInsensitiveString UserName, string VCard)
Sets the vCard of an account.
async Task< bool > IsBlocked(CaseInsensitiveString FromBareJid, CaseInsensitiveString ToBareJid)
Checks if a sender is blocked by a receiver.
byte[] GetRandomNumbers(int NrBytes)
Generates a set of random numbers.
async Task< string > GetApiKeySecret(string ApiKey)
Gets the secret for a given API key.
async Task< bool > RemoveRosterItem(CaseInsensitiveString UserName, CaseInsensitiveString Jid)
Removes a roster item.
async Task< IRegistration > UpdateUser(string UserName, IRegistrationRequest UpdateRequest)
Updates an existing user.
SubscriptionStatus Subscription
Subscription status.
Definition: RosterItem.cs:60
CaseInsensitiveString BareJid
Bare JID.
Definition: RosterItem.cs:39
bool PendingSubscription
If a presence subscription awaits.
Definition: RosterItem.cs:67
string[] Groups
Groups assigned to roster item.
Definition: RosterItem.cs:53
async Task< PubSubNode > GetNodeAsync(CaseInsensitiveString Service, CaseInsensitiveString NodeName, NodeAccessModel? AutoCreateAccess, XmppAddress From, CaseInsensitiveString Domain)
Gets a pubsub node.
Defines a node on which items can be published.
Definition: PubSubNode.cs:19
Provides the user with options to control notifications from the Broker.
bool AccountCreated
If a notification should be sent when a new account is created.
static BrokerNotificationConfiguration Instance
Current instance of configuration.
bool AccountDeleted
If a notification should be sent when a new account is deleted.
Provides the user configuration options regarding use of Push Notification to reach offline clients.
Service Module hosting the XMPP broker and its components.
static async Task AppendRemoteEndPointToTable(StringBuilder Markdown, string RemoteEndPoint)
Appends annotated information about a remote endpoint to a Markdown table.
A dynamic user source, supporting registering new users.
Dynamic client registration, as defined in RFC 7591.
Definition: IRegistration.cs:9
Dynamic client registration request, as defined in RFC 7591.
Interface for XMPP Server persistence layers. The persistence layer should implement caching.
Task< IAccount > GetAccount(CaseInsensitiveString UserName)
Method to call to fetch account information.
Interface for roster items.
Definition: IRosterItem.cs:43
Interface for XMPP Server persistence layers. The persistence layer should implement caching.
new Task< IAccount > GetAccount(CaseInsensitiveString UserName)
Method to call to fetch account information.
Basic interface for a user.
Definition: IUser.cs:7
Task< IUser > TryGetUser(string UserName)
Tries to get a user with a given user name.
EventLevel
Event level.
Definition: EventLevel.cs:7
SubscriptionStatus
Roster item subscription status enumeration.
Definition: IRosterItem.cs:10
BlockingReason
Reason for blocking an account.
MessageType
Type of message received.
Definition: MessageType.cs:7
PendingSubscription
Pending subscription states.
Definition: RosterItem.cs:54
PatternMatchResult
Status result of a pattern matching operation.
Definition: ScriptNode.cs:17
SearchMethod
Method to traverse the expression structure
Definition: ScriptNode.cs:38