Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
MultiUserChatComponent.cs
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Text;
5using System.Threading.Tasks;
6using System.Xml;
7using Waher.Content;
10using Waher.Events;
19
21{
27 {
31 public const string NamespaceMuc = "http://jabber.org/protocol/muc";
32
36 public const string NamespaceMucUser = "http://jabber.org/protocol/muc#user";
37
41 public const string NamespaceMucAdmin = "http://jabber.org/protocol/muc#admin";
42
46 public const string NamespaceMucOwner = "http://jabber.org/protocol/muc#owner";
47
51 public const string FormTypeRegister = "http://jabber.org/protocol/muc#register";
52
56 public const string FormTypeRequest = "http://jabber.org/protocol/muc#request";
57
61 public const string FormTypeRoomConfig = "http://jabber.org/protocol/muc#roomconfig";
62
63 private readonly Cache<CaseInsensitiveString, MucRoom> rooms = new Cache<CaseInsensitiveString, MucRoom>(int.MaxValue, TimeSpan.MaxValue, TimeSpan.FromDays(1));
64
73 : base(Server, Subdomain, Name)
74 {
75 this.RegisterPresenceHandler("x", NamespaceMuc, this.EnterRoomHandler, true);
76 this.RegisterMessageHandler("x", NamespaceMucUser, this.UserCommandHandler, true);
77 this.RegisterMessageHandler("x", XmppServer.DataFormsNamespace, this.FormSubmissionHandler, true);
78 this.RegisterIqGetHandler("query", XmppServer.RegisterNamespace, this.RegisterUserGetFormHandler, true);
79 this.RegisterIqSetHandler("query", XmppServer.RegisterNamespace, this.RegisterUserSubmitFormHandler, false);
80 this.RegisterIqGetHandler("query", NamespaceMucAdmin, this.AdminQueryHandler, false);
81 this.RegisterIqSetHandler("query", NamespaceMucAdmin, this.AdminCommandHandler, true);
82 this.RegisterIqGetHandler("query", NamespaceMucOwner, this.OwnerQueryHandler, false);
83 this.RegisterIqSetHandler("query", NamespaceMucOwner, this.OwnerCommandHandler, false);
84 this.RegisterIqGetHandler("ping", XmppServer.PingNamespace, this.SelfPingHandler, true);
85 this.RegisterFeature("muc_rooms");
86 this.RegisterFeature("http://jabber.org/protocol/muc#self-ping-optimization");
87 }
88
92 public override void Dispose()
93 {
94 this.UnregisterPresenceHandler("x", NamespaceMuc, this.EnterRoomHandler, true);
95 this.UnregisterMessageHandler("x", NamespaceMucUser, this.UserCommandHandler, true);
96 this.UnregisterMessageHandler("x", XmppServer.DataFormsNamespace, this.FormSubmissionHandler, true);
97 this.UnregisterIqGetHandler("query", XmppServer.RegisterNamespace, this.RegisterUserGetFormHandler, true);
98 this.UnregisterIqSetHandler("query", XmppServer.RegisterNamespace, this.RegisterUserSubmitFormHandler, false);
99 this.UnregisterIqGetHandler("query", NamespaceMucAdmin, this.AdminQueryHandler, false);
100 this.UnregisterIqSetHandler("query", NamespaceMucAdmin, this.AdminCommandHandler, true);
101 this.UnregisterIqGetHandler("query", NamespaceMucOwner, this.OwnerQueryHandler, false);
102 this.UnregisterIqSetHandler("query", NamespaceMucOwner, this.OwnerCommandHandler, false);
103 this.UnregisterIqGetHandler("ping", XmppServer.PingNamespace, this.SelfPingHandler, true);
104 this.UnregisterFeature("muc_rooms");
105 this.UnregisterFeature("http://jabber.org/protocol/muc#self-ping-optimization");
106
107 this.rooms.Dispose();
108
109 base.Dispose();
110 }
111
116 public override bool SupportsAccounts => true;
117
118 private async Task EnterRoomHandler(object Sender, PresenceEventArgs e)
119 {
120 if (e.From.IsBareJID)
121 {
122 await e.PresenceErrorJidMalformed(e.To, "From JID must be a full JID.", "en");
123 return;
124 }
125
126 if (!e.To.HasAccount)
127 {
128 await e.PresenceErrorJidMalformed(e.To, "Missing room name.", "en");
129 return;
130 }
131
132 if (e.To.IsBareJID)
133 {
134 await e.PresenceErrorJidMalformed(e.To, "Missing room nick name.", "en");
135 return;
136 }
137
139 CaseInsensitiveString NickName = e.To.Resource;
140 MucRoom Room = await this.GetRoom(RoomId, e.To.Domain);
141 StringBuilder sb;
142 bool RoomCreated = false;
143
144 if (Room is null)
145 {
146 if (!this.Server.IsServerDomain(e.From.Domain, true))
147 {
148 await e.PresenceErrorNotAllowed(e.To, "Only users with accounts on the server can create rooms on the server.", "en");
149 return;
150 }
151
152 if (!(XmppServerModule.Legal is null))
153 {
154 LegalIdentity Identity = await XmppServerModule.Legal.GetCurrentApprovedLegalIdentityAsync(e.From.Account);
155 if (Identity is null)
156 {
157 await e.PresenceErrorNotAllowed(e.To, "Only users with approved legal identities on the server can create rooms on the server.", "en");
158 return;
159 }
160 }
161
162 Room = new MucRoom()
163 {
164 Room = RoomId,
165 Domain = e.To.Domain,
166 Creator = e.From.BareJid,
167 Admins = new CaseInsensitiveString[] { e.From.BareJid },
168 Owners = new CaseInsensitiveString[] { e.From.BareJid },
169 Persistent = false,
170 Locked = true
171 };
172
173 this.rooms[RoomId] = Room;
174 RoomCreated = true;
175
176 Log.Informational("Room created", RoomId, e.From.BareJid,
177 new KeyValuePair<string, object>("Room", RoomId),
178 new KeyValuePair<string, object>("Domain", e.To.Domain),
179 new KeyValuePair<string, object>("Creator", e.From.BareJid));
180 }
181
182 if (Room.PasswordProtected)
183 {
184 string Password = null;
185
186 foreach (XmlNode N in e.Content.ChildNodes)
187 {
188 if (N is XmlElement E && E.LocalName == "password" && E.NamespaceURI == NamespaceMuc)
189 {
190 Password = E.InnerText;
191 break;
192 }
193 }
194
195 DateTime? Next = await (Gateway.LoginAuditor?.GetEarliestLoginOpportunity(e.From.BareJid.LowerCase, "MUC") ?? Task.FromResult<DateTime?>(null));
196
197 if (Next.HasValue)
198 {
199 DateTime TP = Next.Value;
200 DateTime Today = DateTime.Today;
201
202 sb = new StringBuilder();
203
204 if (Next.Value == DateTime.MaxValue)
205 {
206 sb.Append("This endpoint (");
207 sb.Append(e.From.BareJid.LowerCase);
208 sb.Append(") has been blocked from the system.");
209
210 await e.PresenceErrorForbidden(e.To, sb.ToString(), "en");
211 return;
212 }
213 else
214 {
215 sb.Append("Too many failed login attempts in a row registered. Try again after ");
216 sb.Append(TP.ToLongTimeString());
217
218 if (TP.Date != Today)
219 {
220 if (TP.Date == Today.AddDays(1))
221 sb.Append(" tomorrow");
222 else
223 {
224 sb.Append(", ");
225 sb.Append(TP.ToShortDateString());
226 }
227 }
228
229 sb.Append(". Remote Endpoint: ");
230 sb.Append(e.From.BareJid.LowerCase);
231
232 await e.PresenceErrorNotAuthorized(e.To, sb.ToString(), "en");
233 return;
234 }
235 }
236
237 if (Password is null || Room.Password != Password)
238 {
239 await e.PresenceErrorNotAuthorized(e.To, "Room ID or password incorrect.", "en");
240 return;
241 }
242 }
243
244 if (!Room.TryGetOccupantFromNick(NickName, out MucOccupant Occupant))
245 {
246 Occupant = await Database.FindFirstDeleteRest<MucOccupant>(new FilterAnd(
247 new FilterFieldEqualTo("Domain", e.To.Domain),
248 new FilterFieldEqualTo("Room", RoomId),
249 new FilterFieldEqualTo("NickName", NickName)));
250
251 if (!(Occupant is null) && !Room.Add(Occupant))
252 {
253 await e.PresenceErrorServiceUnavailable(e.To, "Room is full.", "en");
254 return;
255 }
256 }
257
258 if (!(Occupant is null))
259 {
260 if (Occupant.BareJid != e.From.BareJid)
261 {
262 await e.PresenceErrorConflict(e.To, "Nick-name conflict.", "en");
263 return;
264 }
265
266 if (Occupant.Role is null)
267 {
268 await e.PresenceErrorRegistrationRequired(e.To, "Registration has not been granted yet.", "en");
269 return;
270 }
271 }
272 else
273 {
274 Affiliation Affiliation = Room.GetDefaultAffiliation(e.From.BareJid);
276
277 if (Role is null)
278 {
279 if (Room.Locked)
280 await e.PresenceErrorItemNotFound(e.To, "Room is locked.", "en");
281 else if (Room.MembersOnly)
282 await e.PresenceErrorRegistrationRequired(e.To, "Entry to room requires membership registration.", "en");
283 else
284 await e.PresenceErrorForbidden(e.To, "Entry to room denied.", "en");
285
286 return;
287 }
288
289 Occupant = new MucOccupant()
290 {
291 Room = RoomId,
292 Domain = e.To.Domain,
293 NickName = NickName,
294 BareJid = e.From.BareJid,
295 FullJids = new XmppAddress[] { e.From },
297 Role = Role,
298 Persistent = Room.Persistent,
299 Archive = Room.Archive
300 };
301
302 if (!Room.Add(Occupant))
303 {
304 await e.PresenceErrorServiceUnavailable(e.To, "Room is full.", "en");
305 return;
306 }
307
308 if (Occupant.Persistent)
309 await Database.InsertLazy(Occupant);
310 }
311
312 await this.OccupantEntered(Room, Occupant, e.From, RoomCreated, e.Id, e.Content);
313 }
314
315 private async Task OccupantEntered(MucRoom Room, MucOccupant Occupant, XmppAddress From,
316 bool RoomCreated, string Id, XmlElement PresenceContent)
317 {
318 Room.RegisterFullJid(Occupant, From);
319
320 bool CanBroadcastOccupant = Room.CanBroadcastPresence(Occupant);
321 string OccupantXmlNoJid = Occupant.GetUserXml(null, null);
322 string OccupantXmlWithJid = Occupant.GetUserXml(From.Address, null);
323 XmppAddress OccupantJid = new XmppAddress(Occupant.OccupantJid);
324 bool CanGetMembership = (Occupant.Role?.ReceiveOccupantPresence ?? false) && Room.CanGetMembership(Occupant);
325 bool CanViewJid = Room.CanDiscoverRealJids(Occupant);
326
327 foreach (MucOccupant Occupant2 in Room.GetOccupants())
328 {
329 if (AreEqual(Occupant2.FullJids, Occupant.FullJids))
330 continue;
331
332 if (CanGetMembership && Room.CanBroadcastPresence(Occupant2))
333 {
334 if (CanViewJid)
335 {
336 if (!(Occupant2.FullJids is null))
337 {
338 foreach (XmppAddress FullJid in Occupant2.FullJids)
339 {
340 await this.Server.Presence(string.Empty, Guid.NewGuid().ToString(), From, new XmppAddress(Occupant2.OccupantJid),
341 string.Empty, Occupant2.GetUserXml(FullJid.Address, null), this);
342 }
343 }
344 }
345 else
346 {
347 await this.Server.Presence(string.Empty, Guid.NewGuid().ToString(), From, new XmppAddress(Occupant2.OccupantJid),
348 string.Empty, Occupant2.GetUserXml(null, null), this);
349 }
350 }
351
352 if (CanBroadcastOccupant && (Occupant2.Role?.ReceiveOccupantPresence ?? false) && Room.CanGetMembership(Occupant2))
353 {
354 if (!(Occupant2.FullJids is null))
355 {
356 foreach (XmppAddress FullJid in Occupant2.FullJids)
357 {
358 await this.Server.Presence(string.Empty, Guid.NewGuid().ToString(), FullJid, OccupantJid, string.Empty,
359 Room.CanDiscoverRealJids(Occupant2) ? OccupantXmlWithJid : OccupantXmlNoJid, this);
360 }
361 }
362 }
363 }
364
365 List<int> StatusCodes = new List<int>();
366
367 if (Room.DiscoverRealJids == DiscoverReadJids.Anyone)
368 StatusCodes.Add(100);
369
370 StatusCodes.Add(110);
371
372 if (Room.Logging)
373 StatusCodes.Add(170);
374
375 if (RoomCreated)
376 StatusCodes.Add(201);
377
378 await this.Server.Presence(string.Empty, string.IsNullOrEmpty(Id) ? Guid.NewGuid().ToString() : Id, From, OccupantJid,
379 string.Empty, Occupant.GetUserXml(CanViewJid ? From.Address : null, null, StatusCodes.ToArray()), this);
380
381 string RoomAddr = XML.Encode(Room.Room + "@" + Room.Domain);
382 bool IncludeJid = Room.CanDiscoverRealJids(Occupant);
383
384 StringBuilder sb = new StringBuilder();
385
386 if (Room.Persistent && (Occupant.Role?.ReceiveMessages ?? false) && Room.MaxHistoryFetch > 0)
387 {
388 List<FilterField> Fields = new List<FilterField>()
389 {
390 new FilterFieldEqualTo("Domain", Room.Domain),
391 new FilterFieldEqualTo("Room", Room.Room)
392 };
393 int MaxStanzas = Room.MaxHistoryFetch;
394 int MaxSize = int.MaxValue;
395
396 if (!(PresenceContent is null))
397 {
398 foreach (XmlNode N in PresenceContent.ChildNodes)
399 {
400 if (N is XmlElement E && E.LocalName == "history" && E.NamespaceURI == NamespaceMuc)
401 {
402 foreach (XmlAttribute Attr in E.Attributes)
403 {
404 switch (Attr.Name)
405 {
406 case "maxchars":
407 if (int.TryParse(Attr.Value, out int i))
408 MaxSize = i;
409 break;
410
411 case "maxstanzas":
412 if (int.TryParse(Attr.Value, out i))
413 MaxStanzas = i;
414 break;
415
416 case "seconds":
417 if (int.TryParse(Attr.Value, out i))
418 Fields.Add(new FilterFieldGreaterOrEqualTo("Timestamp", DateTime.Now.AddSeconds(-i)));
419 break;
420
421 case "since":
422 if (XML.TryParse(Attr.Value, out DateTime TP))
423 Fields.Add(new FilterFieldGreaterOrEqualTo("Timestamp", TP));
424 break;
425 }
426 }
427 }
428 }
429 }
430
431 if (MaxSize > 0 && MaxStanzas > 0)
432 {
433 IEnumerable<MucMessage> History = await Database.Find<MucMessage>(0, MaxStanzas, new FilterAnd(Fields.ToArray()), "-Timestamp");
434 List<MucMessage> Messages = new List<MucMessage>();
435 string Xml;
436 int Len;
437
438 foreach (MucMessage Message in History)
439 Messages.Add(Message);
440
441 Messages.Reverse();
442
443 foreach (MucMessage Message in Messages)
444 {
445 sb.Append(Message.ContentXml);
446 sb.Append("<delay xmlns='");
448 sb.Append("' from='");
449 sb.Append(RoomAddr);
450 sb.Append("' stamp='");
451 sb.Append(XML.Encode(Message.Timestamp));
452 sb.Append("'/>");
453
454 if (IncludeJid && !CaseInsensitiveString.IsNullOrEmpty(Message.FullJid))
455 {
456 sb.Append("<addresses xmlns='");
458 sb.Append("'><address type='ofrom' jid='");
459 sb.Append(XML.Encode(Message.FullJid.Value));
460 sb.Append("'/></addresses>");
461 }
462
463 Xml = sb.ToString();
464 OccupantJid = new XmppAddress(Message.OccupantJid);
465 sb.Clear();
466
467 if (MaxSize < int.MaxValue)
468 {
469 Len = Xml.Length;
470 Len += 19; // <message...>...</message>
471
472 if (!string.IsNullOrEmpty(Message.MessageId))
473 {
474 Len += 6; // _id='...'
475 Len += XML.Encode(Message.MessageId).Length;
476 }
477
478 Len += 17; // _type='groupchat'
479 Len += 6; // _to='...'
480 Len += XML.Encode(From.Address).Length;
481 Len += 8; // _from='...'
482 Len += XML.Encode(OccupantJid.Address).Length;
483
484 if (!string.IsNullOrEmpty(Message.Language))
485 {
486 Len += 12; // _xml:lang='...'
487 Len += XML.Encode(Message.Language).Length;
488 }
489
490 Len += Xml.Length;
491
492 if (Len > MaxSize)
493 break;
494
495 MaxSize -= Len;
496 }
497
498 await this.Server.Message("groupchat", Message.MessageId, From, OccupantJid, Message.Language, Xml, this);
499 }
500 }
501 }
502
503 if (Room.SubjectTimestamp > DateTime.MinValue)
504 {
505 sb.Append("<subject>");
506 sb.Append(XML.Encode(Room.Subject));
507 sb.Append("</subject>");
508 sb.Append("<delay xmlns='");
510 sb.Append("' from='");
511 sb.Append(RoomAddr);
512 sb.Append("' stamp='");
513 sb.Append(XML.Encode(Room.SubjectTimestamp));
514 sb.Append("'/>");
515
516 await this.Server.Message("groupchat", Guid.NewGuid().ToString(), From, new XmppAddress(Room.SubjectJid), string.Empty, sb.ToString(), this);
517 }
518 }
519
520 protected override async Task PresenceNoHandlerReceived(PresenceEventArgs e)
521 {
522 MucRoom Room = await this.GetRoom(e.To.Account, e.To.Domain);
523 if (Room is null || !Room.TryGetOccupantFromFullJid(e.From.Address, out MucOccupant Occupant))
524 {
525 StringBuilder Xml = new StringBuilder();
526
527 Xml.Append("<x xmlns='");
529 Xml.Append("'><item affiliation='none' role='none'><reason>You are not in the room.</reason></item>");
530 Xml.Append("<status code='110'/>");
531 Xml.Append("<status code='307'/>");
532 Xml.Append("<status code='333'/>");
533 Xml.Append("</x>");
534
535 await this.Server.Presence(string.Empty, string.IsNullOrEmpty(e.Id) ? Guid.NewGuid().ToString() : e.Id,
536 e.From, e.To, string.Empty, Xml.ToString(), this);
537
538 return;
539 }
540
541 if (e.Type == "error")
542 {
543 Room.Remove(Occupant);
544
545 StringBuilder Xml = new StringBuilder();
546
547 Xml.Append("<x xmlns='");
549 Xml.Append("'><item affiliation='none' role='none'/>");
550 Xml.Append("<status code='333'/>");
551 Xml.Append("</x>");
552
553 await this.PresenceXmlToAll(e.To, Room.GetOccupants(), Xml.ToString());
554
555 return;
556 }
557
558 if (e.To.Resource == Occupant.NickName)
559 {
560 XmppAddress OccupantJid = new XmppAddress(Occupant.OccupantJid);
561 bool Broadcast = Room.CanBroadcastPresence(Occupant);
562
563 if (e.Type == "unavailable")
564 {
565 if (Room.UnregisterFullJid(Occupant, e.From))
566 {
567 Room.Remove(Occupant);
568 Occupant.Role = new Roles.None();
569 }
570 }
571
572 if (Broadcast)
573 {
574 string SuffixWithJid = null;
575 string SuffixWithoutJid = null;
576 string Suffix;
577
578 foreach (MucOccupant Occupant2 in Room.GetOccupants())
579 {
580 if (!(Occupant2.Role?.ReceiveOccupantPresence ?? false))
581 continue;
582
583 if (Room.CanDiscoverRealJids(Occupant2))
584 {
585 if (SuffixWithJid is null)
586 SuffixWithJid = Occupant.GetUserXml(e.From.Address, null);
587
588 Suffix = SuffixWithJid;
589 }
590 else
591 {
592 if (SuffixWithoutJid is null)
593 SuffixWithoutJid = Occupant.GetUserXml(null, null);
594
595 Suffix = SuffixWithoutJid;
596 }
597
598 if (!(Occupant2.FullJids is null))
599 {
600 foreach (XmppAddress FullJid in Occupant2.FullJids)
601 await this.Server.Presence(e.Type, e.Id, FullJid, OccupantJid, e.Language, InnerXmlNoJabberNamespace(e.Stanza?.StanzaElement) + Suffix, this);
602 }
603 }
604 }
605
606 await this.Server.Presence(e.Type, e.Id, e.From, OccupantJid, e.Language,
607 InnerXmlNoJabberNamespace(e.Stanza?.StanzaElement) + Occupant.GetUserXml(null, null, 110), this);
608 }
609 else if (string.IsNullOrEmpty(e.Type))
610 {
611 MucOccupant Occupant2 = await this.GetOccupantByNickName(Room, e.To.Resource);
612 if (!(Occupant2 is null) && !Occupant2.ContainsFullJid(e.From))
613 {
614 await e.PresenceErrorConflict(e.To, "Nick-name conflict.", "en");
615 return;
616 }
617
618 if (!(Occupant.Role?.ChangeRoomNickname ?? false))
619 {
620 await e.PresenceErrorNotAcceptable(e.To, "Nick-name cannot be changed.", "en");
621 return;
622 }
623
624 XmppAddress OldAddr = new XmppAddress(Occupant.OccupantJid);
625
626 Room.UnregisterFullJid(Occupant, e.From);
627
628 if (Occupant.HasFullJids)
629 {
630 Occupant = new MucOccupant()
631 {
632 Room = e.To.Account,
633 Domain = e.To.Domain,
634 NickName = e.To.Resource,
635 BareJid = e.From.BareJid,
636 FullJids = new XmppAddress[] { e.From },
637 Affiliation = Room.GetDefaultAffiliation(e.From.BareJid),
638 Persistent = Room.Persistent,
639 Archive = Room.Archive
640 };
641
642 Occupant.Role = Occupant.Affiliation.GetDefaultRole(Room);
643
644 if (!(Occupant is null) && !Room.Add(Occupant))
645 {
646 await e.PresenceErrorServiceUnavailable(e.To, "Room is full.", "en");
647 return;
648 }
649
650 if (Occupant.Persistent)
651 await Database.InsertLazy(Occupant);
652 }
653 else
654 {
655 Occupant.NickName = e.To.Resource;
656
657 if (Occupant.Persistent)
658 await Database.UpdateLazy(Occupant);
659 }
660
661 Room.RegisterFullJid(Occupant, e.From);
662
663 XmppAddress NewAddr = new XmppAddress(Occupant.OccupantJid);
664 string OldIncludingJid = null;
665 string OldExcludingJid = null;
666 string NewIncludingJid = null;
667 string NewExcludingJid = null;
668 string OldXml;
669 string NewXml;
670
671 foreach (MucOccupant Occupant3 in Room.GetOccupants())
672 {
673 if (Room.CanDiscoverRealJids(Occupant3))
674 {
675 if (OldIncludingJid is null)
676 OldIncludingJid = Occupant.GetUserXml(e.From.Address, OldAddr.Resource, 303);
677
678 OldXml = OldIncludingJid;
679 }
680 else
681 {
682 if (OldExcludingJid is null)
683 OldExcludingJid = Occupant.GetUserXml(null, OldAddr.Resource, 303);
684
685 OldXml = OldExcludingJid;
686 }
687
688 if (Room.CanDiscoverRealJids(Occupant))
689 {
690 if (NewIncludingJid is null)
691 NewIncludingJid = Occupant.GetUserXml(e.From.Address, NewAddr.Resource);
692
693 NewXml = NewIncludingJid;
694 }
695 else
696 {
697 if (NewExcludingJid is null)
698 NewExcludingJid = Occupant.GetUserXml(null, NewAddr.Resource);
699
700 NewXml = NewExcludingJid;
701 }
702
703 if (!(Occupant3.FullJids is null))
704 {
705 foreach (XmppAddress FullJid in Occupant3.FullJids)
706 {
707 if (FullJid != e.From)
708 {
709 await this.Server.Presence("unavailable", Guid.NewGuid().ToString(), FullJid, OldAddr, string.Empty, OldXml, this);
710 await this.Server.Presence(e.Type, e.Id, FullJid, NewAddr, string.Empty, NewXml, this);
711 }
712 }
713 }
714 }
715
716 await this.Server.Presence("unavailable", Guid.NewGuid().ToString(), e.From, OldAddr, string.Empty,
717 Occupant.GetUserXml(Room.CanDiscoverRealJids(Occupant) ? e.From.Address : null, OldAddr.Resource, 303, 110), this);
718
719 await this.Server.Presence(e.Type, e.Id, e.From, NewAddr, string.Empty,
720 Occupant.GetUserXml(Room.CanDiscoverRealJids(Occupant) ? e.From.Address : null, NewAddr.Resource, 110), this);
721 }
722 else
723 await e.PresenceErrorNotAcceptable(e.To, "Invalid type or nick-name.", "en");
724 }
725
726 private async Task<MucRoom> GetRoom(CaseInsensitiveString RoomId, CaseInsensitiveString Domain)
727 {
728 if (this.rooms.TryGetValue(RoomId, out MucRoom Room))
729 return Room;
730
731 Room = await Database.FindFirstDeleteRest<MucRoom>(new FilterAnd(
732 new FilterFieldEqualTo("Domain", Domain),
733 new FilterFieldEqualTo("Room", RoomId)));
734
735 if (!(Room is null))
736 this.rooms[RoomId] = Room;
737
738 return Room;
739 }
740
741 private async Task<MucOccupant> GetOccupantByBareJid(MucRoom Room, CaseInsensitiveString BareJid)
742 {
743 if (Room.TryGetOccupantFromBareJid(BareJid, out MucOccupant Occupant))
744 return Occupant;
745
746 Occupant = await Database.FindFirstDeleteRest<MucOccupant>(new FilterAnd(
747 new FilterFieldEqualTo("Domain", Room.Domain),
748 new FilterFieldEqualTo("Room", Room.Room),
749 new FilterFieldEqualTo("BareJid", BareJid)));
750
751 if (!(Occupant is null))
752 Room.Add(Occupant);
753
754 return Occupant;
755 }
756
757 private async Task<MucOccupant> GetOccupantByNickName(MucRoom Room, CaseInsensitiveString NickName)
758 {
759 if (Room.TryGetOccupantFromNick(NickName, out MucOccupant Occupant))
760 return Occupant;
761
762 Occupant = await Database.FindFirstDeleteRest<MucOccupant>(new FilterAnd(
763 new FilterFieldEqualTo("Domain", Room.Domain),
764 new FilterFieldEqualTo("Room", Room.Room),
765 new FilterFieldEqualTo("NickName", NickName)));
766
767 if (!(Occupant is null))
768 Room.Add(Occupant);
769
770 return Occupant;
771 }
772
773 protected override async Task MessageNoHandlerReceived(MessageEventArgs e)
774 {
775 if (!e.From.IsFullJID)
776 return;
777
778 MucRoom Room = await this.GetRoom(e.To.Account, e.To.Domain);
779 if (Room is null)
780 {
781 await e.MessageErrorNotAcceptable(e.To, "Not occupant of room.", "en");
782 return;
783 }
784
785 if (!Room.TryGetOccupantFromFullJid(e.From.Address, out MucOccupant Occupant))
786 {
787 await e.MessageErrorNotAcceptable(e.To, "Not occupant of room.", "en");
788 return;
789 }
790
791 if (e.Type == "error")
792 {
793 Room.Remove(Occupant);
794
795 StringBuilder Xml = new StringBuilder();
796
797 Xml.Append("<x xmlns='");
799 Xml.Append("'><item affiliation='none' role='none'/>");
800 Xml.Append("<status code='333'/>");
801 Xml.Append("</x>");
802
803 await this.PresenceXmlToAll(e.To, Room.GetOccupants(), Xml.ToString());
804
805 return;
806 }
807
808 string Subject = null;
809 bool OnlySubject = true;
810
811 foreach (XmlNode N in e.Message.ChildNodes)
812 {
813 if (N is XmlElement E)
814 {
815 if (E.Name == "subject" && E.NamespaceURI == e.Message.NamespaceURI)
816 Subject = E.InnerText;
817 else
818 OnlySubject = false;
819 }
820 }
821
822 switch (e.Type)
823 {
824 case "groupchat":
825 if (!e.To.IsBareJID)
826 {
827 await e.MessageErrorBadRequest(e.To, "Group chat messages must be addressed to the room JID.", "en");
828 return;
829 }
830
831 if (!(Occupant.Role?.SendMessagesToAll ?? false) || !(Occupant.Role?.PresentInRoom ?? false))
832 {
833 await e.MessageErrorForbidden(e.To, "Not allowed to send messages to occupants of room.", "en");
834 return;
835 }
836
837 if (!(Subject is null) && OnlySubject)
838 {
839 if ((Room.AllowChangeSubject && (Occupant.Role?.ModifySubject ?? false)) ||
840 Room.IsModerator(Occupant))
841 {
842 Room.Subject = Subject;
843 Room.SubjectJid = Occupant.OccupantJid;
844 Room.SubjectTimestamp = DateTime.UtcNow;
845
846 if (Room.Persistent)
847 await Database.UpdateLazy(Room);
848 }
849 else
850 {
851 await e.MessageErrorForbidden(e.To, "Not allowed to change room subject.", "en");
852 return;
853 }
854 }
855
856 if (Room.Persistent && Room.Logging && !OnlySubject)
857 {
858 await Database.InsertLazy(new MucMessage()
859 {
860 Domain = e.To.Domain,
861 Room = e.To.Account,
862 NickName = Occupant.NickName,
863 MessageId = e.Id,
864 Timestamp = DateTime.UtcNow,
865 ContentXml = InnerXmlNoJabberNamespace(e.Message),
866 Language = e.Language,
867 FullJid = e.From.Address,
868 ArchiveDays = Room.Archive ? Room.ArchiveDays : 0
869 });
870 }
871
872 XmppAddress OccupantJid = new XmppAddress(Occupant.OccupantJid);
873 string SenderXml = null;
874
875 foreach (MucOccupant Occupant2 in Room.GetOccupants())
876 {
877 if (!(Occupant2.FullJids is null))
878 {
879 bool IncludeJid = Room.CanDiscoverRealJids(Occupant2);
880 string Xml = InnerXmlNoJabberNamespace(e.Message);
881
882 if (IncludeJid)
883 {
884 if (SenderXml is null)
885 {
886 StringBuilder sb = new StringBuilder();
887
888 sb.Append("<addresses xmlns='");
890 sb.Append("'><address type='ofrom' jid='");
891 sb.Append(XML.Encode(e.From.Address.Value));
892 sb.Append("'/></addresses>");
893
894 SenderXml = sb.ToString();
895 }
896
897 Xml += SenderXml;
898 }
899
900 foreach (XmppAddress To in Occupant2.FullJids)
901 await this.Server.Message(e.Type, e.Id, To, OccupantJid, e.Language, Xml, this);
902 }
903 }
904 break;
905
906 default:
907 await this.SendPrivateMessage(Room, Occupant, e);
908 break;
909 }
910 }
911
912 private async Task SendPrivateMessage(MucRoom Room, MucOccupant Occupant, MessageEventArgs e)
913 {
914 if (!e.To.IsFullJID)
915 {
916 await e.MessageErrorBadRequest(e.To, "Private messages must be addressed to individual room occupants.", "en");
917 return;
918 }
919
920 if (!Room.CanSendPrivateMessage(Occupant))
921 {
922 await e.MessageErrorForbidden(e.To, "Not allowed to send private messages to occupants of room", "en");
923 return;
924 }
925
926 MucOccupant Destination = await this.GetOccupantByNickName(Room, e.To.Resource);
927 if (Destination is null)
928 {
929 await e.MessageErrorItemNotFound(e.To, "Recipient not an occupant of the room", "en");
930 return;
931 }
932
933 if (!(Destination.FullJids is null))
934 {
935 XmppAddress OccupantJid = new XmppAddress(Occupant.OccupantJid);
936
937 foreach (XmppAddress To in Destination.FullJids)
938 await this.Server.Message(e.Type, e.Id, To, OccupantJid, e.Language, InnerXmlNoJabberNamespace(e.Message), this);
939 }
940 }
941
942 private async Task UserCommandHandler(object Sender, MessageEventArgs e)
943 {
944 if (e.From.IsBareJID)
945 {
946 await e.MessageErrorJidMalformed(e.To, "From JID must be a full JID.", "en");
947 return;
948 }
949
950 if (!e.To.HasAccount)
951 {
952 await e.MessageErrorJidMalformed(e.To, "Missing room name.", "en");
953 return;
954 }
955
957 MucRoom Room = await this.GetRoom(RoomId, e.To.Domain);
958
959 if (Room is null)
960 {
961 await e.MessageErrorItemNotFound(e.To, "Room not found.", "en");
962 return;
963 }
964
965 if (e.To.IsBareJID)
966 {
967 foreach (XmlNode N in e.Content.ChildNodes)
968 {
969 if (!(N is XmlElement E) || E.NamespaceURI != NamespaceMucUser)
970 continue;
971
972 switch (E.LocalName)
973 {
974 case "invite":
975
976 if (!Room.TryGetOccupantFromFullJid(e.From.Address, out MucOccupant Occupant))
977 {
978 await e.MessageErrorForbidden(e.To, "Not an occupant of room.", "en");
979 return;
980 }
981
982 if (!(Occupant.Affiliation is Affiliations.Admin))
983 {
984 if (!Room.AllowInvites)
985 {
986 await e.MessageErrorForbidden(e.To, "Room does not allow invitations.", "en");
987 break;
988 }
989
990 if (!(Occupant.Role?.InviteOtherUsers ?? false))
991 {
992 await e.MessageErrorForbidden(e.To, "You are not allowed to send invitations to this room.", "en");
993 break;
994 }
995 }
996
997 if (Room.MembersOnly)
998 {
999 if (!Room.IsAdmin(Occupant))
1000 {
1001 await e.MessageErrorForbidden(e.To, "Only room administrators or owners are allowed to invite new members.", "en");
1002 break;
1003 }
1004 }
1005
1006 XmppAddress To = new XmppAddress(XML.Attribute(E, "to"));
1007 string Reason = null;
1008
1009 if (!To.HasAccount || !To.IsBareJID)
1010 {
1011 await e.MessageErrorBadRequest(e.To, "Bad address.", "en");
1012 break;
1013 }
1014
1015 if (Room.MembersOnly && Room.AddMember(To.BareJid) && Room.Persistent)
1016 await Database.UpdateLazy(Room);
1017
1018 foreach (XmlNode N2 in E.ChildNodes)
1019 {
1020 if (N2 is XmlElement E2 && E2.LocalName == "reason" && E2.NamespaceURI == NamespaceMucUser)
1021 {
1022 Reason = E2.InnerText;
1023 break;
1024 }
1025 }
1026
1027 StringBuilder Xml = new StringBuilder();
1028
1029 Xml.Append("<x xmlns='");
1030 Xml.Append(NamespaceMucUser);
1031 Xml.Append("'><invite from='");
1032 Xml.Append(XML.Encode(e.From.Address.Value));
1033 Xml.Append("'>");
1034
1035 if (!(Reason is null))
1036 {
1037 Xml.Append("<reason>");
1038 Xml.Append(XML.Encode(Reason));
1039 Xml.Append("</reason>");
1040 }
1041
1042 Xml.Append("</invite>");
1043
1044 if (Room.PasswordProtected)
1045 {
1046 Xml.Append("<password>");
1047 Xml.Append(XML.Encode(Room.Password));
1048 Xml.Append("</password>");
1049 }
1050
1051 Xml.Append("</x>");
1052
1053 await this.Server.Message(string.Empty, Guid.NewGuid().ToString(), To, e.To, e.Language, Xml.ToString(), this);
1054 break;
1055
1056 case "decline":
1057
1058 To = new XmppAddress(XML.Attribute(E, "to"));
1059 Reason = null;
1060
1061 if (!To.HasAccount || !To.IsBareJID)
1062 {
1063 await e.MessageErrorBadRequest(e.To, "Bad address.", "en");
1064 break;
1065 }
1066
1067 if (Room.MembersOnly && Room.RemoveMember(e.From.BareJid) && Room.Persistent)
1068 await Database.UpdateLazy(Room);
1069
1070 foreach (XmlNode N2 in E.ChildNodes)
1071 {
1072 if (N2 is XmlElement E2 && E2.LocalName == "reason" && E2.NamespaceURI == NamespaceMucUser)
1073 {
1074 Reason = E2.InnerText;
1075 break;
1076 }
1077 }
1078
1079 Xml = new StringBuilder();
1080
1081 Xml.Append("<x xmlns='");
1082 Xml.Append(NamespaceMucUser);
1083 Xml.Append("'><decline from='");
1084 Xml.Append(XML.Encode(e.From.BareJid.Value));
1085 Xml.Append("'>");
1086
1087 if (!(Reason is null))
1088 {
1089 Xml.Append("<reason>");
1090 Xml.Append(XML.Encode(Reason));
1091 Xml.Append("</reason>");
1092 }
1093
1094 Xml.Append("</decline></x>");
1095
1096 await this.Server.Message(string.Empty, Guid.NewGuid().ToString(), To, e.To, e.Language, Xml.ToString(), this);
1097 break;
1098 }
1099 }
1100 }
1101 else
1102 {
1103 if (!Room.TryGetOccupantFromFullJid(e.From.Address, out MucOccupant Occupant))
1104 {
1105 await e.MessageErrorForbidden(e.To, "Not an occupant of room.", "en");
1106 return;
1107 }
1108
1109 if (e.Type == "chat")
1110 await this.SendPrivateMessage(Room, Occupant, e);
1111 else
1112 await e.MessageErrorJidMalformed(e.To, "Message must be addressed to room.", "en");
1113 }
1114 }
1115
1116 private async Task RegisterUserGetFormHandler(object Sender, IqEventArgs e)
1117 {
1118 if (!e.To.HasAccount)
1119 {
1120 await e.IqErrorJidMalformed(e.To, "Missing room name.", "en");
1121 return;
1122 }
1123
1124 if (!e.To.IsBareJID)
1125 {
1126 await e.IqErrorJidMalformed(e.To, "Message must be addressed to room.", "en");
1127 return;
1128 }
1129
1130 CaseInsensitiveString RoomId = e.To.Account;
1131 MucRoom Room = await this.GetRoom(RoomId, e.To.Domain);
1132
1133 if (Room is null)
1134 {
1135 await e.IqErrorItemNotFound(e.To, "Room not found.", "en");
1136 return;
1137 }
1138
1139 MucOccupant Occupant = await this.GetOccupantByBareJid(Room, e.From.BareJid);
1140 StringBuilder Xml = new StringBuilder();
1141
1142 if (!(Occupant is null) && !(Occupant.Role is null))
1143 {
1144 Xml.Append("<query xmlns='");
1145 Xml.Append(XmppServer.RegisterNamespace);
1146 Xml.Append("'><registered/><username>");
1147 Xml.Append(Occupant.NickName);
1148 Xml.Append("</username></query>");
1149
1150 await e.IqResult(Xml.ToString(), e.To);
1151 return;
1152 }
1153
1154 Xml.Append("<query xmlns='");
1155 Xml.Append(XmppServer.RegisterNamespace);
1156 Xml.Append("'><instructions>Fill out the form and submit it, to register in the room.</instructions><x xmlns='");
1157 Xml.Append(XmppServer.DataFormsNamespace);
1158 Xml.Append("' type='form'><title>");
1159 Xml.Append(string.IsNullOrEmpty(Room.Name) ? "Room" : Room.Name);
1160 Xml.Append(" Registration</title><instructions>Fill out the form and submit it, to register in the room.</instructions>");
1161 Xml.Append("<field type='hidden' var='FORM_TYPE'><value>");
1162 Xml.Append(FormTypeRegister);
1163 Xml.Append("</value></field>");
1164 Xml.Append("<field label='Given Name' type='text-single' var='muc#register_first'><required/>");
1165
1166 if (!string.IsNullOrEmpty(Occupant?.FirstName))
1167 {
1168 Xml.Append("<value>");
1169 Xml.Append(XML.Encode(Occupant.FirstName));
1170 Xml.Append("</value>");
1171 }
1172
1173 Xml.Append("</field>");
1174 Xml.Append("<field label='Family Name' type='text-single' var='muc#register_last'><required/>");
1175
1176 if (!string.IsNullOrEmpty(Occupant?.LastName))
1177 {
1178 Xml.Append("<value>");
1179 Xml.Append(XML.Encode(Occupant.LastName));
1180 Xml.Append("</value>");
1181 }
1182
1183 Xml.Append("</field>");
1184 Xml.Append("<field label='Desired Nickname' type='text-single' var='muc#register_roomnick'><required/>");
1185
1186 if (!string.IsNullOrEmpty(Occupant?.NickName))
1187 {
1188 Xml.Append("<value>");
1189 Xml.Append(XML.Encode(Occupant.NickName));
1190 Xml.Append("</value>");
1191 }
1192
1193 Xml.Append("</field>");
1194 Xml.Append("<field label='Your URL' type='text-single' var='muc#register_url'>");
1195
1196 if (!string.IsNullOrEmpty(Occupant?.Url))
1197 {
1198 Xml.Append("<value>");
1199 Xml.Append(XML.Encode(Occupant.Url));
1200 Xml.Append("</value>");
1201 }
1202
1203 Xml.Append("</field>");
1204 Xml.Append("<field label='Email Address' type='text-single' var='muc#register_email'>");
1205
1206 if (!string.IsNullOrEmpty(Occupant?.EMail))
1207 {
1208 Xml.Append("<value>");
1209 Xml.Append(XML.Encode(Occupant.EMail));
1210 Xml.Append("</value>");
1211 }
1212
1213 Xml.Append("</field>");
1214 Xml.Append("<field label='FAQ Entry' type='text-multi' var='muc#register_faqentry'>");
1215
1216 if (!string.IsNullOrEmpty(Occupant?.FaqEntry))
1217 {
1218 Xml.Append("<value>");
1219 Xml.Append(XML.Encode(Occupant.FaqEntry));
1220 Xml.Append("</value>");
1221 }
1222
1223 Xml.Append("</field>");
1224 Xml.Append("</x></query>");
1225
1226 await e.IqResult(Xml.ToString(), e.To);
1227 }
1228
1229 private async Task RegisterUserSubmitFormHandler(object Sender, IqEventArgs e)
1230 {
1231 if (!e.To.HasAccount)
1232 {
1233 await e.IqErrorJidMalformed(e.To, "Missing room name.", "en");
1234 return;
1235 }
1236
1237 if (!e.To.IsBareJID)
1238 {
1239 await e.IqErrorJidMalformed(e.To, "Message must be addressed to room.", "en");
1240 return;
1241 }
1242
1243 CaseInsensitiveString RoomId = e.To.Account;
1244 MucRoom Room = await this.GetRoom(RoomId, e.To.Domain);
1245
1246 if (Room is null)
1247 {
1248 await e.IqErrorItemNotFound(e.To, "Room not found.", "en");
1249 return;
1250 }
1251
1252 MucOccupant Occupant = await this.GetOccupantByBareJid(Room, e.From.BareJid);
1253 StringBuilder Xml = new StringBuilder();
1254
1255 if (!(Occupant is null) && !(Occupant.Role is null))
1256 {
1257 Xml.Append("<query xmlns='");
1258 Xml.Append(XmppServer.RegisterNamespace);
1259 Xml.Append("'><registered/><username>");
1260 Xml.Append(Occupant.NickName);
1261 Xml.Append("</username></query>");
1262
1263 await e.IqResult(Xml.ToString(), e.To);
1264 return;
1265 }
1266
1267 foreach (XmlNode N in e.Query.ChildNodes)
1268 {
1269 if (N is XmlElement E && E.LocalName == "x" && E.NamespaceURI == XmppServer.DataFormsNamespace)
1270 {
1271 string Type = XML.Attribute(E, "type");
1272 if (Type == "cancel")
1273 {
1274 if (!(Occupant is null))
1275 {
1276 Room.Remove(Occupant);
1277
1278 if (Occupant.Persistent)
1279 await Database.DeleteLazy(Occupant);
1280 }
1281
1282 await e.IqResult(string.Empty, e.To);
1283 return;
1284 }
1285
1286 if (Type != "submit")
1287 {
1288 await e.IqErrorBadRequest(e.To, "Expected form submission.", "en");
1289 return;
1290 }
1291
1292 string FormType = null;
1293 string FirstName = null;
1294 string LastName = null;
1295 string NickName = null;
1296 string Url = string.Empty;
1297 string EMail = string.Empty;
1298 string FaqEntry = string.Empty;
1299
1300 foreach (XmlNode N2 in E.ChildNodes)
1301 {
1302 if (N2 is XmlElement E2 && E2.LocalName == "field" && E2.NamespaceURI == XmppServer.DataFormsNamespace)
1303 {
1304 string Name = XML.Attribute(E2, "var");
1305
1306 switch (Name)
1307 {
1308 case "FORM_TYPE":
1309 FormType = GetValue(E2);
1310 break;
1311
1312 case "muc#register_first":
1313 FirstName = GetValue(E2);
1314 break;
1315
1316 case "muc#register_last":
1317 LastName = GetValue(E2);
1318 break;
1319
1320 case "muc#register_roomnick":
1321 NickName = GetValue(E2);
1322 break;
1323
1324 case "muc#register_url":
1325 Url = GetValue(E2);
1326 break;
1327
1328 case "muc#register_email":
1329 EMail = GetValue(E2);
1330 break;
1331
1332 case "muc#register_faqentry":
1333 FaqEntry = GetValue(E2);
1334 break;
1335
1336 default:
1337 await e.IqErrorBadRequest(e.To, "Unknown field: " + Name, "en");
1338 return;
1339 }
1340 }
1341 }
1342
1343 if (FormType is null || string.IsNullOrEmpty(FirstName) || string.IsNullOrEmpty(LastName) || string.IsNullOrEmpty(NickName))
1344 {
1345 await e.IqErrorBadRequest(e.To, "Missing field values.", "en");
1346 return;
1347 }
1348
1349 if (!string.IsNullOrEmpty(FormType) && FormType != FormTypeRegister)
1350 {
1351 await e.IqErrorBadRequest(e.To, "Unexpected FORM_TYPE.", "en");
1352 return;
1353 }
1354
1355 MucOccupant Occupant2 = await this.GetOccupantByNickName(Room, NickName);
1356 if (!(Occupant2 is null) && Occupant2.BareJid != e.From.BareJid)
1357 {
1358 await e.IqErrorConflict(e.To, "Nick-name already taken.", "en");
1359 return;
1360 }
1361
1362 if (!(Occupant is null))
1363 {
1364 bool Changed = false;
1365
1366 if (Occupant.FirstName != FirstName)
1367 {
1368 Occupant.FirstName = FirstName;
1369 Changed = true;
1370 }
1371
1372 if (Occupant.LastName != LastName)
1373 {
1374 Occupant.LastName = LastName;
1375 Changed = true;
1376 }
1377
1378 if (Occupant.NickName != NickName)
1379 {
1380 Occupant.NickName = NickName;
1381 Changed = true;
1382 }
1383
1384 if (Occupant.Url != Url)
1385 {
1386 Occupant.Url = Url;
1387 Changed = true;
1388 }
1389
1390 if (Occupant.EMail != EMail)
1391 {
1392 Occupant.EMail = EMail;
1393 Changed = true;
1394 }
1395
1396 if (Occupant.FaqEntry != FaqEntry)
1397 {
1398 Occupant.FaqEntry = FaqEntry;
1399 Changed = true;
1400 }
1401
1402 if (Changed && Occupant.Persistent)
1403 await Database.UpdateLazy(Occupant);
1404 }
1405
1406 Xml.Append("<x xmlns='");
1407 Xml.Append(XmppServer.DataFormsNamespace);
1408 Xml.Append("' type='form'><title>Registration request</title>");
1409 Xml.Append("<instructions>To approve this registration request, select the &quot;Allow this person to register with the room?&quot; checkbox and click OK. To skip this request, click the cancel button.</instructions>");
1410 Xml.Append("<field var='FORM_TYPE' type='hidden'><value>");
1411 Xml.Append(FormTypeRegister);
1412 Xml.Append("</value></field>");
1413 Xml.Append("<field var='muc#occupant_jid' type='hidden'><value>");
1414 Xml.Append(XML.Encode(e.From.Address.Value));
1415 Xml.Append("</value></field>");
1416 Xml.Append("<field var='muc#register_first' type='text-single' label='Given Name'><value>");
1417 Xml.Append(XML.Encode(FirstName));
1418 Xml.Append("</value></field>");
1419 Xml.Append("<field var='muc#register_last' type='text-single' label='Family Name'><value>");
1420 Xml.Append(XML.Encode(LastName));
1421 Xml.Append("</value></field>");
1422 Xml.Append("<field var='muc#register_roomnick' type='text-single' label='Desired Nickname'><value>");
1423 Xml.Append(XML.Encode(NickName));
1424 Xml.Append("</value></field>");
1425 Xml.Append("<field var='muc#register_url' type='text-single' label='User URL'><value>");
1426 Xml.Append(XML.Encode(Url));
1427 Xml.Append("</value></field>");
1428 Xml.Append("<field var='muc#register_email' type='text-single' label='Email Address'><value>");
1429 Xml.Append(XML.Encode(EMail));
1430 Xml.Append("</value></field>");
1431 Xml.Append("<field var='muc#register_faqentry' type='text-multi' label='FAQ Entry'><value>");
1432 Xml.Append(XML.Encode(FaqEntry));
1433 Xml.Append("</value></field>");
1434 Xml.Append("<field var='muc#register_allow' type='boolean' label='Allow this person to register with the room?'><value>false</value></field></x>");
1435
1436 await this.SendToAdmin(Room, string.Empty, Xml.ToString());
1437
1438 await e.IqResult(string.Empty, e.To);
1439 return;
1440 }
1441 }
1442
1443 await e.IqErrorBadRequest(e.To, string.Empty, string.Empty);
1444 }
1445
1446 private async Task SendToAdmin(MucRoom Room, string Type, string Xml)
1447 {
1448 Dictionary<string, bool> Sent = new Dictionary<string, bool>();
1449 XmppAddress From = new XmppAddress(Room.Room + "@" + Room.Domain);
1450
1451 if (!(Room.Admins is null))
1452 {
1453 foreach (CaseInsensitiveString Jid in Room.Admins)
1454 {
1455 if (!Sent.ContainsKey(Jid))
1456 {
1457 await this.Server.Message(Type, Guid.NewGuid().ToString(), new XmppAddress(Jid), From, "en", Xml, this);
1458 Sent[Jid] = true;
1459 }
1460 }
1461 }
1462
1463 if (!(Room.Owners is null))
1464 {
1465 foreach (CaseInsensitiveString Jid in Room.Owners)
1466 {
1467 if (!Sent.ContainsKey(Jid))
1468 {
1469 await this.Server.Message(Type, Guid.NewGuid().ToString(), new XmppAddress(Jid), From, "en", Xml, this);
1470 Sent[Jid] = true;
1471 }
1472 }
1473 }
1474 }
1475
1476 public static async Task<string> GenerateMessageXml(string Markdown)
1477 {
1478 StringBuilder sb = new StringBuilder();
1479
1480 sb.AppendLine("BodyOnly: 1");
1481 sb.AppendLine();
1482 sb.Append(Markdown);
1483
1484 MarkdownDocument Doc = await MarkdownDocument.CreateAsync(sb.ToString());
1485 string HTML = await Doc.GenerateHTML();
1486 string Text = await Doc.GeneratePlainText();
1487
1488 sb.Clear();
1489
1490 sb.Append("<body>");
1491 sb.Append(XML.Encode(Text));
1492 sb.Append("</body>");
1493 sb.Append("<html xmlns='http://jabber.org/protocol/xhtml-im'>");
1494 sb.Append("<body xmlns='http://www.w3.org/1999/xhtml'>");
1495 sb.Append(HTML);
1496 sb.Append("</body></html>");
1497 sb.Append("<content xmlns='");
1498 sb.Append(XmppServer.ContentNamespace);
1499 sb.Append("' type='text/markdown'>");
1500 sb.Append(XML.HtmlValueEncode(Markdown));
1501 sb.Append("</content>");
1502
1503 return sb.ToString();
1504 }
1505
1511 protected override async Task DiscoveryQueryGet(object Sender, IqEventArgs e)
1512 {
1513 XmlElement E = e.Query;
1514 string Account = e.To.Account;
1515 MucRoom Room;
1516
1517 if (string.IsNullOrEmpty(Account))
1518 Room = null;
1519 else
1520 {
1521 Room = await this.GetRoom(Account, e.To.Domain);
1522 if (Room is null)
1523 {
1524 await e.IqErrorItemNotFound(e.To, "Item not found.", "en");
1525 return;
1526 }
1527
1528 if (Room.MembersOnly && !Room.IsMember(e.From.BareJid))
1529 {
1530 await e.IqErrorBadRequest(e.To, "You are not a member of the room.", "en");
1531 return;
1532 }
1533
1534 if (!string.IsNullOrEmpty(e.To.Resource))
1535 {
1536 if (!Room.TryGetOccupantFromFullJid(e.From.Address, out MucOccupant _))
1537 {
1538 await e.IqErrorBadRequest(e.To, "You are not in the room.", "en");
1539 return;
1540 }
1541
1542 MucOccupant Occupant = await this.GetOccupantByNickName(Room, e.To.Resource);
1543 if (Occupant is null)
1544 {
1545 await e.IqErrorItemNotFound(e.To, "Occupant not found.", "en");
1546 return;
1547 }
1548
1549 XmppAddress[] FullJids = Occupant.FullJids;
1550 if (FullJids is null || FullJids.Length == 0)
1551 {
1552 await e.IqErrorServiceUnavailable(e.To, "Occupant not connected.", "en");
1553 return;
1554 }
1555 else if (FullJids.Length > 1)
1556 {
1557 await e.IqErrorServiceUnavailable(e.To, "Occupant connected with multiple clients.", "en");
1558 return;
1559 }
1560
1561 await this.Server.SendIqRequest("get", e.To.ToBareJID(), FullJids[0], string.Empty, InnerXmlNoJabberNamespace(e.IQ), async (sender2, e2) =>
1562 {
1563 if (e2.Ok)
1564 await e.IqResult(InnerXmlNoJabberNamespace(e2.Response), e.To);
1565 else
1566 await e.IqError(e2.ErrorTypeString, e2.ErrorElement?.OuterXml ?? string.Empty, e.To, e2.ErrorText, string.Empty);
1567 }, null);
1568
1569 return;
1570 }
1571 }
1572
1573 string Node = XML.Attribute(E, "node");
1574 if (!string.IsNullOrEmpty(Node) && Node != "x-roomuser-item")
1575 {
1576 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
1577 return;
1578 }
1579
1580 StringBuilder Xml = new StringBuilder();
1581
1582 Xml.Append("<query xmlns='");
1583 Xml.Append(XmppServer.DiscoveryNamespace);
1584 Xml.Append("'>");
1585
1586 if (!(Room is null))
1587 {
1588 if (string.IsNullOrEmpty(Node))
1589 {
1590 if (!string.IsNullOrEmpty(Account))
1591 {
1592 Xml.Append("<identity category='conference' name='");
1593 Xml.Append(XML.Encode(Room.Name));
1594 Xml.Append("' type='text'/>");
1595 }
1596 }
1597 else
1598 {
1599 MucOccupant Occupant = await this.GetOccupantByBareJid(Room, e.From.BareJid);
1600 if (!(Occupant is null) && !string.IsNullOrEmpty(Occupant.NickName))
1601 {
1602 Xml.Append("<identity category='conference' name='");
1603 Xml.Append(XML.Encode(Occupant.NickName));
1604 Xml.Append("' type='text'/>");
1605 }
1606 }
1607 }
1608
1609 if (string.IsNullOrEmpty(Account) && string.IsNullOrEmpty(Node))
1610 {
1611 lock (this.synchObject)
1612 {
1613 foreach (string Feature in this.features.Keys)
1614 {
1615 Xml.Append("<feature var='");
1616 Xml.Append(XML.Encode(Feature));
1617 Xml.Append("'/>");
1618 }
1619 }
1620 }
1621
1622 if (!string.IsNullOrEmpty(Account) && string.IsNullOrEmpty(Node) && !(Room is null))
1623 {
1624 Xml.Append("<feature var='http://jabber.org/protocol/muc'/>");
1625 Xml.Append("<feature var='");
1626 Xml.Append(FormTypeRegister);
1627 Xml.Append("'/>");
1628 Xml.Append("<feature var='");
1629 Xml.Append(FormTypeRoomConfig);
1630 Xml.Append("'/>");
1631 Xml.Append("<feature var='http://jabber.org/protocol/muc#stable_id'/>");
1632
1633 if (Room.MembersOnly)
1634 Xml.Append("<feature var='muc_membersonly'/>");
1635
1636 if (Room.Moderated)
1637 Xml.Append("<feature var='muc_moderated'/>");
1638 else
1639 Xml.Append("<feature var='muc_unmoderated'/>");
1640
1641 switch (Room.DiscoverRealJids)
1642 {
1643 case DiscoverReadJids.Anyone:
1644 Xml.Append("<feature var='muc_nonanonymous'/>");
1645 break;
1646
1647 case DiscoverReadJids.Moderators:
1648 Xml.Append("<feature var='muc_semianonymous'/>");
1649 break;
1650 }
1651
1652 if (!Room.MembersOnly)
1653 Xml.Append("<feature var='muc_open'/>");
1654
1655 if (Room.PasswordProtected)
1656 Xml.Append("<feature var='muc_passwordprotected'/>");
1657 else
1658 Xml.Append("<feature var='muc_unsecured'/>");
1659
1660 if (Room.Persistent)
1661 Xml.Append("<feature var='muc_persistent'/>");
1662 else
1663 Xml.Append("<feature var='muc_temporary'/>");
1664
1665 if (Room.Locked)
1666 Xml.Append("<feature var='muc_hidden'/>");
1667 else
1668 Xml.Append("<feature var='muc_public'/>");
1669 }
1670
1671 Xml.Append("</query>");
1672
1673 await e.IqResult(Xml.ToString(), e.To);
1674 }
1675
1681 protected override async Task DiscoveryQueryItemsGet(object Sender, IqEventArgs e)
1682 {
1683 XmlElement E = e.Query;
1684 string Account = e.To.Account;
1685 MucRoom Room;
1686
1687 if (string.IsNullOrEmpty(Account))
1688 Room = null;
1689 else
1690 {
1691 Room = await this.GetRoom(Account, e.To.Domain);
1692 if (Room is null)
1693 {
1694 await e.IqErrorItemNotFound(e.To, "Item not found.", "en");
1695 return;
1696 }
1697
1698 if (Room.MembersOnly && !Room.IsMember(e.From.BareJid))
1699 {
1700 await e.IqErrorBadRequest(e.To, "You are not a member of the room.", "en");
1701 return;
1702 }
1703
1704 if (!string.IsNullOrEmpty(e.To.Resource))
1705 {
1706 if (!Room.TryGetOccupantFromFullJid(e.From.Address, out MucOccupant _))
1707 {
1708 await e.IqErrorBadRequest(e.To, "You are not in the room.", "en");
1709 return;
1710 }
1711
1712 MucOccupant Occupant = await this.GetOccupantByNickName(Room, e.To.Resource);
1713 if (Occupant is null)
1714 {
1715 await e.IqErrorItemNotFound(e.To, "Occupant not found.", "en");
1716 return;
1717 }
1718
1719 XmppAddress[] FullJids = Occupant.FullJids;
1720 if (FullJids is null || FullJids.Length == 0)
1721 {
1722 await e.IqErrorServiceUnavailable(e.To, "Occupant not connected.", "en");
1723 return;
1724 }
1725 else if (FullJids.Length > 1)
1726 {
1727 await e.IqErrorServiceUnavailable(e.To, "Occupant connected with multiple clients.", "en");
1728 return;
1729 }
1730
1731 await this.Server.SendIqRequest("get", e.To.ToBareJID(), FullJids[0], string.Empty, InnerXmlNoJabberNamespace(e.IQ), async (sender2, e2) =>
1732 {
1733 if (e2.Ok)
1734 await e.IqResult(InnerXmlNoJabberNamespace(e2.Response), e.To);
1735 else
1736 await e.IqError(e2.ErrorTypeString, e2.ErrorElement?.OuterXml ?? string.Empty, e.To, e2.ErrorText, string.Empty);
1737 }, null);
1738
1739 return;
1740 }
1741 }
1742
1743 string Node = XML.Attribute(E, "node");
1744 if (!string.IsNullOrEmpty(Node) && Node != "x-roomuser-item")
1745 {
1746 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
1747 return;
1748 }
1749
1750 StringBuilder Xml = new StringBuilder();
1751
1752 Xml.Append("<query xmlns='");
1754 Xml.Append("'>");
1755
1756 if (string.IsNullOrEmpty(Node))
1757 {
1758 if (string.IsNullOrEmpty(Account))
1759 {
1760 foreach (MucRoom Room2 in await Database.Find<MucRoom>())
1761 {
1762 if (!this.rooms.ContainsKey(Room2.Room))
1763 this.rooms[Room2.Room] = Room2;
1764
1765 if (Room2.Public)
1766 {
1767 Xml.Append("<item jid='");
1768 Xml.Append(XML.Encode(Room2.Room.Value));
1769 Xml.Append('@');
1770 Xml.Append(XML.Encode(Room2.Domain.Value));
1771 Xml.Append("' name='");
1772 Xml.Append(XML.Encode(Room2.Name));
1773 Xml.Append("'/>");
1774 }
1775 }
1776 }
1777 else
1778 {
1779 if (!(Room is null) &&
1780 Room.Public &&
1781 !Room.MembersOnly &&
1782 !Room.PasswordProtected)
1783 {
1784 IEnumerable<MucOccupant> Occupants;
1785
1786 if (Room.Persistent)
1787 {
1788 Occupants = await Database.Find<MucOccupant>(new FilterAnd(
1789 new FilterFieldEqualTo("Domain", e.To.Domain),
1790 new FilterFieldEqualTo("Room", e.To.Account)));
1791 }
1792 else
1793 Occupants = Room.GetOccupants();
1794
1795 foreach (MucOccupant Occupant in Occupants)
1796 {
1797 Xml.Append("<item jid='");
1798 Xml.Append(XML.Encode(Occupant.OccupantJid.Value));
1799 Xml.Append("'/>");
1800 }
1801 }
1802 }
1803 }
1804
1805 Xml.Append("</query>");
1806
1807 await e.IqResult(Xml.ToString(), e.To);
1808 }
1809
1810 private async Task FormSubmissionHandler(object Sender, MessageEventArgs e)
1811 {
1812 if (e.From.IsBareJID)
1813 {
1814 await e.MessageErrorJidMalformed(e.To, "From JID must be a full JID.", "en");
1815 return;
1816 }
1817
1818 if (!e.To.HasAccount)
1819 {
1820 await e.MessageErrorJidMalformed(e.To, "Missing room name.", "en");
1821 return;
1822 }
1823
1824 if (!e.To.IsBareJID)
1825 {
1826 await e.MessageErrorJidMalformed(e.To, "Message must be addressed to room.", "en");
1827 return;
1828 }
1829
1830 CaseInsensitiveString RoomId = e.To.Account;
1831 MucRoom Room = await this.GetRoom(RoomId, e.To.Domain);
1832
1833 if (Room is null)
1834 {
1835 await e.MessageErrorItemNotFound(e.To, "Room not found.", "en");
1836 return;
1837 }
1838
1839 string Type = XML.Attribute(e.Content, "type");
1840 if (Type == "cancel")
1841 return;
1842 else if (Type != "submit")
1843 {
1844 await e.MessageErrorBadRequest(e.To, "Expected form submission.", "en");
1845 return;
1846 }
1847
1848 MucOccupant Occupant = await this.GetOccupantByBareJid(Room, e.From.BareJid);
1849 if (Occupant is null)
1850 {
1851 await e.MessageErrorForbidden(e.To, "You are not an occupant of the room.", "en");
1852 return;
1853 }
1854
1855 string FormType = null;
1856
1857 foreach (XmlNode N2 in e.Content.ChildNodes)
1858 {
1859 if (N2 is XmlElement E2 &&
1860 E2.LocalName == "field" &&
1861 E2.NamespaceURI == XmppServer.DataFormsNamespace &&
1862 XML.Attribute(E2, "var") == "FORM_TYPE")
1863 {
1864 FormType = E2["value"]?.InnerText ?? string.Empty;
1865 break;
1866 }
1867 }
1868
1869 if (FormType == FormTypeRequest)
1870 {
1871 string Role = null;
1872 XmppAddress Jid = null;
1873 string NickName = null;
1874 bool? Allow = null;
1875
1876 foreach (XmlNode N2 in e.Content.ChildNodes)
1877 {
1878 if (N2 is XmlElement E2 && E2.LocalName == "field" && E2.NamespaceURI == XmppServer.DataFormsNamespace)
1879 {
1880 string Name = XML.Attribute(E2, "var");
1881 string Value = E2["value"]?.InnerText ?? string.Empty;
1882
1883 switch (Name)
1884 {
1885 case "FORM_TYPE":
1886 break;
1887
1888 case "muc#role":
1889 Role = Value;
1890 break;
1891
1892 case "muc#jid":
1893 Jid = new XmppAddress(Value);
1894 break;
1895
1896 case "muc#roomnick":
1897 NickName = Value;
1898 break;
1899
1900 case "muc#request_allow":
1901 if (CommonTypes.TryParse(Value, out bool b))
1902 Allow = b;
1903 else
1904 {
1905 await e.MessageErrorBadRequest(e.To, "Invalid boolean value.", "en");
1906 return;
1907 }
1908 break;
1909
1910 default:
1911 await e.MessageErrorBadRequest(e.To, "Unknown field: " + Name, "en");
1912 return;
1913 }
1914 }
1915 }
1916
1917 if (string.IsNullOrEmpty(NickName))
1918 {
1919 if (string.IsNullOrEmpty(Role))
1920 {
1921 await e.MessageErrorBadRequest(e.To, "Missing fields.", "en");
1922 return;
1923 }
1924
1925 if (Role != "moderator" && Role != "participant" && Role != "visitor" && Role != "none")
1926 {
1927 await e.MessageErrorBadRequest(e.To, "Invalid role.", "en");
1928 return;
1929 }
1930
1931 StringBuilder sb = new StringBuilder();
1932
1933 sb.Append("<x xmlns='");
1934 sb.Append(XmppServer.DataFormsNamespace);
1935 sb.Append("' type='form'><title>Privileges request</title>");
1936 sb.Append("<instructions>To approve this request for privileges, select the ");
1937 sb.Append("&quot;Grant privileges to this person?&quot; checkbox and click OK. ");
1938 sb.Append("To skip this request, click the cancel button.</instructions>");
1939 sb.Append("<field var='FORM_TYPE' type='hidden'><value>");
1940 sb.Append(FormTypeRequest);
1941 sb.Append("</value></field>");
1942 sb.Append("<field var='muc#role' type='list-single' label='Requested role'><value>");
1943 sb.Append(Role);
1944 sb.Append("</value>");
1945 sb.Append("<option label='None'><value>none</value></option>");
1946 sb.Append("<option label='Visitor'><value>visitor</value></option>");
1947 sb.Append("<option label='Participant'><value>participant</value></option>");
1948 sb.Append("<option label='Moderator'><value>moderator</value></option></field>");
1949 sb.Append("<field var='muc#jid' type='jid-single' label='JID of sender'><value>");
1950 sb.Append(XML.Encode(e.From.Address));
1951 sb.Append("</value></field>");
1952 sb.Append("<field var='muc#roomnick' type='text-single' label='Room Nickname'><value>");
1953 sb.Append(XML.Encode(Occupant.NickName));
1954 sb.Append("</value></field>");
1955 sb.Append("<field var='muc#request_allow' type='boolean' label='Grant privileges to this person?'><value>false</value></field></x>");
1956
1957 await this.SendToAdmin(Room, string.Empty, sb.ToString());
1958 }
1959 else
1960 {
1961 if (string.IsNullOrEmpty(Role) || string.IsNullOrEmpty(NickName) || Jid is null || !Allow.HasValue)
1962 {
1963 await e.MessageErrorBadRequest(e.To, "Missing fields.", "en");
1964 return;
1965 }
1966
1967 if (Allow.Value)
1968 {
1969 if (!Room.IsModerator(Occupant))
1970 {
1971 await e.MessageErrorForbidden(e.To, "Insufficient privileges.", "en");
1972 return;
1973 }
1974
1975 MucOccupant Occupant2 = await this.GetOccupantByNickName(Room, NickName);
1976 if (Occupant2 is null)
1977 {
1978 Occupant2 = new MucOccupant()
1979 {
1980 Room = RoomId,
1981 Domain = e.To.Domain,
1982 NickName = NickName,
1983 BareJid = Jid.BareJid,
1984 Affiliation = Room.GetDefaultAffiliation(Jid.BareJid),
1985 Persistent = Room.Persistent,
1986 Archive = Room.Archive
1987 };
1988
1989 Occupant2.Role = Occupant2.Affiliation.GetDefaultRole(Room);
1990
1991 if (!Room.Add(Occupant2))
1992 {
1993 await e.MessageErrorServiceUnavailable(e.To, "Room is full.", "en");
1994 return;
1995 }
1996
1997 if (Occupant2.Persistent)
1998 await Database.InsertLazy(Occupant2);
1999 }
2000 else
2001 {
2002 if (Occupant2.BareJid != Jid.BareJid)
2003 {
2004 await e.MessageErrorConflict(e.To, "Nick-name belongs to a different occupant.", "en");
2005 return;
2006 }
2007 }
2008
2009 switch (Role)
2010 {
2011 case "none":
2012 Occupant2.Role = new Roles.None();
2013 break;
2014
2015 case "visitor":
2016 Occupant2.Role = new Visitor();
2017 break;
2018
2019 case "participant":
2020 Occupant2.Role = new Participant();
2021 break;
2022
2023 case "moderator":
2024 Occupant2.Role = new Moderator();
2025 break;
2026
2027 default:
2028 await e.MessageErrorBadRequest(e.To, "Unrecognized role.", "en");
2029 return;
2030 }
2031
2032 if (Occupant2.Persistent)
2033 await Database.UpdateLazy(Occupant2);
2034
2035 XmppAddress OccupantJid = new XmppAddress(Occupant2.OccupantJid);
2036 StringBuilder sb = new StringBuilder();
2037 XmppAddress[] Jids0 = Occupant2.FullJids;
2038 XmppAddress[] Jids;
2039 string Xml;
2040
2041 if (Jids0 is null || Jids0.Length == 0)
2042 Jids0 = new XmppAddress[] { null };
2043
2044 foreach (MucOccupant Occupant3 in Room.GetOccupants())
2045 {
2046 if (Room.CanDiscoverRealJids(Occupant3))
2047 Jids = Jids0;
2048 else
2049 Jids = new XmppAddress[] { null };
2050
2051 foreach (XmppAddress Jid2 in Jids)
2052 {
2053 sb.Append("<x xmlns='");
2054 sb.Append(NamespaceMucUser);
2055 sb.Append("'><item affiliation='");
2056 sb.Append(Occupant2.Affiliation.Name);
2057
2058 if (!(Jid2 is null))
2059 {
2060 sb.Append("' jid='");
2061 sb.Append(XML.Encode(Jid2.Address));
2062 }
2063
2064 sb.Append("' role='");
2065 sb.Append(Occupant2.Role?.Name ?? "none");
2066 sb.Append("'><actor nick='");
2067 sb.Append(XML.Encode(NickName));
2068 sb.Append("'/>");
2069 sb.Append("</item>");
2070 sb.Append("</x>");
2071
2072 Xml = sb.ToString();
2073 sb.Clear();
2074
2075 if (!(Occupant3.FullJids is null))
2076 {
2077 foreach (XmppAddress FullJid in Occupant3.FullJids)
2078 await this.Server.Presence(string.Empty, Guid.NewGuid().ToString(), FullJid, OccupantJid, string.Empty, Xml, this);
2079 }
2080 }
2081 }
2082 }
2083 }
2084 }
2085 else if (FormType == FormTypeRegister)
2086 {
2087 string FirstName = null;
2088 string LastName = null;
2089 string NickName = null;
2090 string Url = string.Empty;
2091 string EMail = string.Empty;
2092 string FaqEntry = string.Empty;
2093 string FullJid = string.Empty;
2094 bool? Allow = null;
2095
2096 foreach (XmlNode N2 in e.Content.ChildNodes)
2097 {
2098 if (N2 is XmlElement E2 && E2.LocalName == "field" && E2.NamespaceURI == XmppServer.DataFormsNamespace)
2099 {
2100 string Name = XML.Attribute(E2, "var");
2101
2102 switch (Name)
2103 {
2104 case "FORM_TYPE":
2105 break;
2106
2107 case "muc#occupant_jid":
2108 FullJid = GetValue(E2);
2109 break;
2110
2111 case "muc#register_first":
2112 FirstName = GetValue(E2);
2113 break;
2114
2115 case "muc#register_last":
2116 LastName = GetValue(E2);
2117 break;
2118
2119 case "muc#register_roomnick":
2120 NickName = GetValue(E2);
2121 break;
2122
2123 case "muc#register_url":
2124 Url = GetValue(E2);
2125 break;
2126
2127 case "muc#register_email":
2128 EMail = GetValue(E2);
2129 break;
2130
2131 case "muc#register_faqentry":
2132 FaqEntry = GetValue(E2);
2133 break;
2134
2135 case "muc#register_allow":
2136 if (CommonTypes.TryParse(GetValue(E2), out bool b))
2137 Allow = b;
2138 else
2139 {
2140 await e.MessageErrorBadRequest(e.To, "Invalid boolean value.", "en");
2141 return;
2142 }
2143 break;
2144
2145 default:
2146 await e.MessageErrorBadRequest(e.To, "Unknown field: " + Name, "en");
2147 return;
2148 }
2149 }
2150 }
2151
2152 if (FormType is null ||
2153 string.IsNullOrEmpty(FirstName) ||
2154 string.IsNullOrEmpty(LastName) ||
2155 string.IsNullOrEmpty(NickName) ||
2156 string.IsNullOrEmpty(FullJid) ||
2157 !Allow.HasValue)
2158 {
2159 await e.MessageErrorBadRequest(e.To, "Missing fields.", "en");
2160 return;
2161 }
2162
2163 if (!Room.IsModerator(Occupant))
2164 {
2165 await e.MessageErrorForbidden(e.To, "Insufficient privileges.", "en");
2166 return;
2167 }
2168
2169 XmppAddress From = new XmppAddress(FullJid);
2170 MucOccupant Occupant2 = await this.GetOccupantByBareJid(Room, From.BareJid);
2171
2172 if (Allow.Value)
2173 {
2174 if (Occupant2 is null)
2175 {
2176 Occupant2 = new MucOccupant()
2177 {
2178 Room = RoomId,
2179 Domain = e.To.Domain,
2180 NickName = NickName,
2181 BareJid = From.BareJid,
2182 FullJids = new XmppAddress[] { From },
2183 Affiliation = Room.GetDefaultAffiliation(From.BareJid),
2184 Persistent = Room.Persistent,
2185 Archive = Room.Archive,
2186 FirstName = FirstName,
2187 LastName = LastName,
2188 Url = Url,
2189 EMail = EMail,
2190 FaqEntry = FaqEntry
2191 };
2192
2193 Occupant2.Role = Occupant2.Affiliation.GetDefaultRole(Room) ?? new Visitor();
2194
2195 if (!Room.Add(Occupant2))
2196 {
2197 await e.MessageErrorServiceUnavailable(e.To, "Room is full.", "en");
2198 return;
2199 }
2200
2201 if (Occupant2.Persistent)
2202 await Database.InsertLazy(Occupant2);
2203 }
2204 else
2205 {
2206 Occupant2.NickName = NickName;
2207 Occupant2.FullJids = new XmppAddress[] { From };
2208 Occupant2.Persistent = Room.Persistent;
2209 Occupant2.Archive = Room.Archive;
2210 Occupant2.FirstName = FirstName;
2211 Occupant2.LastName = LastName;
2212 Occupant2.Url = Url;
2213 Occupant2.EMail = EMail;
2214 Occupant2.FaqEntry = FaqEntry;
2215
2216 if (Occupant2.Role is null)
2217 Occupant2.Role = new Visitor();
2218
2219 if (Occupant2.Persistent)
2220 await Database.UpdateLazy(Occupant2);
2221 }
2222
2223 await this.OccupantEntered(Room, Occupant2, From, false, string.Empty, null);
2224 }
2225 else
2226 {
2227 Room.Remove(Occupant2);
2228
2229 if (Occupant2.Persistent)
2230 await Database.DeleteLazy(Occupant2);
2231 }
2232 }
2233 else
2234 {
2235 await e.MessageErrorBadRequest(e.To, "Unexpected FORM_TYPE.", "en");
2236 return;
2237 }
2238 }
2239
2240 private async Task AdminCommandHandler(object Sender, IqEventArgs e)
2241 {
2242 if (e.From.IsBareJID)
2243 {
2244 await e.IqErrorJidMalformed(e.To, "From JID must be a full JID.", "en");
2245 return;
2246 }
2247
2248 if (!e.To.HasAccount)
2249 {
2250 await e.IqErrorJidMalformed(e.To, "Missing room name.", "en");
2251 return;
2252 }
2253
2254 if (!e.To.IsBareJID)
2255 {
2256 await e.IqErrorJidMalformed(e.To, "Message must be addressed to room.", "en");
2257 return;
2258 }
2259
2260 CaseInsensitiveString RoomId = e.To.Account;
2261 MucRoom Room = await this.GetRoom(RoomId, e.To.Domain);
2262
2263 if (Room is null)
2264 {
2265 await e.IqErrorItemNotFound(e.To, "Room not found.", "en");
2266 return;
2267 }
2268
2269 if (!Room.TryGetOccupantFromFullJid(e.From.Address, out MucOccupant Occupant))
2270 {
2271 await e.IqErrorForbidden(e.To, "Not an occupant of room.", "en");
2272 return;
2273 }
2274
2275 foreach (XmlNode N in e.Query.ChildNodes)
2276 {
2277 if (!(N is XmlElement E) || E.NamespaceURI != NamespaceMucAdmin)
2278 continue;
2279
2280 switch (E.LocalName)
2281 {
2282 case "item":
2283 string NickName = XML.Attribute(E, "nick");
2284 string Jid = XML.Attribute(E, "jid");
2285 string Role = XML.Attribute(E, "role");
2286 string Affiliation = XML.Attribute(E, "affiliation");
2287
2288 if (!Room.IsModerator(Occupant))
2289 {
2290 await e.IqErrorForbidden(e.To, "Insufficient privileges.", "en");
2291 return;
2292 }
2293
2294 MucOccupant Occupant2;
2295
2296 if (string.IsNullOrEmpty(Affiliation))
2297 Occupant2 = await this.GetOccupantByNickName(Room, NickName);
2298 else
2299 Occupant2 = await this.GetOccupantByBareJid(Room, Jid);
2300
2301 if (Occupant2 is null)
2302 {
2303 if (string.IsNullOrEmpty(Affiliation))
2304 await e.IqErrorItemNotFound(e.To, "Occupant with specified nick-name not found in room.", "en");
2305 else
2306 await e.IqErrorItemNotFound(e.To, "Occupant with specified jid not found in room.", "en");
2307
2308 return;
2309 }
2310
2311 if ((Room.IsAdmin(Occupant2) && !Room.IsAdmin(Occupant)) ||
2312 (Room.IsOwner(Occupant2) && !Room.IsOwner(Occupant)))
2313 {
2314 await e.IqErrorNotAllowed(e.To, "Higher affiliation required.", "en");
2315 return;
2316 }
2317
2318 bool Kicked = false;
2319 bool Outcast = false;
2320 bool Removed = false;
2321 bool RoomUpdated = false;
2322 string Type = string.Empty;
2323
2324 Occupant2.Reason = string.Empty;
2325
2326 if (!string.IsNullOrEmpty(Role))
2327 {
2328 switch (Role)
2329 {
2330 case "none":
2331 if (Occupant.BareJid == Occupant2.BareJid)
2332 {
2333 await e.IqErrorNotAllowed(e.To, "You cannot kick yourself.", "en");
2334 return;
2335 }
2336
2337 Occupant2.Role = new Roles.None();
2338 Type = "unavailable";
2339 Kicked = true;
2340 break;
2341
2342 case "visitor":
2343 Occupant2.Role = new Visitor();
2344 break;
2345
2346 case "participant":
2347 Occupant2.Role = new Participant();
2348 break;
2349
2350 case "moderator":
2351 Occupant2.Role = new Moderator();
2352 break;
2353
2354 default:
2355 await e.IqErrorBadRequest(e.To, "Unrecognized role.", "en");
2356 return;
2357 }
2358 }
2359
2360 if (!string.IsNullOrEmpty(Affiliation))
2361 {
2362 int NrOwnersBefore = Room.Owners.Length;
2363
2364 if (Room.IsOwner(Occupant2))
2365 {
2366 if (Occupant.BareJid == Occupant2.BareJid)
2367 {
2368 await e.IqErrorNotAllowed(e.To, "You cannot disown yourself.", "en");
2369 return;
2370 }
2371
2372 Room.RemoveOwner(Occupant2.BareJid);
2373 RoomUpdated = true;
2374 }
2375
2376 if (Room.IsAdmin(Occupant2))
2377 {
2378 Room.RemoveAdmin(Occupant2.BareJid);
2379 RoomUpdated = true;
2380 }
2381
2382 switch (Affiliation)
2383 {
2384 case "none":
2385 if (Occupant.BareJid == Occupant2.BareJid)
2386 {
2387 await e.IqErrorNotAllowed(e.To, "You cannot kick yourself.", "en");
2388 return;
2389 }
2390
2391 Occupant2.Affiliation = new Affiliations.None();
2392
2393 if (Room.MembersOnly)
2394 {
2395 Removed = true;
2396 Room.Remove(Occupant);
2397 Type = "unavailable";
2398 }
2399 break;
2400
2401 case "member":
2402 Occupant2.Affiliation = new Member();
2403 break;
2404
2405 case "admin":
2406 Occupant2.Affiliation = new Affiliations.Admin();
2407 Room.AddAdmin(Occupant2.BareJid);
2408 RoomUpdated = true;
2409 break;
2410
2411 case "owner":
2412 Occupant2.Affiliation = new Owner();
2413 Room.AddOwner(Occupant2.BareJid);
2414 RoomUpdated = true;
2415 break;
2416
2417 case "outcast":
2418 Room.Remove(Occupant);
2419 Occupant2.Affiliation = new Outcast();
2420 Outcast = true;
2421 Type = "unavailable";
2422 break;
2423
2424 default:
2425 await e.IqErrorBadRequest(e.To, "Unrecognized affiliation.", "en");
2426 return;
2427 }
2428
2429 int NrOwnersAfter = Room.Owners.Length;
2430
2431 if (NrOwnersAfter == 0 && NrOwnersBefore > 0)
2432 {
2433 if (Room.Persistent)
2434 this.rooms.Remove(RoomId);
2435
2436 Room.AddOwner(Occupant2.BareJid);
2437 await e.IqErrorNotAcceptable(e.To, "Room must have at least one owner.", "en");
2438 return;
2439 }
2440 }
2441
2442
2443 foreach (XmlNode N2 in E.ChildNodes)
2444 {
2445 if (N2 is XmlElement E2 && E2.LocalName == "reason" && E2.NamespaceURI == NamespaceMucAdmin)
2446 {
2447 Occupant2.Reason = E2.InnerText;
2448 break;
2449 }
2450 }
2451
2452 if (Occupant2.Persistent)
2453 await Database.UpdateLazy(Occupant2);
2454
2455 if (RoomUpdated && Room.Persistent)
2456 await Database.UpdateLazy(Room);
2457
2458 StringBuilder Xml = new StringBuilder();
2459 string ToSelf;
2460 string ToOthers;
2461
2462 Xml.Append("<x xmlns='");
2463 Xml.Append(NamespaceMucUser);
2464 Xml.Append("'><item affiliation='");
2465 Xml.Append(Occupant2.Affiliation.Name);
2466 Xml.Append("' role='");
2467 Xml.Append(Occupant2.Role?.Name ?? "none");
2468 Xml.Append("'><actor nick='");
2469 Xml.Append(XML.Encode(Occupant2.NickName));
2470 Xml.Append("'/>");
2471
2472 if (!string.IsNullOrEmpty(Occupant2.Reason))
2473 {
2474 Xml.Append("<reason>");
2475 Xml.Append(XML.Encode(Occupant2.Reason));
2476 Xml.Append("</reason>");
2477 }
2478
2479 Xml.Append("</item>");
2480
2481 if (Outcast)
2482 Xml.Append("<status code='301'/>");
2483
2484 if (Kicked)
2485 Xml.Append("<status code='307'/>");
2486
2487 if (Removed)
2488 Xml.Append("<status code='321'/>");
2489
2490 StringBuilder Xml2 = new StringBuilder();
2491 Xml2.Append(Xml.ToString());
2492 Xml2.Append("</x>");
2493 Xml.Append("</x>");
2494
2495 ToSelf = Xml2.ToString();
2496 ToOthers = Xml.ToString();
2497
2498 XmppAddress OccupantJid = new XmppAddress(Occupant2.OccupantJid);
2499
2500 foreach (MucOccupant Occupant3 in Room.GetOccupants())
2501 {
2502 if (!(Occupant3.FullJids is null))
2503 {
2504 foreach (XmppAddress FullJid in Occupant3.FullJids)
2505 {
2506 await this.Server.Presence(Type, Guid.NewGuid().ToString(), FullJid, OccupantJid, string.Empty,
2507 FullJid == e.From ? ToSelf : ToOthers, this);
2508 }
2509 }
2510 }
2511
2512 if (Kicked)
2513 Room.Remove(Occupant2);
2514 break;
2515 }
2516 }
2517
2518 await e.IqResult(string.Empty, e.To);
2519 }
2520
2521 private async Task AdminQueryHandler(object Sender, IqEventArgs e)
2522 {
2523 if (e.From.IsBareJID)
2524 {
2525 await e.IqErrorJidMalformed(e.To, "From JID must be a full JID.", "en");
2526 return;
2527 }
2528
2529 if (!e.To.HasAccount)
2530 {
2531 await e.IqErrorJidMalformed(e.To, "Missing room name.", "en");
2532 return;
2533 }
2534
2535 if (!e.To.IsBareJID)
2536 {
2537 await e.IqErrorJidMalformed(e.To, "Message must be addressed to room.", "en");
2538 return;
2539 }
2540
2541 CaseInsensitiveString RoomId = e.To.Account;
2542 MucRoom Room = await this.GetRoom(RoomId, e.To.Domain);
2543
2544 if (Room is null)
2545 {
2546 await e.IqErrorItemNotFound(e.To, "Room not found.", "en");
2547 return;
2548 }
2549
2550 if (!Room.TryGetOccupantFromFullJid(e.From.Address, out MucOccupant Occupant))
2551 {
2552 await e.IqErrorForbidden(e.To, "Not an occupant of room.", "en");
2553 return;
2554 }
2555
2556 foreach (XmlNode N in e.Query.ChildNodes)
2557 {
2558 if (!(N is XmlElement E) || E.NamespaceURI != NamespaceMucAdmin)
2559 continue;
2560
2561 switch (E.LocalName)
2562 {
2563 case "item":
2564 string Role = XML.Attribute(E, "role");
2565 string Affiliation = XML.Attribute(E, "affiliation");
2566
2567 if (!Room.IsModerator(Occupant))
2568 {
2569 await e.IqErrorForbidden(e.To, "Insufficient privileges.", "en");
2570 return;
2571 }
2572
2573 IEnumerable<MucOccupant> Occupants;
2574
2575 if (!string.IsNullOrEmpty(Role))
2576 {
2577 if (Room.Persistent)
2578 {
2579 Occupants = await Database.Find<MucOccupant>(new FilterAnd(
2580 new FilterFieldEqualTo("Domain", e.To.Domain),
2581 new FilterFieldEqualTo("Room", e.To.Account),
2582 new FilterCustom<MucOccupant>(o => (o.Role?.Name ?? "none") == Role)));
2583 }
2584 else
2585 {
2586 Occupants = from Occupant2 in Room.GetOccupants()
2587 where (Occupant2.Role?.Name ?? "none") == Role
2588 select Occupant2;
2589 }
2590 }
2591 else if (!string.IsNullOrEmpty(Affiliation))
2592 {
2593 if (Room.Persistent)
2594 {
2595 Occupants = await Database.Find<MucOccupant>(new FilterAnd(
2596 new FilterFieldEqualTo("Domain", e.To.Domain),
2597 new FilterFieldEqualTo("Room", e.To.Account),
2598 new FilterCustom<MucOccupant>(o => (o.Affiliation?.Name ?? "none") == Affiliation)));
2599 }
2600 else
2601 {
2602 Occupants = from Occupant2 in Room.GetOccupants()
2603 where (Occupant2.Affiliation?.Name ?? "none") == Role
2604 select Occupant2;
2605 }
2606 }
2607 else
2608 Occupants = new MucOccupant[0];
2609
2610 StringBuilder Xml = new StringBuilder();
2611 XmppAddress[] Jids;
2612
2613 Xml.Append("<query xmlns='");
2614 Xml.Append(NamespaceMucAdmin);
2615 Xml.Append("'>");
2616
2617 foreach (MucOccupant Occupant2 in Occupants)
2618 {
2619 if (Room.DiscoverRealJids == DiscoverReadJids.None ||
2620 !Room.TryGetOccupantFromNick(Occupant2.NickName, out MucOccupant Occupant3))
2621 {
2622 Jids = new XmppAddress[] { null };
2623 }
2624 else
2625 {
2626 Jids = Occupant3.FullJids;
2627 if (Jids is null || Jids.Length == 0)
2628 Jids = new XmppAddress[] { null };
2629 }
2630
2631 foreach (XmppAddress Jid in Jids)
2632 {
2633 Xml.Append("<item affiliation='");
2634 Xml.Append(Occupant2.Affiliation?.Name ?? "none");
2635
2636 if (Occupant2.Affiliation is Outcast)
2637 {
2638 Xml.Append("' jid='");
2639 if (Jid is null)
2640 Xml.Append(XML.Encode(Occupant2.BareJid));
2641 else
2642 Xml.Append(XML.Encode(Jid.Address.Value));
2643 }
2644 else
2645 {
2646 if (!(Jid is null))
2647 {
2648 Xml.Append("' jid='");
2649 Xml.Append(XML.Encode(Jid.Address.Value));
2650 }
2651
2652 Xml.Append("' nick='");
2653 Xml.Append(XML.Encode(Occupant2.NickName));
2654 Xml.Append("' role='");
2655 Xml.Append(Occupant2.Role?.Name ?? "none");
2656 }
2657
2658 if (string.IsNullOrEmpty(Occupant2.Reason))
2659 Xml.Append("'/>");
2660 else
2661 {
2662 Xml.Append("'><reason>");
2663 Xml.Append(XML.Encode(Occupant2.Reason));
2664 Xml.Append("</reason></item>");
2665 }
2666 }
2667 }
2668
2669 Xml.Append("</query>");
2670
2671 await e.IqResult(Xml.ToString(), e.To);
2672 break;
2673 }
2674 }
2675 }
2676
2677 private async Task OwnerQueryHandler(object Sender, IqEventArgs e)
2678 {
2679 if (!e.To.HasAccount)
2680 {
2681 await e.IqErrorJidMalformed(e.To, "Missing room name.", "en");
2682 return;
2683 }
2684
2685 if (!e.To.IsBareJID)
2686 {
2687 await e.IqErrorJidMalformed(e.To, "Message must be addressed to room.", "en");
2688 return;
2689 }
2690
2691 CaseInsensitiveString RoomId = e.To.Account;
2692 MucRoom Room = await this.GetRoom(RoomId, e.To.Domain);
2693
2694 if (Room is null)
2695 {
2696 await e.IqErrorItemNotFound(e.To, "Room not found.", "en");
2697 return;
2698 }
2699
2700 if (!Room.IsOwner(e.From.BareJid))
2701 {
2702 await e.IqErrorForbidden(e.To, "You're not an owner of this room.", "en");
2703 return;
2704 }
2705
2706 StringBuilder Xml = new StringBuilder();
2707
2708 Xml.Append("<query xmlns='");
2709 Xml.Append(NamespaceMucOwner);
2710 Xml.Append("'><x xmlns='jabber:x:data' type='form'>");
2711 Xml.Append("<title>Configuration for ");
2712 Xml.Append(XML.Encode(RoomId));
2713 Xml.Append(" Room</title>");
2714 Xml.Append("<instructions>Enter room configuration below, then click OK.</instructions>");
2715 Xml.Append("<field type='hidden' var='FORM_TYPE'><value>");
2716 Xml.Append(FormTypeRoomConfig);
2717 Xml.Append("</value></field>");
2718 Xml.Append("<field label='Natural-Language Room Name' type='text-single' var='muc#roomconfig_roomname'><value>");
2719 Xml.Append(XML.Encode(Room.Name));
2720 Xml.Append("</value></field>");
2721 Xml.Append("<field label='Short Description of Room' type='text-single' var='muc#roomconfig_roomdesc'><value>");
2722 Xml.Append(XML.Encode(Room.Description));
2723 Xml.Append("</value></field>");
2724 Xml.Append("<field label='Natural Language for Room Discussions' type='text-single' var='muc#roomconfig_lang'><value>");
2725 Xml.Append(XML.Encode(Room.Language));
2726 Xml.Append("</value></field>");
2727 Xml.Append("<field label='Enable Public Logging?' type='boolean' var='muc#roomconfig_enablelogging'><value>");
2728 Xml.Append(Room.Logging ? "1" : "0");
2729 Xml.Append("</value></field>");
2730 Xml.Append("<field label='Allow Occupants to Change Subject?' type='boolean' var='muc#roomconfig_changesubject'><value>");
2731 Xml.Append(Room.AllowChangeSubject ? "1" : "0");
2732 Xml.Append("</value></field>");
2733 Xml.Append("<field label='Allow Occupants to Invite Others?' type='boolean' var='muc#roomconfig_allowinvites'><value>");
2734 Xml.Append(Room.AllowInvites ? "1" : "0");
2735 Xml.Append("</value></field>");
2736 Xml.Append("<field label='Who Can Send Private Messages?' type='list-single' var='muc#roomconfig_allowpm'><value>");
2737 Xml.Append(Room.AllowPrivateMessages.ToString().ToLower());
2738 Xml.Append("</value>");
2739 Xml.Append("<option label='Anyone'><value>anyone</value></option>");
2740 Xml.Append("<option label='Anyone with Voice'><value>participants</value></option>");
2741 Xml.Append("<option label='Moderators Only'><value>moderators</value></option>");
2742 Xml.Append("<option label='Nobody'><value>none</value></option></field>");
2743 Xml.Append("<field label='Maximum Number of Occupants' type='list-single' var='muc#roomconfig_maxusers'><value>");
2744 Xml.Append(Room.MaxHistoryFetch == int.MaxValue ? "none" : Room.MaxHistoryFetch.ToString());
2745 Xml.Append("</value>");
2746 Xml.Append("<option label='10'><value>10</value></option>");
2747 Xml.Append("<option label='20'><value>20</value></option>");
2748 Xml.Append("<option label='30'><value>30</value></option>");
2749 Xml.Append("<option label='50'><value>50</value></option>");
2750 Xml.Append("<option label='100'><value>100</value></option>");
2751 Xml.Append("<option label='None'><value>none</value></option></field>");
2752 Xml.Append("<field label='Roles for which Presence is Broadcasted' type='list-multi' var='muc#roomconfig_presencebroadcast'>");
2753 if (Room.PresenceBroadcast.HasFlag(BroadcastPresence.Moderator))
2754 Xml.Append("<value>moderator</value>");
2755 if (Room.PresenceBroadcast.HasFlag(BroadcastPresence.Participant))
2756 Xml.Append("<value>participant</value>");
2757 if (Room.PresenceBroadcast.HasFlag(BroadcastPresence.Visitor))
2758 Xml.Append("<value>visitor</value>");
2759 Xml.Append("<option label='Moderator'><value>moderator</value></option>");
2760 Xml.Append("<option label='Participant'><value>participant</value></option>");
2761 Xml.Append("<option label='Visitor'><value>visitor</value></option></field>");
2762 Xml.Append("<field label='Roles and Affiliations that May Retrieve Member List' type='list-multi' var='muc#roomconfig_getmemberlist'>");
2763 if (Room.GetMemberList.HasFlag(RetrieveMembershipList.Moderator))
2764 Xml.Append("<value>moderator</value>");
2765 if (Room.GetMemberList.HasFlag(RetrieveMembershipList.Participant))
2766 Xml.Append("<value>participant</value>");
2767 if (Room.GetMemberList.HasFlag(RetrieveMembershipList.Visitor))
2768 Xml.Append("<value>visitor</value>");
2769 Xml.Append("<option label='Moderator'><value>moderator</value></option>");
2770 Xml.Append("<option label='Participant'><value>participant</value></option>");
2771 Xml.Append("<option label='Visitor'><value>visitor</value></option></field>");
2772 Xml.Append("<field label='Make Room Publicly Searchable?' type='boolean' var='muc#roomconfig_publicroom'><value>");
2773 Xml.Append(Room.Public ? "1" : "0");
2774 Xml.Append("</value></field>");
2775 Xml.Append("<field label='Make Room Persistent?' type='boolean' var='muc#roomconfig_persistentroom'><value>");
2776 Xml.Append(Room.Persistent ? "1" : "0");
2777 Xml.Append("</value></field>");
2778 Xml.Append("<field label='Make Room Moderated?' type='boolean' var='muc#roomconfig_moderatedroom'><value>");
2779 Xml.Append(Room.Moderated ? "1" : "0");
2780 Xml.Append("</value></field>");
2781 Xml.Append("<field label='Make Room Members-Only?' type='boolean' var='muc#roomconfig_membersonly'><value>");
2782 Xml.Append(Room.MembersOnly ? "1" : "0");
2783 Xml.Append("</value></field>");
2784 Xml.Append("<field label='Password Required to Enter?' type='boolean' var='muc#roomconfig_passwordprotectedroom'><value>");
2785 Xml.Append(Room.PasswordProtected ? "1" : "0");
2786 Xml.Append("</value></field>");
2787 Xml.Append("<field type='fixed'><value>If a password is required to enter this room, you must specify the password below.</value></field>");
2788 Xml.Append("<field label='Password' type='text-private' var='muc#roomconfig_roomsecret'><value>");
2789 Xml.Append(XML.Encode(Room.Password));
2790 Xml.Append("</value></field>");
2791 Xml.Append("<field label='Who May Discover Real JIDs?' type='list-single' var='muc#roomconfig_whois'><value>");
2792 Xml.Append(Room.DiscoverRealJids.ToString().ToLower());
2793 Xml.Append("</value>");
2794 Xml.Append("<option label='No-one'><value>none</value></option>");
2795 Xml.Append("<option label='Moderators Only'><value>moderators</value></option>");
2796 Xml.Append("<option label='Anyone'><value>anyone</value></option></field>");
2797 Xml.Append("<field label='Maximum Number of History Messages Returned by Room' type='text-single' var='muc#maxhistoryfetch'><value>");
2798 Xml.Append(Room.MaxHistoryFetch.ToString());
2799 Xml.Append("</value></field>");
2800 Xml.Append("<field type='fixed'><value>You may specify additional people who have admin status in the room. Please provide one Jabber ID per line.</value></field>");
2801 Xml.Append("<field label='Room Admins' type='jid-multi' var='muc#roomconfig_roomadmins'>");
2802 if (!(Room.Admins is null))
2803 {
2804 foreach (CaseInsensitiveString Jid in Room.Admins)
2805 {
2806 Xml.Append("<value>");
2807 Xml.Append(XML.Encode(Jid.Value));
2808 Xml.Append("</value>");
2809 }
2810 }
2811 Xml.Append("</field>");
2812 Xml.Append("<field type='fixed'><value>You may specify additional owners for this room. Please provide one Jabber ID per line.</value></field>");
2813 Xml.Append("<field label='Room Owners' type='jid-multi' var='muc#roomconfig_roomowners'>");
2814 if (!(Room.Owners is null))
2815 {
2816 foreach (CaseInsensitiveString Jid in Room.Owners)
2817 {
2818 Xml.Append("<value>");
2819 Xml.Append(XML.Encode(Jid.Value));
2820 Xml.Append("</value>");
2821 }
2822 }
2823 Xml.Append("</field></x></query>");
2824
2825 await e.IqResult(Xml.ToString(), e.To);
2826 }
2827
2828 private async Task OwnerCommandHandler(object Sender, IqEventArgs e)
2829 {
2830 if (!e.To.HasAccount)
2831 {
2832 await e.IqErrorJidMalformed(e.To, "Missing room name.", "en");
2833 return;
2834 }
2835
2836 if (!e.To.IsBareJID)
2837 {
2838 await e.IqErrorJidMalformed(e.To, "Message must be addressed to room.", "en");
2839 return;
2840 }
2841
2842 CaseInsensitiveString RoomId = e.To.Account;
2843 MucRoom Room = await this.GetRoom(RoomId, e.To.Domain);
2844
2845 if (Room is null)
2846 {
2847 await e.IqErrorItemNotFound(e.To, "Room not found.", "en");
2848 return;
2849 }
2850
2851 if (!Room.IsOwner(e.From.BareJid))
2852 {
2853 await e.IqErrorForbidden(e.To, "You're not an owner of this room.", "en");
2854 return;
2855 }
2856
2857 foreach (XmlNode N in e.Query.ChildNodes)
2858 {
2859 if (!(N is XmlElement E))
2860 continue;
2861
2862 if (E.LocalName == "destroy" && E.NamespaceURI == NamespaceMucOwner)
2863 {
2864 string AlternativeRoomJid = XML.Attribute(E, "jid");
2865 string Reason = null;
2866 string Password = null;
2867
2868 foreach (XmlNode N2 in E.ChildNodes)
2869 {
2870 if (!(N2 is XmlElement E2))
2871 continue;
2872
2873 switch (E2.LocalName)
2874 {
2875 case "reason":
2876 Reason = E2.InnerText;
2877 break;
2878
2879 case "password":
2880 Password = E2.InnerText;
2881 break;
2882 }
2883 }
2884
2885 if (Room.PasswordProtected && (Password is null || Room.Password != Password))
2886 {
2887 await e.IqErrorNotAuthorized(e.To, "Room ID or password incorrect.", "en");
2888 return;
2889 }
2890
2891 StringBuilder sb = new StringBuilder();
2892
2893 sb.Append("<x xmlns='");
2894 sb.Append(NamespaceMucUser);
2895 sb.Append("'><item affiliation='none' role='none'/><destroy");
2896
2897 if (!string.IsNullOrEmpty(AlternativeRoomJid))
2898 {
2899 sb.Append(" jid='");
2900 sb.Append(XML.Encode(AlternativeRoomJid));
2901 sb.Append("'");
2902 }
2903
2904 sb.Append('>');
2905
2906 if (!string.IsNullOrEmpty(Reason))
2907 {
2908 sb.Append("<reason>");
2909 sb.Append(XML.Encode(Reason));
2910 sb.Append("</reason>");
2911 }
2912
2913 sb.Append("</destroy></x>");
2914
2915 string Xml = sb.ToString();
2916
2917 await Database.StartBulk();
2918 try
2919 {
2920 foreach (MucOccupant Occupant2 in Room.GetOccupants())
2921 {
2922 foreach (XmppAddress Jid in Occupant2.FullJids)
2923 {
2924 await this.Server.Presence("unavailable", Guid.NewGuid().ToString(), Jid,
2925 new XmppAddress(Occupant2.OccupantJid), string.Empty, Xml, this);
2926
2927 if (Occupant2.Persistent)
2928 await Database.DeleteLazy(Occupant2);
2929
2930 Room.Remove(Occupant2);
2931 }
2932 }
2933
2934 if (Room.Persistent)
2935 await Database.DeleteLazy(Room);
2936 }
2937 finally
2938 {
2939 await Database.EndBulk();
2940 }
2941
2942 if (Room.Persistent && Room.Logging)
2943 {
2945 new FilterFieldEqualTo("Domain", e.To.Domain),
2946 new FilterFieldEqualTo("Room", RoomId)));
2947 }
2948
2949 Log.Informational("Room destroyed", RoomId, e.From.BareJid,
2950 new KeyValuePair<string, object>("Room", RoomId),
2951 new KeyValuePair<string, object>("Domain", e.To.Domain),
2952 new KeyValuePair<string, object>("Destroyer", e.From.BareJid));
2953
2954 await e.IqResult(string.Empty, e.To);
2955 }
2956 else if (E.LocalName == "x" && E.NamespaceURI == XmppServer.DataFormsNamespace)
2957 {
2958 string Type = XML.Attribute(E, "type");
2959 if (Type == "submit")
2960 {
2961 CanSendPrivateMessages? PrivateMessages = null;
2962 DiscoverReadJids? DiscoverJids = null;
2963 BroadcastPresence? PresenceBroadcast = null;
2964 RetrieveMembershipList? GetMemberList = null;
2965 string FormType = null;
2966 string RoomName = null;
2967 string RoomDescription = null;
2968 string RoomLanguage = null;
2969 string Password = null;
2970 CaseInsensitiveString[] Administrators = null;
2971 CaseInsensitiveString[] Owners = null;
2972 int? MaxUsers = null;
2973 int? MaxHistory = null;
2974 bool? Logging = null;
2975 bool? ChangeSubject = null;
2976 bool? AllowInvites = null;
2977 bool? Public = null;
2978 bool? Persist = null;
2979 bool? Moderated = null;
2980 bool? MembersOnly = null;
2981 bool? PasswordProtected = null;
2982 bool InvalidBoolean = false;
2983 bool InvalidInt = false;
2984 bool InvalidEnum = false;
2985
2986 foreach (XmlNode N2 in E.ChildNodes)
2987 {
2988 if (N2 is XmlElement E2 && E2.LocalName == "field" && E2.NamespaceURI == XmppServer.DataFormsNamespace)
2989 {
2990 string Name = XML.Attribute(E2, "var");
2991
2992 switch (Name)
2993 {
2994 case "FORM_TYPE":
2995 FormType = GetValue(E2);
2996 break;
2997
2998 case "muc#roomconfig_roomname":
2999 RoomName = GetValue(E2);
3000 break;
3001
3002 case "muc#roomconfig_roomdesc":
3003 RoomDescription = GetValue(E2);
3004 break;
3005
3006 case "muc#roomconfig_lang":
3007 RoomLanguage = GetValue(E2);
3008 break;
3009
3010 case "muc#roomconfig_enablelogging":
3011 if (CommonTypes.TryParse(GetValue(E2), out bool b))
3012 Logging = b;
3013 else
3014 InvalidBoolean = true;
3015 break;
3016
3017 case "muc#roomconfig_changesubject":
3018 if (CommonTypes.TryParse(GetValue(E2), out b))
3019 ChangeSubject = b;
3020 else
3021 InvalidBoolean = true;
3022 break;
3023
3024 case "muc#roomconfig_allowinvites":
3025 if (CommonTypes.TryParse(GetValue(E2), out b))
3026 AllowInvites = b;
3027 else
3028 InvalidBoolean = true;
3029 break;
3030
3031 case "muc#roomconfig_allowpm":
3032 switch (GetValue(E2))
3033 {
3034 case "anyone":
3035 PrivateMessages = CanSendPrivateMessages.Anyone;
3036 break;
3037
3038 case "participants":
3039 PrivateMessages = CanSendPrivateMessages.Participants;
3040 break;
3041
3042 case "moderators":
3043 PrivateMessages = CanSendPrivateMessages.Moderators;
3044 break;
3045
3046 case "none":
3047 PrivateMessages = CanSendPrivateMessages.None;
3048 break;
3049
3050 default:
3051 InvalidEnum = true;
3052 break;
3053 }
3054 break;
3055
3056 case "muc#roomconfig_maxusers":
3057 string s = GetValue(E2);
3058 if (int.TryParse(s, out int i))
3059 MaxUsers = i;
3060 else if (s == "none")
3061 MaxUsers = int.MaxValue;
3062 else
3063 InvalidInt = true;
3064 break;
3065
3066 case "muc#roomconfig_publicroom":
3067 if (CommonTypes.TryParse(GetValue(E2), out b))
3068 Public = b;
3069 else
3070 InvalidBoolean = true;
3071 break;
3072
3073 case "muc#roomconfig_persistentroom":
3074 if (CommonTypes.TryParse(GetValue(E2), out b))
3075 Persist = b;
3076 else
3077 InvalidBoolean = true;
3078 break;
3079
3080 case "muc#roomconfig_moderatedroom":
3081 if (CommonTypes.TryParse(GetValue(E2), out b))
3082 Moderated = b;
3083 else
3084 InvalidBoolean = true;
3085 break;
3086
3087 case "muc#roomconfig_membersonly":
3088 if (CommonTypes.TryParse(GetValue(E2), out b))
3089 MembersOnly = b;
3090 else
3091 InvalidBoolean = true;
3092 break;
3093
3094 case "muc#roomconfig_passwordprotectedroom":
3095 if (CommonTypes.TryParse(GetValue(E2), out b))
3096 PasswordProtected = b;
3097 else
3098 InvalidBoolean = true;
3099 break;
3100
3101 case "muc#roomconfig_roomsecret":
3102 Password = GetValue(E2);
3103 break;
3104
3105 case "muc#roomconfig_presencebroadcast":
3106 PresenceBroadcast = BroadcastPresence.None;
3107 foreach (CaseInsensitiveString Choice in GetValues(E2))
3108 {
3109 switch (Choice.LowerCase)
3110 {
3111 case "moderator":
3112 PresenceBroadcast |= BroadcastPresence.Moderator;
3113 break;
3114
3115 case "participant":
3116 PresenceBroadcast |= BroadcastPresence.Participant;
3117 break;
3118
3119 case "visitor":
3120 PresenceBroadcast |= BroadcastPresence.Visitor;
3121 break;
3122
3123 default:
3124 InvalidEnum = true;
3125 break;
3126 }
3127 }
3128 break;
3129
3130 case "muc#roomconfig_getmemberlist":
3131 GetMemberList = RetrieveMembershipList.None;
3132 foreach (CaseInsensitiveString Choice in GetValues(E2))
3133 {
3134 switch (Choice.LowerCase)
3135 {
3136 case "moderator":
3137 GetMemberList |= RetrieveMembershipList.Moderator;
3138 break;
3139
3140 case "participant":
3141 GetMemberList |= RetrieveMembershipList.Participant;
3142 break;
3143
3144 case "visitor":
3145 GetMemberList |= RetrieveMembershipList.Visitor;
3146 break;
3147
3148 default:
3149 InvalidEnum = true;
3150 break;
3151 }
3152 }
3153 break;
3154
3155 case "muc#roomconfig_whois":
3156 switch (GetValue(E2))
3157 {
3158 case "anyone":
3159 DiscoverJids = DiscoverReadJids.Anyone;
3160 break;
3161
3162 case "moderators":
3163 DiscoverJids = DiscoverReadJids.Moderators;
3164 break;
3165
3166 case "none":
3167 DiscoverJids = DiscoverReadJids.None;
3168 break;
3169
3170 default:
3171 InvalidEnum = true;
3172 break;
3173 }
3174 break;
3175
3176 case "muc#maxhistoryfetch":
3177 if (int.TryParse(GetValue(E2), out i))
3178 MaxHistory = i;
3179 else
3180 InvalidInt = true;
3181 break;
3182
3183 case "muc#roomconfig_roomadmins":
3184 Administrators = GetValues(E2);
3185 break;
3186
3187 case "muc#roomconfig_roomowners":
3188 Owners = GetValues(E2);
3189 if (Owners.Length == 0)
3190 {
3191 await e.IqErrorConflict(e.To, "Room must have owners.", "en");
3192 return;
3193 }
3194 break;
3195
3196 default:
3197 await e.IqErrorBadRequest(e.To, "Unknown field: " + Name, "en");
3198 return;
3199 }
3200 }
3201 }
3202
3203 if (InvalidBoolean)
3204 {
3205 await e.IqErrorBadRequest(e.To, "Invalid boolean value.", "en");
3206 return;
3207 }
3208
3209 if (InvalidInt)
3210 {
3211 await e.IqErrorBadRequest(e.To, "Invalid integer value.", "en");
3212 return;
3213 }
3214
3215 if (InvalidEnum)
3216 {
3217 await e.IqErrorBadRequest(e.To, "Invalid option.", "en");
3218 return;
3219 }
3220
3221 if (!string.IsNullOrEmpty(FormType) && FormType != FormTypeRoomConfig)
3222 {
3223 await e.IqErrorBadRequest(e.To, "Invalid or unexpected FORM_TYPE.", "en");
3224 return;
3225 }
3226
3227 CaseInsensitiveString[] OldAdmins = null;
3228 CaseInsensitiveString[] OldOwners = null;
3229 bool NameChanged = false;
3230 bool DescriptionChanged = false;
3231 bool LanguageChanged = false;
3232 bool LoggingChanged = false;
3233 bool ChangeSubjectChanged = false;
3234 bool AllowInvitesChanged = false;
3235 bool PrivateMessagesChanged = false;
3236 bool MaxOccupantsChanged = false;
3237 bool PresenceBroadcastChanged = false;
3238 bool MembershipListChanged = false;
3239 bool PersistentChanged = false;
3240 bool ModeratedChanged = false;
3241 bool MembersOnlyChanged = false;
3242 bool PublicChanged = false;
3243 bool PasswordProtectedChanged = false;
3244 bool PasswordChanged = false;
3245 bool DiscoverJidsChanged = false;
3246 bool MaxHistoryChanged = false;
3247 bool AdminsChanged = false;
3248 bool OwnersChanged = false;
3249 SortedDictionary<int, bool> StatusChange = null;
3250
3251 if (!(RoomName is null) && Room.Name != RoomName)
3252 {
3253 Room.Name = RoomName;
3254 NameChanged = true;
3255 this.AddStatus(ref StatusChange, 104);
3256 }
3257
3258 if (!(RoomDescription is null) && Room.Description != RoomDescription)
3259 {
3260 Room.Description = RoomDescription;
3261 DescriptionChanged = true;
3262 this.AddStatus(ref StatusChange, 104);
3263 }
3264
3265 if (!(RoomLanguage is null) && Room.Language != RoomLanguage)
3266 {
3267 Room.Language = RoomLanguage;
3268 LanguageChanged = true;
3269 this.AddStatus(ref StatusChange, 104);
3270 }
3271
3272 if (Logging.HasValue && Logging.Value != Room.Logging)
3273 {
3274 Room.Logging = Logging.Value;
3275 LoggingChanged = true;
3276 this.AddStatus(ref StatusChange, Room.Logging ? 170 : 171);
3277 this.AddStatus(ref StatusChange, 104);
3278 }
3279
3280 if (ChangeSubject.HasValue && ChangeSubject.Value != Room.AllowChangeSubject)
3281 {
3282 Room.AllowChangeSubject = ChangeSubject.Value;
3283 ChangeSubjectChanged = true;
3284 this.AddStatus(ref StatusChange, 104);
3285 }
3286
3287 if (AllowInvites.HasValue && AllowInvites.Value != Room.AllowInvites)
3288 {
3289 Room.AllowInvites = AllowInvites.Value;
3290 AllowInvitesChanged = true;
3291 this.AddStatus(ref StatusChange, 104);
3292 }
3293
3294 if (PrivateMessages.HasValue && PrivateMessages.Value != Room.AllowPrivateMessages)
3295 {
3296 Room.AllowPrivateMessages = PrivateMessages.Value;
3297 PrivateMessagesChanged = true;
3298 this.AddStatus(ref StatusChange, 104);
3299 }
3300
3301 if (MaxUsers.HasValue && MaxUsers.Value != Room.MaxOccupants)
3302 {
3303 Room.MaxOccupants = MaxUsers.Value;
3304 MaxOccupantsChanged = true;
3305 this.AddStatus(ref StatusChange, 104);
3306 }
3307
3308 if (PresenceBroadcast.HasValue && PresenceBroadcast.Value != Room.PresenceBroadcast)
3309 {
3310 Room.PresenceBroadcast = PresenceBroadcast.Value;
3311 PresenceBroadcastChanged = true;
3312 this.AddStatus(ref StatusChange, 104);
3313 }
3314
3315 if (GetMemberList.HasValue && GetMemberList.Value != Room.GetMemberList)
3316 {
3317 Room.GetMemberList = GetMemberList.Value;
3318 MembershipListChanged = true;
3319 this.AddStatus(ref StatusChange, 104);
3320 }
3321
3322 if (Persist.HasValue && Persist.Value != Room.Persistent)
3323 {
3324 Room.Persistent = Persist.Value;
3325 PersistentChanged = true;
3326 this.AddStatus(ref StatusChange, 104);
3327 }
3328
3329 if (Moderated.HasValue && Moderated.Value != Room.Moderated)
3330 {
3331 Room.Moderated = Moderated.Value;
3332 ModeratedChanged = true;
3333 this.AddStatus(ref StatusChange, 104);
3334 }
3335
3336 if (MembersOnly.HasValue && MembersOnly.Value != Room.MembersOnly)
3337 {
3338 Room.MembersOnly = MembersOnly.Value;
3339 MembersOnlyChanged = true;
3340 this.AddStatus(ref StatusChange, 104);
3341 }
3342
3343 if (Public.HasValue && Public.Value != Room.Public)
3344 {
3345 Room.Public = Public.Value;
3346 PublicChanged = true;
3347 this.AddStatus(ref StatusChange, 104);
3348 }
3349
3350 if (PasswordProtected.HasValue && PasswordProtected.Value != Room.PasswordProtected)
3351 {
3352 Room.PasswordProtected = PasswordProtected.Value;
3353 PasswordProtectedChanged = true;
3354 this.AddStatus(ref StatusChange, 104);
3355 }
3356
3357 if (!(Password is null) && Password != Room.Password)
3358 {
3359 Room.Password = Password;
3360 PasswordChanged = true;
3361 this.AddStatus(ref StatusChange, 104);
3362 }
3363
3364 if (DiscoverJids.HasValue && DiscoverJids.Value != Room.DiscoverRealJids)
3365 {
3366 Room.DiscoverRealJids = DiscoverJids.Value;
3367 DiscoverJidsChanged = true;
3368 this.AddStatus(ref StatusChange, 104);
3369
3370 switch (Room.DiscoverRealJids)
3371 {
3372 case DiscoverReadJids.Anyone:
3373 this.AddStatus(ref StatusChange, 172);
3374 break;
3375
3376 case DiscoverReadJids.Moderators:
3377 this.AddStatus(ref StatusChange, 173);
3378 break;
3379 }
3380 }
3381
3382 if (MaxHistory.HasValue && MaxHistory.Value != Room.MaxHistoryFetch)
3383 {
3384 Room.MaxHistoryFetch = MaxHistory.Value;
3385 MaxHistoryChanged = true;
3386 this.AddStatus(ref StatusChange, 104);
3387 }
3388
3389 if (!(Administrators is null) && !AreEqual(Room.Admins, Administrators))
3390 {
3391 if (!(Room.Admins is null))
3392 {
3393 foreach (CaseInsensitiveString Jid in Room.Admins)
3394 {
3395 if (Array.IndexOf(Administrators, Jid) < 0)
3396 {
3397 MucOccupant Occupant = await this.GetOccupantByBareJid(Room, Jid);
3398 if (!(Occupant is null))
3399 {
3400 Occupant.Affiliation = new Member();
3401 Occupant.Role = new Participant();
3402
3403 if (Occupant.Persistent)
3404 await Database.UpdateLazy(Occupant);
3405
3406 await this.PresenceUserXmlToAll(Room, Occupant);
3407 }
3408 }
3409 }
3410 }
3411
3412 OldAdmins = Room.Admins ?? new CaseInsensitiveString[0];
3413 Room.Admins = Administrators;
3414 AdminsChanged = true;
3415 this.AddStatus(ref StatusChange, 104);
3416 }
3417
3418 if (!(Owners is null) && !AreEqual(Room.Owners, Owners))
3419 {
3420 if (!(Room.Owners is null))
3421 {
3422 foreach (CaseInsensitiveString Jid in Room.Owners)
3423 {
3424 if (Array.IndexOf(Owners, Jid) < 0)
3425 {
3426 MucOccupant Occupant = await this.GetOccupantByBareJid(Room, Jid);
3427 if (!(Occupant is null))
3428 {
3429 Occupant.Affiliation = new Member();
3430 Occupant.Role = new Participant();
3431
3432 if (Occupant.Persistent)
3433 await Database.UpdateLazy(Occupant);
3434
3435 await this.PresenceUserXmlToAll(Room, Occupant);
3436 }
3437 }
3438 }
3439 }
3440
3441 OldOwners = Room.Owners ?? new CaseInsensitiveString[0];
3442 Room.Owners = Owners;
3443 OwnersChanged = true;
3444 this.AddStatus(ref StatusChange, 104);
3445 }
3446
3447 if (AdminsChanged)
3448 {
3449 foreach (CaseInsensitiveString Jid in Administrators)
3450 {
3451 if (Array.IndexOf(OldAdmins, Jid) < 0)
3452 {
3453 MucOccupant Occupant = await this.GetOccupantByBareJid(Room, Jid);
3454 if (!(Occupant is null))
3455 {
3456 Occupant.Affiliation = new Affiliations.Admin();
3457 Occupant.Role = new Moderator();
3458
3459 if (Occupant.Persistent)
3460 await Database.UpdateLazy(Occupant);
3461
3462 await this.PresenceUserXmlToAll(Room, Occupant);
3463 }
3464 }
3465 }
3466 }
3467
3468 if (OwnersChanged)
3469 {
3470 foreach (CaseInsensitiveString Jid in Owners)
3471 {
3472 if (Array.IndexOf(OldOwners, Jid) < 0)
3473 {
3474 MucOccupant Occupant = await this.GetOccupantByBareJid(Room, Jid);
3475 if (!(Occupant is null))
3476 {
3477 Occupant.Affiliation = new Owner();
3478 Occupant.Role = new Moderator();
3479
3480 if (Occupant.Persistent)
3481 await Database.UpdateLazy(Occupant);
3482
3483 await this.PresenceUserXmlToAll(Room, Occupant);
3484 }
3485 }
3486 }
3487 }
3488
3489 bool Created = Room.Locked;
3490 Room.Locked = false;
3491
3492 if (Room.Persistent)
3493 {
3494 if (string.IsNullOrEmpty(Room.ObjectID))
3495 await Database.InsertLazy(Room);
3496 else
3497 await Database.UpdateLazy(Room);
3498
3499 if (Created)
3500 {
3501 foreach (MucOccupant Occupant in Room.GetOccupants())
3502 {
3503 if (!Occupant.Persistent)
3504 {
3505 Occupant.Persistent = true;
3506
3507 if (string.IsNullOrEmpty(Occupant.ObjectID))
3508 await Database.InsertLazy(Occupant);
3509 else
3510 await Database.UpdateLazy(Occupant);
3511 }
3512 }
3513 }
3514 }
3515
3516 await e.IqResult(string.Empty, e.To);
3517
3518 StringBuilder sb = new StringBuilder();
3519
3520 if (!(StatusChange is null))
3521 {
3522 sb.Append("<x xmlns='");
3523 sb.Append(NamespaceMucUser);
3524 sb.Append("'>");
3525
3526 foreach (int Code in StatusChange.Keys)
3527 {
3528 sb.Append("<status code='");
3529 sb.Append(Code.ToString());
3530 sb.Append("'/>");
3531 }
3532
3533 sb.Append("</x>");
3534
3535 await this.PresenceXmlToAll(Room, sb.ToString());
3536
3537 sb.Clear();
3538 }
3539
3540 sb.Append("| ");
3541 if (Created)
3542 sb.Append("Room Created");
3543 else
3544 sb.Append("Room Configuration Updated");
3545
3546 sb.AppendLine(" ||");
3547 sb.AppendLine("|:-----|:------|");
3548 sb.Append("| Room: | `");
3549 sb.Append(Room.Room);
3550 sb.AppendLine("` |");
3551 sb.Append("| Domain: | `");
3552 sb.Append(Room.Domain);
3553 sb.AppendLine("` |");
3554 sb.Append("| Creator: | `");
3555 sb.Append(Room.Creator);
3556 sb.AppendLine("` |");
3557 sb.Append("| Name: | ");
3558 sb.Append(BoldIfChanged(MarkdownDocument.Encode(OneRow(Room.Name)), NameChanged));
3559 sb.AppendLine(" |");
3560 sb.Append("| Description: | ");
3561 sb.Append(BoldIfChanged(MarkdownDocument.Encode(OneRow(Room.Description)), DescriptionChanged));
3562 sb.AppendLine(" |");
3563 sb.Append("| Language: | ");
3564 sb.Append(BoldIfChanged(MarkdownDocument.Encode(OneRow(Room.Language)), LanguageChanged));
3565 sb.AppendLine(" |");
3566 sb.Append("| Logging: | ");
3567 sb.Append(BoldIfChanged(OneRow(Room.Logging), LoggingChanged));
3568 sb.AppendLine(" |");
3569 sb.Append("| Allow Change of Subject: | ");
3570 sb.Append(BoldIfChanged(OneRow(Room.AllowChangeSubject), ChangeSubjectChanged));
3571 sb.AppendLine(" |");
3572 sb.Append("| Allow Invites: | ");
3573 sb.Append(BoldIfChanged(OneRow(Room.AllowInvites), AllowInvitesChanged));
3574 sb.AppendLine(" |");
3575 sb.Append("| Private Messages: | ");
3576 sb.Append(BoldIfChanged(OneRow(Room.AllowPrivateMessages), PrivateMessagesChanged));
3577 sb.AppendLine(" |");
3578 sb.Append("| Max Occupants: | ");
3579 sb.Append(BoldIfChanged(Room.MaxOccupants == int.MaxValue ? "No limit" : Room.MaxOccupants.ToString(), MaxOccupantsChanged));
3580 sb.AppendLine(" |");
3581 sb.Append("| Presence Broadcast: | ");
3582 sb.Append(BoldIfChanged(OneRow(Room.PresenceBroadcast), PresenceBroadcastChanged));
3583 sb.AppendLine(" |");
3584 sb.Append("| Membership List: | ");
3585 sb.Append(BoldIfChanged(OneRow(Room.GetMemberList), MembershipListChanged));
3586 sb.AppendLine(" |");
3587 sb.Append("| Persistent: | ");
3588 sb.Append(BoldIfChanged(OneRow(Room.Persistent), PersistentChanged));
3589 sb.AppendLine(" |");
3590 sb.Append("| Moderated: | ");
3591 sb.Append(BoldIfChanged(OneRow(Room.Moderated), ModeratedChanged));
3592 sb.AppendLine(" |");
3593 sb.Append("| Members-Only: | ");
3594 sb.Append(BoldIfChanged(OneRow(Room.MembersOnly), MembersOnlyChanged));
3595 sb.AppendLine(" |");
3596 sb.Append("| Public: | ");
3597 sb.Append(BoldIfChanged(OneRow(Room.Public), PublicChanged));
3598 sb.AppendLine(" |");
3599 sb.Append("| Password protection: | ");
3600 sb.Append(BoldIfChanged(OneRow(Room.PasswordProtected), PasswordProtectedChanged));
3601 sb.AppendLine(" |");
3602 sb.Append("| Password: | ");
3603 sb.Append(BoldIfChanged(OneRow(!string.IsNullOrEmpty(Room.Password)), PasswordChanged));
3604 sb.AppendLine(" |");
3605 sb.Append("| Discover JIDs: | ");
3606 sb.Append(BoldIfChanged(OneRow(Room.DiscoverRealJids), DiscoverJidsChanged));
3607 sb.AppendLine(" |");
3608 sb.Append("| Max History: | ");
3609 sb.Append(BoldIfChanged(OneRow(Room.MaxHistoryFetch.ToString()), MaxHistoryChanged));
3610 sb.AppendLine(" |");
3611 sb.Append("| Administrators: | ");
3612 sb.Append(BoldIfChanged(OneRow(Room.Admins), AdminsChanged));
3613 sb.AppendLine(" |");
3614 sb.Append("| Owners: | ");
3615 sb.Append(BoldIfChanged(OneRow(Room.Owners), OwnersChanged));
3616 sb.AppendLine(" |");
3617
3618 await this.SendToAdmin(Room, "chat", await GenerateMessageXml(sb.ToString()));
3619 }
3620 else if (Type == "cancel")
3621 {
3622 if (Room.Locked)
3623 this.rooms.Remove(RoomId);
3624
3625 await e.IqResult(string.Empty, e.To);
3626 }
3627 else
3628 await e.IqErrorBadRequest(e.To, "Unexpected form type.", "en");
3629 }
3630 }
3631 }
3632
3633 private void AddStatus(ref SortedDictionary<int, bool> Codes, int Code)
3634 {
3635 if (Codes is null)
3636 Codes = new SortedDictionary<int, bool>();
3637
3638 Codes[Code] = true;
3639 }
3640
3641 private async Task PresenceUserXmlToAll(MucRoom Room, MucOccupant Occupant)
3642 {
3643 XmppAddress From = new XmppAddress(Occupant.OccupantJid);
3644
3645 foreach (MucOccupant Occupant2 in Room.GetOccupants())
3646 {
3647 if (!(Occupant2.FullJids is null))
3648 {
3649 foreach (XmppAddress Jid in Occupant2.FullJids)
3650 {
3651 if (Room.CanDiscoverRealJids(Occupant2) && !(Occupant.FullJids is null) && Occupant.FullJids.Length > 0)
3652 {
3653 foreach (XmppAddress FullJid in Occupant.FullJids)
3654 {
3655 await this.Server.Presence(string.Empty, Guid.NewGuid().ToString(), Jid, From, string.Empty,
3656 Occupant.GetUserXml(FullJid.Address, Occupant.NickName), this);
3657 }
3658 }
3659 else
3660 {
3661 await this.Server.Presence(string.Empty, Guid.NewGuid().ToString(), Jid, From, string.Empty,
3662 Occupant.GetUserXml(null, Occupant.NickName), this);
3663 }
3664 }
3665 }
3666 }
3667 }
3668
3669 private Task PresenceXmlToAll(MucRoom Room, string Xml)
3670 {
3671 XmppAddress From = new XmppAddress(Room.Room + "@" + Room.Domain);
3672 return this.PresenceXmlToAll(From, Room.GetOccupants(), Xml);
3673 }
3674
3675 private async Task PresenceXmlToAll(XmppAddress From, MucOccupant[] Occupants, string Xml)
3676 {
3677 foreach (MucOccupant Occupant2 in Occupants)
3678 {
3679 if (!(Occupant2.FullJids is null))
3680 {
3681 foreach (XmppAddress Jid in Occupant2.FullJids)
3682 await this.Server.Presence(string.Empty, Guid.NewGuid().ToString(), Jid, From, string.Empty, Xml, this);
3683 }
3684 }
3685 }
3686
3687 private static string OneRow(CaseInsensitiveString[] Strings)
3688 {
3689 if (Strings is null)
3690 return string.Empty;
3691
3692 StringBuilder sb = new StringBuilder();
3693 bool First = true;
3694
3695 foreach (CaseInsensitiveString s in Strings)
3696 {
3697 if (First)
3698 First = false;
3699 else
3700 sb.Append("<br/>");
3701
3702 sb.Append('`');
3703 sb.Append(s.Value);
3704 sb.Append('`');
3705 }
3706
3707 return sb.ToString();
3708 }
3709
3710 private static bool AreEqual(Array A1, Array A2)
3711 {
3712 if ((A1 is null) ^ (A2 is null))
3713 return false;
3714
3715 if (A1 is null)
3716 return true;
3717
3718 int c = A1.Length;
3719 if (A2.Length != c)
3720 return false;
3721
3722 int i;
3723
3724 for (i = 0; i < c; i++)
3725 {
3726 if (!A1.GetValue(i).Equals(A2.GetValue(i)))
3727 return false;
3728 }
3729
3730 return true;
3731 }
3732
3733 private static string BoldIfChanged(string s, bool Changed)
3734 {
3735 return Changed ? "**" + s + "**" : s;
3736 }
3737
3738 private static string OneRow(Enum e)
3739 {
3740 return "`" + e.ToString() + "`";
3741 }
3742
3743 private static string OneRow(bool b)
3744 {
3745 return b ? "✔" : "✗";
3746 }
3747
3748 private static string OneRow(string s)
3749 {
3750 return s.Replace("\r\n", "\n").Replace('\r', '\n').Replace('\n', ' ');
3751 }
3752
3753 private static string GetValue(XmlElement E)
3754 {
3755 foreach (XmlNode N in E.ChildNodes)
3756 {
3757 if (N is XmlElement E2 && E2.LocalName == "value" && E.NamespaceURI == E2.NamespaceURI)
3758 return E2.InnerText;
3759 }
3760
3761 return string.Empty;
3762 }
3763
3764 private static CaseInsensitiveString[] GetValues(XmlElement E)
3765 {
3766 List<CaseInsensitiveString> Values = new List<CaseInsensitiveString>();
3767
3768 foreach (XmlNode N in E.ChildNodes)
3769 {
3770 if (N is XmlElement E2 && E2.LocalName == "value" && E.NamespaceURI == E2.NamespaceURI)
3771 Values.Add(E2.InnerText);
3772 }
3773
3774 return Values.ToArray();
3775 }
3776
3777 private async Task SelfPingHandler(object Sender, IqEventArgs e)
3778 {
3779 CaseInsensitiveString NickName = e.To.Resource;
3781 {
3782 await e.IqErrorBadRequest(e.To, "Nick-name missing.", "en");
3783 return;
3784 }
3785
3786 if (!e.From.IsFullJID)
3787 {
3788 await e.IqErrorBadRequest(e.To, "Sender not an XMPP client.", "en");
3789 return;
3790 }
3791
3792 MucRoom Room = await this.GetRoom(e.To.Account, e.To.Domain);
3793 if (Room is null)
3794 {
3795 await e.IqErrorNotAcceptable(e.To, "Room not found.", "en");
3796 return;
3797 }
3798
3799 if (!Room.TryGetOccupantFromNick(e.To.Resource, out MucOccupant Occupant) || !Occupant.ContainsFullJid(e.From))
3800 {
3801 await e.IqErrorNotAcceptable(e.To, "Occupant not in room.", "en");
3802 return;
3803 }
3804
3805 await e.IqResult(string.Empty, e.To);
3806 }
3807
3808 }
3809}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:46
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 ...
async Task< string > GeneratePlainText()
Generates Plain Text from the markdown text.
async Task< string > GenerateHTML()
Generates HTML from the markdown text.
static Task< MarkdownDocument > CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:914
static string HtmlValueEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe or the quote.
Definition: XML.cs:209
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
static bool TryParse(string s, out DateTime Value)
Tries to decode a string encoded DateTime.
Definition: XML.cs:744
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
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:334
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:126
static LoginAuditor LoginAuditor
Current Login Auditor. Should be used by modules accepting user logins, to protect the system from un...
Definition: Gateway.cs:3033
Base class for components.
Definition: Component.cs:16
bool UnregisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters a message handler.
Definition: Component.cs:297
void RegisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Set handler.
Definition: Component.cs:161
CaseInsensitiveString Subdomain
Subdomain name.
Definition: Component.cs:76
bool UnregisterPresenceHandler(string LocalName, string Namespace, EventHandlerAsync< PresenceEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters a presence handler.
Definition: Component.cs:326
void RegisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Get handler.
Definition: Component.cs:149
static string InnerXmlNoJabberNamespace(XmlElement Xml)
Removes the jabber namespace from the outer XML of an element.
Definition: Component.cs:639
void RegisterPresenceHandler(string LocalName, string Namespace, EventHandlerAsync< PresenceEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers a presence handler.
Definition: Component.cs:213
readonly object synchObject
Synchronization object for thread-safe access to internal structures.
Definition: Component.cs:25
async Task< bool > Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
Message stanza.
Definition: Component.cs:481
XmppServer Server
XMPP Server.
Definition: Component.cs:96
bool UnregisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Get handler.
Definition: Component.cs:249
bool UnregisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Set handler.
Definition: Component.cs:262
void UnregisterFeature(string Namespace)
Unregisters a feature.
Definition: Component.cs:351
void RegisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers a message handler.
Definition: Component.cs:190
SortedDictionary< string, bool > features
Features supported by component.
Definition: Component.cs:20
void RegisterFeature(string Namespace)
Registers a feature.
Definition: Component.cs:233
Event arguments for IQ queries.
Definition: IqEventArgs.cs:12
Task IqErrorNotAuthorized(XmppAddress From, string ErrorText, string Language)
Returns a not-authorized error.
Definition: IqEventArgs.cs:285
XmppAddress From
From address attribute
Definition: IqEventArgs.cs:93
Task IqErrorNotAcceptable(XmppAddress From, string ErrorText, string Language)
Returns a not-acceptable error.
Definition: IqEventArgs.cs:243
Task IqResult(string Xml, string From)
Returns a response to the current request.
Definition: IqEventArgs.cs:113
Task IqErrorItemNotFound(XmppAddress From, string ErrorText, string Language)
Returns a item-not-found error.
Definition: IqEventArgs.cs:201
XmlElement Query
Query element, if found, null otherwise.
Definition: IqEventArgs.cs:70
Task IqErrorNotAllowed(XmppAddress From, string ErrorText, string Language)
Returns a not-allowed error.
Definition: IqEventArgs.cs:187
XmppAddress To
To address attribute
Definition: IqEventArgs.cs:88
async Task IqError(string ErrorType, string Xml, XmppAddress From, string ErrorText, string Language)
Returns an error response to the current request.
Definition: IqEventArgs.cs:137
Task IqErrorServiceUnavailable(XmppAddress From, string ErrorText, string Language)
Returns a service-unavailable error.
Definition: IqEventArgs.cs:215
Task IqErrorJidMalformed(XmppAddress From, string ErrorText, string Language)
Returns a jid-malformed error.
Definition: IqEventArgs.cs:271
Task IqErrorConflict(XmppAddress From, string ErrorText, string Language)
Returns a conflict error.
Definition: IqEventArgs.cs:257
Task IqErrorBadRequest(XmppAddress From, string ErrorText, string Language)
Returns a bad-request error.
Definition: IqEventArgs.cs:159
Task IqErrorForbidden(XmppAddress From, string ErrorText, string Language)
Returns a forbidden error.
Definition: IqEventArgs.cs:229
Task< bool > MessageErrorServiceUnavailable(XmppAddress From, string ErrorText, string Language)
Sens a message error stanza
Task< bool > MessageErrorItemNotFound(XmppAddress From, string ErrorText, string Language)
Sens a message error stanza
Task< bool > MessageErrorJidMalformed(XmppAddress From, string ErrorText, string Language)
Sens a message error stanza
Task< bool > MessageErrorNotAcceptable(XmppAddress From, string ErrorText, string Language)
Sens a message error stanza
Task< bool > MessageErrorConflict(XmppAddress From, string ErrorText, string Language)
Sens a message error stanza
Task< bool > MessageErrorForbidden(XmppAddress From, string ErrorText, string Language)
Sens a message error stanza
XmppAddress From
From address attribute
Task< bool > MessageErrorBadRequest(XmppAddress From, string ErrorText, string Language)
Sens a message error stanza
XmlElement Content
Content element, if found, null otherwise.
Presence information event arguments.
Task PresenceErrorRegistrationRequired(XmppAddress From, string ErrorText, string Language)
Returns an error response to the current request.
XmlElement Content
Content element, if found, null otherwise.
Task PresenceErrorServiceUnavailable(XmppAddress From, string ErrorText, string Language)
Returns an error response to the current request.
Task PresenceErrorItemNotFound(XmppAddress From, string ErrorText, string Language)
Returns an error response to the current request.
Task PresenceErrorForbidden(XmppAddress From, string ErrorText, string Language)
Returns an error response to the current request.
Task PresenceErrorNotAcceptable(XmppAddress From, string ErrorText, string Language)
Returns an error response to the current request.
Task PresenceErrorNotAllowed(XmppAddress From, string ErrorText, string Language)
Returns an error response to the current request.
Task PresenceErrorJidMalformed(XmppAddress From, string ErrorText, string Language)
Returns an error response to the current request.
Task PresenceErrorConflict(XmppAddress From, string ErrorText, string Language)
Returns an error response to the current request.
Task PresenceErrorNotAuthorized(XmppAddress From, string ErrorText, string Language)
Returns an error response to the current request.
XmlElement StanzaElement
Stanza element.
Definition: Stanza.cs:114
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
override string ToString()
object.ToString()
Definition: XmppAddress.cs:190
bool IsBareJID
If the address is a Bare JID.
Definition: XmppAddress.cs:159
bool HasAccount
If the address has an account part.
Definition: XmppAddress.cs:167
CaseInsensitiveString Resource
Resource part.
Definition: XmppAddress.cs:71
CaseInsensitiveString Domain
Domain
Definition: XmppAddress.cs:97
CaseInsensitiveString Address
XMPP Address
Definition: XmppAddress.cs:37
XmppAddress ToBareJID()
Returns the Bare JID as an XmppAddress object.
Definition: XmppAddress.cs:215
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
bool IsFullJID
If the Address is a Full JID.
Definition: XmppAddress.cs:151
const string DiscoveryItemsNamespace
http://jabber.org/protocol/disco#items (XEP-0030)
Definition: XmppServer.cs:133
const string ExtendedAddressingNamespace
http://jabber.org/protocol/address (XEP-0033)
Definition: XmppServer.cs:138
const string DiscoveryNamespace
http://jabber.org/protocol/disco#info (XEP-0030)
Definition: XmppServer.cs:128
async Task< bool > Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
Message stanza.
Definition: XmppServer.cs:2060
const string ContentNamespace
urn:xmpp:content
Definition: XmppServer.cs:218
Task< bool > SendIqRequest(string Type, string From, string To, string Language, string ContentXml, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends an IQ stanza to a recipient.
Definition: XmppServer.cs:3317
const string DataFormsNamespace
jabber:x:data (XEP-0004)
Definition: XmppServer.cs:118
const string PingNamespace
urn:xmpp:ping (XEP-0199)
Definition: XmppServer.cs:178
bool IsServerDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the server domain, or optionally, an alternative domain.
Definition: XmppServer.cs:861
const string RegisterNamespace
jabber:iq:register (XEP-0077)
Definition: XmppServer.cs:153
const string DelayedDeliveryNamespace
urn:xmpp:delay (XEP-0203)
Definition: XmppServer.cs:188
async Task< bool > Presence(string Type, string Id, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
Presence stanza.
Definition: XmppServer.cs:2647
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
int Length
Gets the number of characters in the current CaseInsensitiveString object.
string LowerCase
Lower-case representation of the case-insensitive string.
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static Task< IEnumerable< object > > FindDelete(string Collection, params string[] SortOrder)
Finds objects in a given collection and deletes them in the same atomic operation.
Definition: Database.cs:879
static async Task DeleteLazy(object Object)
Deletes an object in the database, if unlocked. If locked, object will be deleted at next opportunity...
Definition: Database.cs:756
static Task EndBulk()
Ends bulk-processing of data. Must be called once for every call to StartBulk.
Definition: Database.cs:1494
static async Task InsertLazy(object Object)
Inserts an object into the database, if unlocked. If locked, object will be inserted at next opportun...
Definition: Database.cs:156
static Task StartBulk()
Starts bulk-proccessing of data. Must be followed by a call to EndBulk.
Definition: Database.cs:1486
static async Task UpdateLazy(object Object)
Updates an object in the database, if unlocked. If locked, object will be updated at next opportunity...
Definition: Database.cs:687
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
Custom filter used to filter objects using an external expression.
Definition: FilterCustom.cs:10
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field greater or equal to a given value.
Implements an in-memory cache.
Definition: Cache.cs:15
Abstract base class for MUC affiliations.
Definition: Affiliation.cs:12
abstract Role GetDefaultRole(MucRoom Room)
Gets the default role, given type of room type. If null is returned, entry is not permitted.
abstract string Name
Name of affiliation, in XML.
Definition: Affiliation.cs:17
Multi-User-Chat (MUC) Component component, as defined in XEP-0045. https://xmpp.org/extensions/xep-00...
override async Task DiscoveryQueryGet(object Sender, IqEventArgs e)
Get handler for Service Discovery query (XEP-0030).
const string NamespaceMucOwner
http://jabber.org/protocol/muc#owner
MultiUserChatComponent(XmppServer Server, CaseInsensitiveString Subdomain, string Name)
Multi-User-Chat (MUC) Component component, as defined in XEP-0045. https://xmpp.org/extensions/xep-00...
const string FormTypeRoomConfig
http://jabber.org/protocol/muc#roomconfig
const string NamespaceMucUser
http://jabber.org/protocol/muc#user
override async Task MessageNoHandlerReceived(MessageEventArgs e)
Message without corresponding handler received.
const string FormTypeRequest
http://jabber.org/protocol/muc#request
override async Task DiscoveryQueryItemsGet(object Sender, IqEventArgs e)
Get handler for Service Discovery items query (XEP-0030).
override bool SupportsAccounts
If the component supports accounts (true), or if the subdomain name is the only valid address.
override async Task PresenceNoHandlerReceived(PresenceEventArgs e)
Presence stanza without a corresponding handler received.
const string FormTypeRegister
http://jabber.org/protocol/muc#register
const string NamespaceMucAdmin
http://jabber.org/protocol/muc#admin
Abstract base class for MUC roles.
Definition: Role.cs:12
abstract string Name
Name of role, in XML.
Definition: Role.cs:17
abstract bool ReceiveMessages
If user can receive messages
Definition: Role.cs:29
abstract bool ReceiveOccupantPresence
If user can receive presence stanzas from occupants
Definition: Role.cs:34
Service Module hosting the XMPP broker and its components.
FormType
Type of data form.
Definition: FormType.cs:7
Reason
Reason a token is not valid.
Definition: JwtFactory.cs:12