Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ProvisioningComponent.cs
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Security.Cryptography;
5using System.Security.Cryptography.X509Certificates;
6using System.Text;
7using System.Threading.Tasks;
8using System.Xml;
9using Waher.Content;
11using Waher.Events;
22using Waher.Things;
24
26{
30 public enum RuleRange
31 {
35 Caller,
36
40 Domain,
41
45 All
46 }
47
52 {
56 public const string NamespaceProvisioningTokenIeeeV1 = "urn:ieee:iot:prov:t:1.0";
57
61 public const string NamespaceProvisioningTokenNeuroFoundationV1 = "urn:nf:iot:prov:t:1.0";
62
66 public const string NamespaceProvisioningDeviceIeeeV1 = "urn:ieee:iot:prov:d:1.0";
67
71 public const string NamespaceProvisioningDeviceNeuroFoundationV1 = "urn:nf:iot:prov:d:1.0";
72
76 public const string NamespaceProvisioningOwnerIeeeV1 = "urn:ieee:iot:prov:o:1.0";
77
81 public const string NamespaceProvisioningOwnerNeuroFoundationV1 = "urn:nf:iot:prov:o:1.0";
82
86 public const string NamespaceIoTDiscoveryXsfV0 = "urn:xmpp:iot:discovery";
87
91 public const string NamespaceIoTDiscoveryIeeeV1 = "urn:ieee:iot:disco:1.0";
92
96 public const string NamespaceIoTDiscoveryNeuroFoundationV1 = "urn:nf:iot:disco:1.0";
97
101 public const string NamespaceSoftwareUpdatesIeeeV1 = "urn:ieee:iot:swu:1.0";
102
106 public const string NamespaceSoftwareUpdatesNeuroFoundationV1 = "urn:nf:iot:swu:1.0";
107
113 public static string NamespaceProvisioningToken(NamespaceSet Version)
114 {
115 switch (Version)
116 {
117 case NamespaceSet.XsfV0:
119 default:
120 case NamespaceSet.NeuroFoundationV1: return NamespaceProvisioningTokenNeuroFoundationV1;
121 }
122 }
123
129 public static string NamespaceProvisioningDevice(NamespaceSet Version)
130 {
131 switch (Version)
132 {
133 case NamespaceSet.XsfV0:
135 default:
136 case NamespaceSet.NeuroFoundationV1: return NamespaceProvisioningDeviceNeuroFoundationV1;
137 }
138 }
139
145 public static string NamespaceProvisioningOwner(NamespaceSet Version)
146 {
147 switch (Version)
148 {
149 case NamespaceSet.XsfV0:
151 default:
152 case NamespaceSet.NeuroFoundationV1: return NamespaceProvisioningOwnerNeuroFoundationV1;
153 }
154 }
155
161 public static string NamespaceIoTDiscovery(NamespaceSet Version)
162 {
163 switch (Version)
164 {
165 case NamespaceSet.XsfV0: return NamespaceIoTDiscoveryXsfV0;
166 case NamespaceSet.IeeeV1: return NamespaceIoTDiscoveryIeeeV1;
167 default:
168 case NamespaceSet.NeuroFoundationV1: return NamespaceIoTDiscoveryNeuroFoundationV1;
169 }
170 }
171
177 public static string NamespaceSoftwareUpdates(NamespaceSet Version)
178 {
179 switch (Version)
180 {
181 case NamespaceSet.XsfV0:
183 default:
184 case NamespaceSet.NeuroFoundationV1: return NamespaceSoftwareUpdatesNeuroFoundationV1;
185 }
186 }
187
188 private Cache<int, KeyValuePair<string, KeyValuePair<byte[], X509Certificate2>>> tokenRequests = new Cache<int, KeyValuePair<string, KeyValuePair<byte[], X509Certificate2>>>(int.MaxValue, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1), true);
189 private readonly Cache<string, bool> challengedTokens = new Cache<string, bool>(int.MaxValue, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1), true);
190 private readonly Dictionary<string, KeyValuePair<byte[], X509Certificate2>> certificates = new Dictionary<string, KeyValuePair<byte[], X509Certificate2>>();
191 private int seqNr = 0;
192
200 : base(Server, Subdomain, Name)
201 {
202 #region Neuro-Foundation V1 handlers
203
204 this.RegisterIqGetHandler("getToken", NamespaceProvisioningTokenNeuroFoundationV1, this.GetTokenHandler, true);
205 this.RegisterIqGetHandler("getTokenChallengeResponse", NamespaceProvisioningTokenNeuroFoundationV1, this.GetTokenChallengeResponseHandler, false);
206 this.RegisterIqGetHandler("getCertificate", NamespaceProvisioningTokenNeuroFoundationV1, this.GetCertificateHandler, false);
207
208 this.RegisterIqGetHandler("isFriend", NamespaceProvisioningDeviceNeuroFoundationV1, this.IsFriendHandler, true);
209 this.RegisterIqGetHandler("canRead", NamespaceProvisioningDeviceNeuroFoundationV1, this.CanReadHandler, false);
210 this.RegisterIqGetHandler("canControl", NamespaceProvisioningDeviceNeuroFoundationV1, this.CanControlHandler, false);
211
212 this.RegisterIqSetHandler("isFriendRule", NamespaceProvisioningOwnerNeuroFoundationV1, this.IsFriendRuleHandler, true);
213 this.RegisterIqSetHandler("canReadRule", NamespaceProvisioningOwnerNeuroFoundationV1, this.CanReadRuleHandler, false);
214 this.RegisterIqSetHandler("canControlRule", NamespaceProvisioningOwnerNeuroFoundationV1, this.CanControlRuleHandler, false);
215 this.RegisterIqSetHandler("clearCache", NamespaceProvisioningOwnerNeuroFoundationV1, this.ClearCacheHandler, false);
216 this.RegisterIqGetHandler("getDevices", NamespaceProvisioningOwnerNeuroFoundationV1, this.GetDevicesHandler, false);
217 this.RegisterIqSetHandler("deleteRules", NamespaceProvisioningOwnerNeuroFoundationV1, this.DeleteRulesHandler, false);
218
219 this.RegisterIqSetHandler("register", NamespaceIoTDiscoveryNeuroFoundationV1, this.RegisterHandler, true);
220 this.RegisterIqSetHandler("mine", NamespaceIoTDiscoveryNeuroFoundationV1, this.MineHandler, false);
221 this.RegisterIqSetHandler("update", NamespaceIoTDiscoveryNeuroFoundationV1, this.UpdateHandler, false);
222 this.RegisterIqSetHandler("remove", NamespaceIoTDiscoveryNeuroFoundationV1, this.RemoveHandler, false);
223 this.RegisterIqSetHandler("unregister", NamespaceIoTDiscoveryNeuroFoundationV1, this.UnregisterHandler, false);
224 this.RegisterIqSetHandler("disown", NamespaceIoTDiscoveryNeuroFoundationV1, this.DisownHandler, false);
225 this.RegisterIqGetHandler("search", NamespaceIoTDiscoveryNeuroFoundationV1, this.SearchHandler, false);
226
227 this.RegisterIqGetHandler("getPackageInfo", NamespaceSoftwareUpdatesNeuroFoundationV1, this.GetPackageInfoHandler, true);
228 this.RegisterIqGetHandler("getPackages", NamespaceSoftwareUpdatesNeuroFoundationV1, this.GetPackagesHandler, false);
229 this.RegisterIqSetHandler("subscribe", NamespaceSoftwareUpdatesNeuroFoundationV1, this.SubscribeHandler, false);
230 this.RegisterIqSetHandler("unsubscribe", NamespaceSoftwareUpdatesNeuroFoundationV1, this.UnsubscribeHandler, false);
231 this.RegisterIqGetHandler("getSubscriptions", NamespaceSoftwareUpdatesNeuroFoundationV1, this.GetSubscriptionsHandler, false);
232
233 #endregion
234
235 #region IEEE V1 handlers
236
237 this.RegisterIqGetHandler("getToken", NamespaceProvisioningTokenIeeeV1, this.GetTokenHandler, true);
238 this.RegisterIqGetHandler("getTokenChallengeResponse", NamespaceProvisioningTokenIeeeV1, this.GetTokenChallengeResponseHandler, false);
239 this.RegisterIqGetHandler("getCertificate", NamespaceProvisioningTokenIeeeV1, this.GetCertificateHandler, false);
240
241 this.RegisterIqGetHandler("isFriend", NamespaceProvisioningDeviceIeeeV1, this.IsFriendHandler, true);
242 this.RegisterIqGetHandler("canRead", NamespaceProvisioningDeviceIeeeV1, this.CanReadHandler, false);
243 this.RegisterIqGetHandler("canControl", NamespaceProvisioningDeviceIeeeV1, this.CanControlHandler, false);
244
245 this.RegisterIqSetHandler("isFriendRule", NamespaceProvisioningOwnerIeeeV1, this.IsFriendRuleHandler, true);
246 this.RegisterIqSetHandler("canReadRule", NamespaceProvisioningOwnerIeeeV1, this.CanReadRuleHandler, false);
247 this.RegisterIqSetHandler("canControlRule", NamespaceProvisioningOwnerIeeeV1, this.CanControlRuleHandler, false);
248 this.RegisterIqSetHandler("clearCache", NamespaceProvisioningOwnerIeeeV1, this.ClearCacheHandler, false);
249 this.RegisterIqGetHandler("getDevices", NamespaceProvisioningOwnerIeeeV1, this.GetDevicesHandler, false);
250 this.RegisterIqSetHandler("deleteRules", NamespaceProvisioningOwnerIeeeV1, this.DeleteRulesHandler, false);
251
252 this.RegisterIqSetHandler("register", NamespaceIoTDiscoveryIeeeV1, this.RegisterHandler, true);
253 this.RegisterIqSetHandler("mine", NamespaceIoTDiscoveryIeeeV1, this.MineHandler, false);
254 this.RegisterIqSetHandler("update", NamespaceIoTDiscoveryIeeeV1, this.UpdateHandler, false);
255 this.RegisterIqSetHandler("remove", NamespaceIoTDiscoveryIeeeV1, this.RemoveHandler, false);
256 this.RegisterIqSetHandler("unregister", NamespaceIoTDiscoveryIeeeV1, this.UnregisterHandler, false);
257 this.RegisterIqSetHandler("disown", NamespaceIoTDiscoveryIeeeV1, this.DisownHandler, false);
258 this.RegisterIqGetHandler("search", NamespaceIoTDiscoveryIeeeV1, this.SearchHandler, false);
259
260 this.RegisterIqGetHandler("getPackageInfo", NamespaceSoftwareUpdatesIeeeV1, this.GetPackageInfoHandler, true);
261 this.RegisterIqGetHandler("getPackages", NamespaceSoftwareUpdatesIeeeV1, this.GetPackagesHandler, false);
262 this.RegisterIqSetHandler("subscribe", NamespaceSoftwareUpdatesIeeeV1, this.SubscribeHandler, false);
263 this.RegisterIqSetHandler("unsubscribe", NamespaceSoftwareUpdatesIeeeV1, this.UnsubscribeHandler, false);
264 this.RegisterIqGetHandler("getSubscriptions", NamespaceSoftwareUpdatesIeeeV1, this.GetSubscriptionsHandler, false);
265
266 #endregion
267
268 #region XSF handlers
269
270 this.RegisterIqSetHandler("register", NamespaceIoTDiscoveryXsfV0, this.RegisterHandler, true);
271 this.RegisterIqSetHandler("mine", NamespaceIoTDiscoveryXsfV0, this.MineHandler, false);
272 this.RegisterIqSetHandler("update", NamespaceIoTDiscoveryXsfV0, this.UpdateHandler, false);
273 this.RegisterIqSetHandler("remove", NamespaceIoTDiscoveryXsfV0, this.RemoveHandler, false);
274 this.RegisterIqSetHandler("unregister", NamespaceIoTDiscoveryXsfV0, this.UnregisterHandler, false);
275 this.RegisterIqSetHandler("disown", NamespaceIoTDiscoveryXsfV0, this.DisownHandler, false);
276 this.RegisterIqGetHandler("search", NamespaceIoTDiscoveryXsfV0, this.SearchHandler, false);
277
278 #endregion
279 }
280
284 public override void Dispose()
285 {
286 #region Neuro-Foundation V1 handlers
287
288 this.UnregisterIqGetHandler("getToken", NamespaceProvisioningTokenNeuroFoundationV1, this.GetTokenHandler, true);
289 this.UnregisterIqGetHandler("getTokenChallengeResponse", NamespaceProvisioningTokenNeuroFoundationV1, this.GetTokenChallengeResponseHandler, false);
290 this.UnregisterIqGetHandler("getCertificate", NamespaceProvisioningTokenNeuroFoundationV1, this.GetCertificateHandler, false);
291
292 this.UnregisterIqGetHandler("isFriend", NamespaceProvisioningDeviceNeuroFoundationV1, this.IsFriendHandler, true);
293 this.UnregisterIqGetHandler("canRead", NamespaceProvisioningDeviceNeuroFoundationV1, this.CanReadHandler, false);
294 this.UnregisterIqGetHandler("canControl", NamespaceProvisioningDeviceNeuroFoundationV1, this.CanControlHandler, false);
295
296 this.UnregisterIqSetHandler("isFriendRule", NamespaceProvisioningOwnerNeuroFoundationV1, this.IsFriendRuleHandler, true);
297 this.UnregisterIqSetHandler("canReadRule", NamespaceProvisioningOwnerNeuroFoundationV1, this.CanReadRuleHandler, false);
298 this.UnregisterIqSetHandler("canControlRule", NamespaceProvisioningOwnerNeuroFoundationV1, this.CanControlRuleHandler, false);
299 this.UnregisterIqSetHandler("clearCache", NamespaceProvisioningOwnerNeuroFoundationV1, this.ClearCacheHandler, false);
300 this.UnregisterIqGetHandler("getDevices", NamespaceProvisioningOwnerNeuroFoundationV1, this.GetDevicesHandler, false);
301 this.UnregisterIqSetHandler("deleteRules", NamespaceProvisioningOwnerNeuroFoundationV1, this.DeleteRulesHandler, false);
302
303 this.UnregisterIqSetHandler("register", NamespaceIoTDiscoveryNeuroFoundationV1, this.UnregisterHandler, true);
304 this.UnregisterIqSetHandler("mine", NamespaceIoTDiscoveryNeuroFoundationV1, this.MineHandler, false);
305 this.UnregisterIqSetHandler("update", NamespaceIoTDiscoveryNeuroFoundationV1, this.UpdateHandler, false);
306 this.UnregisterIqSetHandler("remove", NamespaceIoTDiscoveryNeuroFoundationV1, this.RemoveHandler, false);
307 this.UnregisterIqSetHandler("unregister", NamespaceIoTDiscoveryNeuroFoundationV1, this.UnregisterHandler, false);
308 this.UnregisterIqSetHandler("disown", NamespaceIoTDiscoveryNeuroFoundationV1, this.DisownHandler, false);
309 this.UnregisterIqGetHandler("search", NamespaceIoTDiscoveryNeuroFoundationV1, this.SearchHandler, false);
310
311 this.UnregisterIqGetHandler("getPackageInfo", NamespaceSoftwareUpdatesNeuroFoundationV1, this.GetPackageInfoHandler, true);
312 this.UnregisterIqGetHandler("getPackages", NamespaceSoftwareUpdatesNeuroFoundationV1, this.GetPackagesHandler, false);
313 this.UnregisterIqSetHandler("subscribe", NamespaceSoftwareUpdatesNeuroFoundationV1, this.SubscribeHandler, false);
314 this.UnregisterIqSetHandler("unsubscribe", NamespaceSoftwareUpdatesNeuroFoundationV1, this.UnsubscribeHandler, false);
315 this.UnregisterIqGetHandler("getSubscriptions", NamespaceSoftwareUpdatesNeuroFoundationV1, this.GetSubscriptionsHandler, false);
316
317 #endregion
318
319 #region IEEE V1 handlers
320
321 this.UnregisterIqGetHandler("getToken", NamespaceProvisioningTokenIeeeV1, this.GetTokenHandler, true);
322 this.UnregisterIqGetHandler("getTokenChallengeResponse", NamespaceProvisioningTokenIeeeV1, this.GetTokenChallengeResponseHandler, false);
323 this.UnregisterIqGetHandler("getCertificate", NamespaceProvisioningTokenIeeeV1, this.GetCertificateHandler, false);
324
325 this.UnregisterIqGetHandler("isFriend", NamespaceProvisioningDeviceIeeeV1, this.IsFriendHandler, true);
326 this.UnregisterIqGetHandler("canRead", NamespaceProvisioningDeviceIeeeV1, this.CanReadHandler, false);
327 this.UnregisterIqGetHandler("canControl", NamespaceProvisioningDeviceIeeeV1, this.CanControlHandler, false);
328
329 this.UnregisterIqSetHandler("isFriendRule", NamespaceProvisioningOwnerIeeeV1, this.IsFriendRuleHandler, true);
330 this.UnregisterIqSetHandler("canReadRule", NamespaceProvisioningOwnerIeeeV1, this.CanReadRuleHandler, false);
331 this.UnregisterIqSetHandler("canControlRule", NamespaceProvisioningOwnerIeeeV1, this.CanControlRuleHandler, false);
332 this.UnregisterIqSetHandler("clearCache", NamespaceProvisioningOwnerIeeeV1, this.ClearCacheHandler, false);
333 this.UnregisterIqGetHandler("getDevices", NamespaceProvisioningOwnerIeeeV1, this.GetDevicesHandler, false);
334 this.UnregisterIqSetHandler("deleteRules", NamespaceProvisioningOwnerIeeeV1, this.DeleteRulesHandler, false);
335
336 this.UnregisterIqSetHandler("register", NamespaceIoTDiscoveryIeeeV1, this.UnregisterHandler, true);
337 this.UnregisterIqSetHandler("mine", NamespaceIoTDiscoveryIeeeV1, this.MineHandler, false);
338 this.UnregisterIqSetHandler("update", NamespaceIoTDiscoveryIeeeV1, this.UpdateHandler, false);
339 this.UnregisterIqSetHandler("remove", NamespaceIoTDiscoveryIeeeV1, this.RemoveHandler, false);
340 this.UnregisterIqSetHandler("unregister", NamespaceIoTDiscoveryIeeeV1, this.UnregisterHandler, false);
341 this.UnregisterIqSetHandler("disown", NamespaceIoTDiscoveryIeeeV1, this.DisownHandler, false);
342 this.UnregisterIqGetHandler("search", NamespaceIoTDiscoveryIeeeV1, this.SearchHandler, false);
343
344 this.UnregisterIqGetHandler("getPackageInfo", NamespaceSoftwareUpdatesIeeeV1, this.GetPackageInfoHandler, true);
345 this.UnregisterIqGetHandler("getPackages", NamespaceSoftwareUpdatesIeeeV1, this.GetPackagesHandler, false);
346 this.UnregisterIqSetHandler("subscribe", NamespaceSoftwareUpdatesIeeeV1, this.SubscribeHandler, false);
347 this.UnregisterIqSetHandler("unsubscribe", NamespaceSoftwareUpdatesIeeeV1, this.UnsubscribeHandler, false);
348 this.UnregisterIqGetHandler("getSubscriptions", NamespaceSoftwareUpdatesIeeeV1, this.GetSubscriptionsHandler, false);
349
350 #endregion
351
352 #region XSF handlers
353
354 this.UnregisterIqSetHandler("register", NamespaceIoTDiscoveryXsfV0, this.UnregisterHandler, true);
355 this.UnregisterIqSetHandler("mine", NamespaceIoTDiscoveryXsfV0, this.MineHandler, false);
356 this.UnregisterIqSetHandler("update", NamespaceIoTDiscoveryXsfV0, this.UpdateHandler, false);
357 this.UnregisterIqSetHandler("remove", NamespaceIoTDiscoveryXsfV0, this.RemoveHandler, false);
358 this.UnregisterIqSetHandler("unregister", NamespaceIoTDiscoveryXsfV0, this.UnregisterHandler, false);
359 this.UnregisterIqSetHandler("disown", NamespaceIoTDiscoveryXsfV0, this.DisownHandler, false);
360 this.UnregisterIqGetHandler("search", NamespaceIoTDiscoveryXsfV0, this.SearchHandler, false);
361
362 #endregion
363
364 this.tokenRequests?.Dispose();
365 this.tokenRequests = null;
366
367 this.registryAccounts?.Clear();
368 this.registryAccounts?.Dispose();
369 this.registryAccounts = null;
370 }
371
376 public override bool SupportsAccounts => false;
377
378 #region Provisioning, Tokens
379
380 private Task GetTokenHandler(object Sender, IqEventArgs e)
381 {
382 try
383 {
384 string Base64 = e.Query.InnerText;
385 byte[] Bin = Convert.FromBase64String(Base64);
386 X509Certificate2 Certificate = new X509Certificate2(Bin);
387
388 if (Certificate.Verify())
389 {
391 string Response = Convert.ToBase64String(Bin);
392 Bin = ((RSACryptoServiceProvider)Certificate.PublicKey.Key).Encrypt(Bin, true);
393 string Challenge = Convert.ToBase64String(Bin);
394
395 int SeqNr;
396
397 lock (this.synchObject)
398 {
399 do
400 {
401 SeqNr = this.seqNr++;
402 }
403 while (this.tokenRequests.ContainsKey(SeqNr));
404
405 this.tokenRequests.Add(SeqNr, new KeyValuePair<string, KeyValuePair<byte[], X509Certificate2>>(Response,
406 new KeyValuePair<byte[], X509Certificate2>(Bin, Certificate)));
407 }
408
409 e.IqResult("<getTokenChallenge xmlns='" + e.Query.NamespaceURI + "' seqnr='" +
410 SeqNr.ToString() + "'>" + Challenge + "</getTokenChallenge>", e.To);
411 }
412 else
413 e.IqErrorNotAcceptable(e.To, "Invalid certificate.", "en");
414 }
415 catch (Exception)
416 {
417 e.IqErrorBadRequest(e.To, "Invalid certificate.", "en");
418 }
419
420 return Task.CompletedTask;
421 }
422
423 private Task GetTokenChallengeResponseHandler(object Sender, IqEventArgs e)
424 {
425 XmlElement E = e.Query;
426 int SeqNr = XML.Attribute(E, "seqnr", 0);
427
428 if (!this.tokenRequests.TryGetValue(SeqNr, out KeyValuePair<string, KeyValuePair<byte[], X509Certificate2>> Rec))
429 e.IqErrorItemNotFound(e.To, "Sequence number not recognized.", "en");
430 else
431 {
432 this.tokenRequests.Remove(SeqNr);
433
434 string Response = E.InnerText;
435 if (Response != Rec.Key)
436 e.IqErrorBadRequest(e.To, "Invalid response.", "en");
437 else
438 {
439 byte[] Bin = XmppServer.GetRandomNumbers(64);
440 string Token = Convert.ToBase64String(Bin);
441
442 lock (this.certificates)
443 {
444 this.certificates[Token] = Rec.Value;
445 }
446
447 e.IqResult("<getTokenResponse xmlns='" + e.Query.NamespaceURI + "' token='" + e.To + ":" + Token + "'/>",
448 e.To);
449 }
450 }
451
452 return Task.CompletedTask;
453 }
454
455 private class TokenChallengesRecord
456 {
457 public NamespaceSet QueryVersion;
458 public Dictionary<string, bool> TokensToChallenge;
459 public IqEventArgs e;
460 public List<ThingReference> Nodes = null;
461 public List<string> Fields = null;
462 public FieldType FieldTypes;
463 public string[] ServiceTokens;
464 public string[] DeviceTokens;
465 public string[] UserTokens;
466 public bool Allowed = false;
467 public CaseInsensitiveString Jid;
468 public int NrTokensToChallenge;
469 public bool AllOk = true;
470 public bool Read;
471 }
472
473 private class TokenChallengeRecord
474 {
475 public TokenChallengesRecord Challenges;
476 public string Token;
477 public string Response;
478 }
479
480 private async Task TokenChallengeResponse(object Sender, IqResultEventArgs e)
481 {
482 TokenChallengeRecord Rec2 = (TokenChallengeRecord)e.State;
483 TokenChallengesRecord Rec = Rec2.Challenges;
484 XmlElement E = e.FirstElement;
485 bool Done;
486
487 if (e.Ok && !(E is null) && E.LocalName == "tokenChallengeResponse" && E.InnerText == Rec2.Response)
488 {
489 this.challengedTokens.Add(e.From + " " + Rec2.Token, true);
490
491 lock (Rec.TokensToChallenge)
492 {
493 Rec.TokensToChallenge[Rec2.Token] = true;
494 Rec.NrTokensToChallenge--;
495 Done = Rec.NrTokensToChallenge == 0;
496 }
497 }
498 else
499 {
500 lock (Rec.TokensToChallenge)
501 {
502 Rec.AllOk = false;
503 Rec.NrTokensToChallenge--;
504 Done = Rec.NrTokensToChallenge == 0;
505 }
506 }
507
508 if (Done)
509 {
510 if (Rec.Read)
511 {
512 if (!Rec.AllOk)
513 Rec.Allowed = false;
514
515 await this.TestAndSendCanReadResponse(Rec.Jid, Rec.e, Rec.Allowed, Rec.FieldTypes, Rec.Nodes, Rec.Fields,
516 Rec.ServiceTokens, Rec.DeviceTokens, Rec.UserTokens, Rec.QueryVersion);
517 }
518 else
519 {
520 if (Rec.AllOk)
521 Rec.Allowed = false;
522
523 await this.TestAndSendCanControlResponse(Rec.Jid, Rec.e, Rec.Allowed, Rec.Nodes, Rec.Fields,
524 Rec.ServiceTokens, Rec.DeviceTokens, Rec.UserTokens, Rec.QueryVersion);
525 }
526 }
527 }
528
529 private void AddValidTokens(Dictionary<string, bool> TokensToChallenge, string[] Tokens, string From)
530 {
531 foreach (string Token in Tokens)
532 {
533 if (!TokensToChallenge.ContainsKey(Token) && !this.challengedTokens.ContainsKey(From + " " + Token))
534 {
535 lock (this.certificates)
536 {
537 if (!this.certificates.ContainsKey(Token))
538 continue;
539 }
540
541 TokensToChallenge[Token] = false;
542 }
543 }
544 }
545
546 private Task GetCertificateHandler(object Sender, IqEventArgs e)
547 {
548 KeyValuePair<byte[], X509Certificate2> Rec;
549 string Token = XML.Attribute(e.Query, "token");
550 bool Found;
551 int i;
552
553 i = Token.IndexOf(':');
554 if (i > 0)
555 {
556 string Address = Token.Substring(0, i);
557 Token = Token.Substring(i + 1);
558
559 if (this.IsComponentDomain(Address, true))
560 {
561 e.IqErrorItemNotFound(e.To, "Token not found.", "en");
562 return Task.CompletedTask;
563 }
564 }
565
566 lock (this.certificates)
567 {
568 Found = this.certificates.TryGetValue(Token, out Rec);
569 }
570
571 if (!Found)
572 e.IqErrorItemNotFound(e.To, "Token not found.", "en");
573 else
574 e.IqResult("<certificate xmlns='" + e.Query.NamespaceURI + "'>" + Convert.ToBase64String(Rec.Key) + "</certificate>", e.To);
575
576 return Task.CompletedTask;
577 }
578
579 #endregion
580
581 #region Provisioning, Device interface
582
583 private async Task IsFriendHandler(object Sender, IqEventArgs e)
584 {
586 CaseInsensitiveString RemoteJID = XML.Attribute(e.Query, "jid");
587 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
588 bool AreFrieds;
589
590 Registration Registration = await this.GetRegistration(JID, string.Empty, string.Empty, string.Empty, QueryVersion, null);
591 if (Registration is null || string.IsNullOrEmpty(Registration.Owner))
592 AreFrieds = false;
593 else
594 {
595 FriendshipRule Rule = await Database.FindFirstDeleteRest<FriendshipRule>(new FilterAnd(
596 new FilterFieldEqualTo("JID", JID), new FilterFieldEqualTo("RemoteJID", RemoteJID)));
597
598 if (!(Rule is null))
599 AreFrieds = Rule.CanSubscribeToPresence;
600 else
601 {
602 string RemoteDomain = new XmppAddress(RemoteJID).Domain;
603
604 Rule = await Database.FindFirstDeleteRest<FriendshipRule>(new FilterAnd(
605 new FilterFieldEqualTo("JID", JID), new FilterFieldEqualTo("RemoteJID", RemoteDomain)));
606
607 if (!(Rule is null))
608 AreFrieds = Rule.CanSubscribeToPresence;
609 else
610 {
611 Rule = await Database.FindFirstDeleteRest<FriendshipRule>(new FilterAnd(
612 new FilterFieldEqualTo("JID", JID), new FilterFieldEqualTo("RemoteJID", string.Empty)));
613
614 if (!(Rule is null))
615 AreFrieds = Rule.CanSubscribeToPresence;
616 else
617 {
618 AreFrieds = false;
619
620 Rule = new FriendshipRule()
621 {
622 JID = JID,
623 RemoteJID = RemoteJID,
624 CanSubscribeToPresence = false
625 };
626
627 await Database.InsertLazy(Rule);
628
629 StringBuilder Xml = new StringBuilder();
630
631 Xml.Append("<isFriend xmlns='");
632 Xml.Append(NamespaceProvisioningOwner(Registration.OwnerVersion));
633 Xml.Append("' jid='");
634 Xml.Append(XML.Encode(JID));
635 Xml.Append("' remoteJid='");
636 Xml.Append(XML.Encode(RemoteJID));
637 Xml.Append("' key='");
638 Xml.Append(XML.Encode(Rule.ObjectId.ToString()));
639 Xml.Append("'/>");
640
641 await this.Server.SendMessage(string.Empty, string.Empty, e.To, new XmppAddress(Registration.Owner), string.Empty, Xml.ToString());
642 }
643 }
644 }
645 }
646
647 await e.IqResult("<isFriendResponse xmlns='" + e.Query.NamespaceURI + "' jid='" + XML.Encode(RemoteJID) + "' result='" +
648 CommonTypes.Encode(AreFrieds) + "'/>", e.To);
649 }
650
651 private async Task CanReadHandler(object Sender, IqEventArgs e)
652 {
653 List<ThingReference> Nodes = null;
654 List<string> Fields = null;
655 XmlElement E = e.Query;
656 FieldType FieldTypes = 0;
657 string ServiceToken = string.Empty;
658 string DeviceToken = string.Empty;
659 string UserToken = string.Empty;
661 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
662 bool b;
663
664 foreach (XmlAttribute Attr in E.Attributes)
665 {
666 switch (Attr.Name)
667 {
668 case "st":
669 ServiceToken = Attr.Value;
670 break;
671
672 case "dt":
673 DeviceToken = Attr.Value;
674 break;
675
676 case "ut":
677 UserToken = Attr.Value;
678 break;
679
680 case "jid":
681 Jid = Attr.Value;
682 break;
683
684 case "all":
685 if (CommonTypes.TryParse(Attr.Value, out b) && b)
686 FieldTypes |= FieldType.All;
687 break;
688
689 case "h":
690 if (CommonTypes.TryParse(Attr.Value, out b) && b)
691 FieldTypes |= FieldType.Historical;
692 break;
693
694 case "m":
695 if (CommonTypes.TryParse(Attr.Value, out b) && b)
696 FieldTypes |= FieldType.Momentary;
697 break;
698
699 case "p":
700 if (CommonTypes.TryParse(Attr.Value, out b) && b)
701 FieldTypes |= FieldType.Peak;
702 break;
703
704 case "s":
705 if (CommonTypes.TryParse(Attr.Value, out b) && b)
706 FieldTypes |= FieldType.Status;
707 break;
708
709 case "c":
710 if (CommonTypes.TryParse(Attr.Value, out b) && b)
711 FieldTypes |= FieldType.Computed;
712 break;
713
714 case "i":
715 if (CommonTypes.TryParse(Attr.Value, out b) && b)
716 FieldTypes |= FieldType.Identity;
717 break;
718 }
719 }
720
721 foreach (XmlNode N in E.ChildNodes)
722 {
723 switch (N.LocalName)
724 {
725 case "nd":
726 if (Nodes is null)
727 Nodes = new List<ThingReference>();
728
729 E = (XmlElement)N;
730 Nodes.Add(this.ParseNodeInfo(E));
731 break;
732
733 case "f":
734 if (Fields is null)
735 Fields = new List<string>();
736
737 Fields.Add(XML.Attribute((XmlElement)N, "n"));
738 break;
739 }
740 }
741
742 string[] ServiceTokens = ServiceToken.Split(space, StringSplitOptions.RemoveEmptyEntries);
743 string[] DeviceTokens = DeviceToken.Split(space, StringSplitOptions.RemoveEmptyEntries);
744 string[] UserTokens = UserToken.Split(space, StringSplitOptions.RemoveEmptyEntries);
745 Dictionary<string, bool> TokensToChallenge = null;
746 int NrTokensToChallenge;
747
748 if (ServiceTokens.Length == 0 && DeviceTokens.Length == 0 && UserTokens.Length == 0)
749 NrTokensToChallenge = 0;
750 else
751 {
752 TokensToChallenge = new Dictionary<string, bool>();
753
754 this.AddValidTokens(TokensToChallenge, ServiceTokens, e.From.Address);
755 this.AddValidTokens(TokensToChallenge, DeviceTokens, e.From.Address);
756 this.AddValidTokens(TokensToChallenge, UserTokens, e.From.Address);
757
758 NrTokensToChallenge = TokensToChallenge.Count;
759 if (NrTokensToChallenge > 0)
760 {
761 TokenChallengesRecord Rec = new TokenChallengesRecord()
762 {
763 TokensToChallenge = TokensToChallenge,
764 Nodes = Nodes,
765 Fields = Fields,
766 FieldTypes = FieldTypes,
767 Jid = Jid,
768 ServiceTokens = ServiceTokens,
769 DeviceTokens = DeviceTokens,
770 UserTokens = UserTokens,
771 e = e,
772 NrTokensToChallenge = NrTokensToChallenge,
773 Allowed = true,
774 Read = true,
775 QueryVersion = QueryVersion
776 };
777
778 string[] Tokens = new string[TokensToChallenge.Count];
779 TokensToChallenge.Keys.CopyTo(Tokens, 0);
780
781 foreach (string Token in Tokens)
782 {
783 KeyValuePair<byte[], X509Certificate2> Certificate;
784
785 lock (this.certificates)
786 {
787 Certificate = this.certificates[Token];
788 }
789
790 byte[] Bin = XmppServer.GetRandomNumbers(64);
791 string Response = Convert.ToBase64String(Bin);
792 Bin = ((RSACryptoServiceProvider)Certificate.Value.PublicKey.Key).Encrypt(Bin, true);
793 string Challenge = Convert.ToBase64String(Bin);
794
795 TokenChallengeRecord Rec2 = new TokenChallengeRecord()
796 {
797 Challenges = Rec,
798 Token = Token,
799 Response = Response
800 };
801
802 await this.Server.SendIqRequest("get", e.To, e.From, string.Empty,
803 "<tokenChallenge xmlns='" + NamespaceProvisioningToken(QueryVersion) + "' token='" +
804 XML.Encode(Token) + "'>" + Challenge + "</tokenChallenge>", this.TokenChallengeResponse, Rec2);
805 }
806
807 return;
808 }
809 }
810
811 await this.TestAndSendCanReadResponse(Jid, e, true, FieldTypes, Nodes, Fields, ServiceTokens, DeviceTokens, UserTokens, QueryVersion);
812 }
813
814 private async Task TestAndSendCanReadResponse(CaseInsensitiveString Jid, IqEventArgs e, bool Allowed, FieldType FieldTypes,
815 List<ThingReference> Nodes, List<string> Fields, string[] ServiceTokens, string[] DeviceTokens, string[] UserTokens,
816 NamespaceSet QueryVersion)
817 {
818 if (Allowed)
819 {
821 CaseInsensitiveString RemoteJID = Jid;
822
823 List<ThingReference> NodesAllowed = null;
824 CaseInsensitiveString RemoteDomain = GetDomain(RemoteJID);
825
826 if (Nodes is null || Nodes.Count == 0)
827 Nodes = new List<ThingReference>() { ThingReference.Empty };
828
829 Context Context = new Context()
830 {
831 RemoteJid = RemoteJID,
832 RemoteDomain = RemoteDomain,
833 Types = FieldTypes,
834 ServiceTokens = ServiceTokens,
835 DeviceTokens = DeviceTokens,
836 UserTokens = UserTokens
837 };
838
839 if (!(Fields is null))
840 {
841 Context.Names = new Dictionary<string, bool>();
842
843 foreach (string Field in Fields)
844 Context.Names[Field] = true;
845 }
846
847 foreach (ThingReference Node in Nodes)
848 {
849 Registration Registration = await this.GetRegistration(JID, Node.NodeId, Node.SourceId, Node.Partition, QueryVersion, null);
850 if (Registration is null || string.IsNullOrEmpty(Registration.Owner))
851 continue;
852
853 ReadoutRule Rule = await Database.FindFirstDeleteRest<ReadoutRule>(new FilterAnd(
854 new FilterFieldEqualTo("JID", JID),
855 new FilterFieldEqualTo("NodeID", Node.NodeId),
856 new FilterFieldEqualTo("SourceID", Node.SourceId),
857 new FilterFieldEqualTo("Partition", Node.Partition)));
858
859 if (Rule is null)
860 {
861 Rule = new ReadoutRule()
862 {
863 JID = JID,
864 NodeID = Node.NodeId,
865 SourceID = Node.SourceId,
866 Partition = Node.Partition
867 };
868
869 await Database.InsertLazy(Rule);
870 }
871
872 bool? Result = null;
873
874 if (!(Rule.Rules is null))
875 {
876 foreach (Rule Rule2 in Rule.Rules)
877 {
878 Result = Rule2.Evaluate(Context);
879 if (Result.HasValue)
880 break;
881 }
882 }
883
884 if (Result.HasValue)
885 {
886 if (Result.Value)
887 {
888 if (NodesAllowed is null)
889 NodesAllowed = new List<ThingReference>();
890
891 NodesAllowed.Add(Node);
892 }
893
894 continue;
895 }
896
897 StringBuilder Xml = new StringBuilder();
898
899 Xml.Append("<canRead xmlns='");
900 Xml.Append(NamespaceProvisioningOwner(Registration.OwnerVersion));
901 Xml.Append("' jid='");
902 Xml.Append(XML.Encode(JID));
903 Xml.Append("' remoteJid='");
904 Xml.Append(XML.Encode(RemoteJID));
905 Xml.Append("' key='");
906 Xml.Append(XML.Encode(Rule.ObjectId.ToString()));
907
908 if (FieldTypes == FieldType.All)
909 Xml.Append("' all='true");
910 else
911 {
912 if ((FieldTypes & FieldType.Historical) != 0)
913 Xml.Append("' h='true");
914
915 if ((FieldTypes & FieldType.Momentary) != 0)
916 Xml.Append("' m='true");
917
918 if ((FieldTypes & FieldType.Peak) != 0)
919 Xml.Append("' p='true");
920
921 if ((FieldTypes & FieldType.Status) != 0)
922 Xml.Append("' s='true");
923
924 if ((FieldTypes & FieldType.Computed) != 0)
925 Xml.Append("' c='true");
926
927 if ((FieldTypes & FieldType.Identity) != 0)
928 Xml.Append("' i='true");
929 }
930
931 this.AppendTokens(Xml, "st", ServiceTokens);
932 this.AppendTokens(Xml, "dt", DeviceTokens);
933 this.AppendTokens(Xml, "ut", UserTokens);
934
935 Xml.Append("'>");
936
937 if (!Node.IsEmpty)
938 this.AppendNode(Xml, Node);
939
940 if (!(Context.Names is null))
941 {
942 foreach (string FieldName in Context.Names.Keys)
943 {
944 Xml.Append("<f n='");
945 Xml.Append(XML.Encode(FieldName));
946 Xml.Append("'/>");
947 }
948 }
949
950 Xml.Append("</canRead>");
951
952 await this.Server.SendMessage(string.Empty, string.Empty, e.To, new XmppAddress(Registration.Owner), string.Empty, Xml.ToString());
953 }
954
955 await this.SendCanReadResponse(Jid, e, !(NodesAllowed is null), Context.Types, NodesAllowed, Context?.Names?.Keys, QueryVersion);
956 }
957 else
958 await this.SendCanReadResponse(Jid, e, false, 0, null, null, QueryVersion);
959 }
960
961 private void AppendTokens(StringBuilder Xml, string Attribute, string[] Tokens)
962 {
963 if (!(Tokens is null) && Tokens.Length > 0)
964 {
965 bool First = true;
966
967 Xml.Append("' ");
968 Xml.Append(Attribute);
969 Xml.Append("='");
970
971 foreach (string Token in Tokens)
972 {
973 if (First)
974 First = false;
975 else
976 Xml.Append(' ');
977
978 Xml.Append(XML.Encode(Token));
979 }
980 }
981 }
982
983 private Task SendCanReadResponse(CaseInsensitiveString Jid, IqEventArgs e, bool Allowed, FieldType FieldTypes,
984 List<ThingReference> Nodes, IEnumerable<string> Fields, NamespaceSet Version)
985 {
986 StringBuilder Xml = new StringBuilder();
987
988 Xml.Append("<canReadResponse xmlns='");
989 Xml.Append(NamespaceProvisioningOwner(Version));
990 Xml.Append("' result='");
991 Xml.Append(CommonTypes.Encode(Allowed));
992 Xml.Append("' jid='");
993 Xml.Append(XML.Encode(Jid));
994
995 if (Allowed)
996 {
997 if ((FieldTypes & FieldType.All) == FieldType.All)
998 Xml.Append("' all='true");
999 else
1000 {
1001 if (FieldTypes.HasFlag(FieldType.Momentary))
1002 Xml.Append("' m='true");
1003
1004 if (FieldTypes.HasFlag(FieldType.Peak))
1005 Xml.Append("' p='true");
1006
1007 if (FieldTypes.HasFlag(FieldType.Status))
1008 Xml.Append("' s='true");
1009
1010 if (FieldTypes.HasFlag(FieldType.Computed))
1011 Xml.Append("' c='true");
1012
1013 if (FieldTypes.HasFlag(FieldType.Identity))
1014 Xml.Append("' i='true");
1015
1016 if (FieldTypes.HasFlag(FieldType.Historical))
1017 Xml.Append("' h='true");
1018 }
1019
1020 if (Nodes is null && Fields is null)
1021 Xml.Append("'/>");
1022 else
1023 {
1024 Xml.Append("'>");
1025
1026 if (!(Nodes is null) && (Nodes.Count != 1 || !Nodes[0].IsEmpty))
1027 {
1028 foreach (ThingReference Node in Nodes)
1029 this.AppendNode(Xml, Node);
1030 }
1031
1032 if (!(Fields is null))
1033 {
1034 foreach (string FieldName in Fields)
1035 {
1036 Xml.Append("<f n='");
1037 Xml.Append(XML.Encode(FieldName));
1038 Xml.Append("'/>");
1039 }
1040 }
1041
1042 Xml.Append("</canReadResponse>");
1043 }
1044 }
1045 else
1046 Xml.Append("'/>");
1047
1048 return e.IqResult(Xml.ToString(), e.To);
1049 }
1050
1051 private void AppendNode(StringBuilder Xml, ThingReference Node)
1052 {
1053 Xml.Append("<nd id='");
1054 Xml.Append(XML.Encode(Node.NodeId));
1055
1056 if (!string.IsNullOrEmpty(Node.SourceId))
1057 {
1058 Xml.Append("' src='");
1059 Xml.Append(XML.Encode(Node.SourceId));
1060 }
1061
1062 if (!string.IsNullOrEmpty(Node.Partition))
1063 {
1064 Xml.Append("' pt='");
1065 Xml.Append(XML.Encode(Node.Partition));
1066 }
1067
1068 Xml.Append("'/>");
1069 }
1070
1071 private static readonly char[] space = new char[] { ' ' };
1072
1079 public bool TryGetCertificate(string Token, out X509Certificate2 Certificate)
1080 {
1081 lock (this.certificates)
1082 {
1083 if (this.certificates.TryGetValue(Token, out KeyValuePair<byte[], X509Certificate2> Rec))
1084 {
1085 Certificate = Rec.Value;
1086 return true;
1087 }
1088 else
1089 {
1090 Certificate = null;
1091 return false;
1092 }
1093 }
1094 }
1095
1096 private async Task CanControlHandler(object Sender, IqEventArgs e)
1097 {
1098 List<ThingReference> Nodes = null;
1099 List<string> ParameterNames = null;
1100 XmlElement E = e.Query;
1101 string ServiceToken = string.Empty;
1102 string DeviceToken = string.Empty;
1103 string UserToken = string.Empty;
1105 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
1106
1107 foreach (XmlAttribute Attr in E.Attributes)
1108 {
1109 switch (Attr.Name)
1110 {
1111 case "st":
1112 ServiceToken = Attr.Value;
1113 break;
1114
1115 case "dt":
1116 DeviceToken = Attr.Value;
1117 break;
1118
1119 case "ut":
1120 UserToken = Attr.Value;
1121 break;
1122
1123 case "jid":
1124 Jid = Attr.Value;
1125 break;
1126 }
1127 }
1128
1129 foreach (XmlNode N in E.ChildNodes)
1130 {
1131 switch (N.LocalName)
1132 {
1133 case "nd":
1134 if (Nodes is null)
1135 Nodes = new List<ThingReference>();
1136
1137 E = (XmlElement)N;
1138 Nodes.Add(this.ParseNodeInfo(E));
1139 break;
1140
1141 case "p":
1142 if (ParameterNames is null)
1143 ParameterNames = new List<string>();
1144
1145 ParameterNames.Add(XML.Attribute((XmlElement)N, "n"));
1146 break;
1147 }
1148 }
1149
1150 string[] ServiceTokens = ServiceToken.Split(space, StringSplitOptions.RemoveEmptyEntries);
1151 string[] DeviceTokens = DeviceToken.Split(space, StringSplitOptions.RemoveEmptyEntries);
1152 string[] UserTokens = UserToken.Split(space, StringSplitOptions.RemoveEmptyEntries);
1153 Dictionary<string, bool> TokensToChallenge = null;
1154 int NrTokensToChallenge;
1155
1156 if (ServiceTokens.Length == 0 && DeviceTokens.Length == 0 && UserTokens.Length == 0)
1157 NrTokensToChallenge = 0;
1158 else
1159 {
1160 TokensToChallenge = new Dictionary<string, bool>();
1161
1162 this.AddValidTokens(TokensToChallenge, ServiceTokens, e.From.Address);
1163 this.AddValidTokens(TokensToChallenge, DeviceTokens, e.From.Address);
1164 this.AddValidTokens(TokensToChallenge, UserTokens, e.From.Address);
1165
1166 NrTokensToChallenge = TokensToChallenge.Count;
1167 if (NrTokensToChallenge > 0)
1168 {
1169 TokenChallengesRecord Rec = new TokenChallengesRecord()
1170 {
1171 TokensToChallenge = TokensToChallenge,
1172 Nodes = Nodes,
1173 Fields = ParameterNames,
1174 Jid = Jid,
1175 ServiceTokens = ServiceTokens,
1176 DeviceTokens = DeviceTokens,
1177 UserTokens = UserTokens,
1178 e = e,
1179 NrTokensToChallenge = NrTokensToChallenge,
1180 Allowed = true,
1181 Read = false,
1182 QueryVersion = QueryVersion
1183 };
1184
1185 string[] Tokens = new string[TokensToChallenge.Count];
1186 TokensToChallenge.Keys.CopyTo(Tokens, 0);
1187
1188 foreach (string Token in Tokens)
1189 {
1190 KeyValuePair<byte[], X509Certificate2> Certificate;
1191
1192 lock (this.certificates)
1193 {
1194 Certificate = this.certificates[Token];
1195 }
1196
1197 byte[] Bin = XmppServer.GetRandomNumbers(64);
1198 string Response = System.Convert.ToBase64String(Bin);
1199 Bin = ((RSACryptoServiceProvider)Certificate.Value.PublicKey.Key).Encrypt(Bin, true);
1200 string Challenge = System.Convert.ToBase64String(Bin);
1201
1202 TokenChallengeRecord Rec2 = new TokenChallengeRecord()
1203 {
1204 Challenges = Rec,
1205 Token = Token,
1206 Response = Response
1207 };
1208
1209 await this.Server.SendIqRequest("get", e.To, e.From, string.Empty,
1210 "<tokenChallenge xmlns='" + NamespaceProvisioningToken(QueryVersion) + "' token='" +
1211 XML.Encode(Token) + "'>" + Challenge + "</tokenChallenge>", this.TokenChallengeResponse, Rec2);
1212 }
1213
1214 return;
1215 }
1216 }
1217
1218 await this.TestAndSendCanControlResponse(Jid, e, true, Nodes, ParameterNames, ServiceTokens, DeviceTokens, UserTokens, QueryVersion);
1219 }
1220
1221 public static string GetDomain(CaseInsensitiveString Jid)
1222 {
1223 CaseInsensitiveString Domain;
1224 int i = Jid.IndexOf('@');
1225 if (i < 0)
1226 Domain = Jid;
1227 else
1228 Domain = Jid.Substring(i + 1);
1229
1230 i = Domain.IndexOf('/');
1231 if (i > 0)
1232 Domain = Domain.Substring(0, i);
1233
1234 return Domain;
1235 }
1236
1237 private async Task TestAndSendCanControlResponse(CaseInsensitiveString Jid, IqEventArgs e, bool Allowed, List<ThingReference> Nodes,
1238 List<string> ParameterNames, string[] ServiceTokens, string[] DeviceTokens, string[] UserTokens, NamespaceSet QueryVersion)
1239 {
1240 if (Allowed)
1241 {
1243 CaseInsensitiveString RemoteJID = Jid;
1244
1245 List<ThingReference> NodesAllowed = null;
1246 CaseInsensitiveString RemoteDomain = GetDomain(RemoteJID);
1247
1248 if (Nodes is null || Nodes.Count == 0)
1249 Nodes = new List<ThingReference>() { ThingReference.Empty };
1250
1251 Context Context = new Context()
1252 {
1253 RemoteJid = RemoteJID,
1254 RemoteDomain = RemoteDomain,
1255 ServiceTokens = ServiceTokens,
1256 DeviceTokens = DeviceTokens,
1257 UserTokens = UserTokens
1258 };
1259
1260 if (!(ParameterNames is null))
1261 {
1262 Context.Names = new Dictionary<string, bool>();
1263
1264 foreach (string ParameterName in ParameterNames)
1265 Context.Names[ParameterName] = true;
1266 }
1267
1268 foreach (ThingReference Node in Nodes)
1269 {
1270 Registration Registration = await this.GetRegistration(JID, Node.NodeId, Node.SourceId, Node.Partition, QueryVersion, null);
1271 if (Registration is null || string.IsNullOrEmpty(Registration.Owner))
1272 continue;
1273
1274 ControlRule Rule = await Database.FindFirstDeleteRest<ControlRule>(new FilterAnd(
1275 new FilterFieldEqualTo("JID", JID),
1276 new FilterFieldEqualTo("NodeID", Node.NodeId),
1277 new FilterFieldEqualTo("SourceID", Node.SourceId),
1278 new FilterFieldEqualTo("Partition", Node.Partition)));
1279
1280 if (Rule is null)
1281 {
1282 Rule = new ControlRule()
1283 {
1284 JID = JID,
1285 NodeID = Node.NodeId,
1286 SourceID = Node.SourceId,
1287 Partition = Node.Partition
1288 };
1289
1290 await Database.InsertLazy(Rule);
1291 }
1292
1293 bool? Result = null;
1294
1295 if (!(Rule.Rules is null))
1296 {
1297 foreach (Rule Rule2 in Rule.Rules)
1298 {
1299 Result = Rule2.Evaluate(Context);
1300 if (Result.HasValue)
1301 break;
1302 }
1303 }
1304
1305 if (Result.HasValue)
1306 {
1307 if (Result.Value)
1308 {
1309 if (NodesAllowed is null)
1310 NodesAllowed = new List<ThingReference>();
1311
1312 NodesAllowed.Add(Node);
1313 }
1314
1315 continue;
1316 }
1317
1318 StringBuilder Xml = new StringBuilder();
1319
1320 Xml.Append("<canControl xmlns='");
1321 Xml.Append(NamespaceProvisioningOwner(QueryVersion));
1322 Xml.Append("' jid='");
1323 Xml.Append(XML.Encode(JID));
1324 Xml.Append("' remoteJid='");
1325 Xml.Append(XML.Encode(RemoteJID));
1326 Xml.Append("' key='");
1327 Xml.Append(XML.Encode(Rule.ObjectId.ToString()));
1328
1329 this.AppendTokens(Xml, "st", ServiceTokens);
1330 this.AppendTokens(Xml, "dt", DeviceTokens);
1331 this.AppendTokens(Xml, "ut", UserTokens);
1332
1333 Xml.Append("'>");
1334
1335 if (!Node.IsEmpty)
1336 this.AppendNode(Xml, Node);
1337
1338 if (!(Context.Names is null))
1339 {
1340 foreach (string FieldName in Context.Names.Keys)
1341 {
1342 Xml.Append("<p n='");
1343 Xml.Append(XML.Encode(FieldName));
1344 Xml.Append("'/>");
1345 }
1346 }
1347
1348 Xml.Append("</canControl>");
1349
1350 await this.Server.SendMessage(string.Empty, string.Empty, e.To, new XmppAddress(Registration.Owner), string.Empty, Xml.ToString());
1351 }
1352
1353 await this.SendCanControlResponse(Jid, e, !(NodesAllowed is null), NodesAllowed, Context?.Names?.Keys);
1354 }
1355 else
1356 await this.SendCanControlResponse(Jid, e, false, null, null);
1357 }
1358
1359 private Task SendCanControlResponse(CaseInsensitiveString Jid, IqEventArgs e, bool Allowed, List<ThingReference> Nodes,
1360 IEnumerable<string> ParameterNames)
1361 {
1362 StringBuilder Xml = new StringBuilder();
1363
1364 Xml.Append("<canControlResponse xmlns='");
1365 Xml.Append(e.Query.NamespaceURI);
1366 Xml.Append("' result='");
1367 Xml.Append(CommonTypes.Encode(Allowed));
1368 Xml.Append("' jid='");
1369 Xml.Append(XML.Encode(Jid));
1370
1371 if (Allowed)
1372 {
1373 if (Nodes is null && ParameterNames is null)
1374 Xml.Append("'/>");
1375 else
1376 {
1377 Xml.Append("'>");
1378
1379 if (!(Nodes is null))
1380 {
1381 foreach (ThingReference Node in Nodes)
1382 this.AppendNode(Xml, Node);
1383 }
1384
1385 if (!(ParameterNames is null))
1386 {
1387 foreach (string FieldName in ParameterNames)
1388 {
1389 Xml.Append("<p n='");
1390 Xml.Append(XML.Encode(FieldName));
1391 Xml.Append("'/>");
1392 }
1393 }
1394
1395 Xml.Append("</canControlResponse>");
1396 }
1397 }
1398 else
1399 Xml.Append("'/>");
1400
1401 return e.IqResult(Xml.ToString(), e.To);
1402 }
1403
1404 #endregion
1405
1406 #region Provisioning, Owner interface
1407
1408 private async Task IsFriendRuleHandler(object Sender, IqEventArgs e)
1409 {
1410 CaseInsensitiveString OwnerJid = e.From.BareJid;
1411 CaseInsensitiveString JID = XML.Attribute(e.Query, "jid");
1412 CaseInsensitiveString RemoteJID = XML.Attribute(e.Query, "remoteJid");
1413 string Key = XML.Attribute(e.Query, "key");
1414 bool AreFrieds = XML.Attribute(e.Query, "result", false);
1415 RuleRange Range = XML.Attribute(e.Query, "range", RuleRange.Caller);
1416 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
1418
1419 if (!Guid.TryParse(Key, out Guid ObjectId))
1420 {
1421 await e.IqErrorBadRequest(e.To, "Invalid key.", "en");
1422 return;
1423 }
1424
1425 Rule = await Database.TryLoadObject<FriendshipRule>(ObjectId);
1426 if (Rule is null)
1427 {
1428 await e.IqErrorItemNotFound(e.To, "Key not found.", "en");
1429 return;
1430 }
1431
1432 if (Rule.JID != JID || Rule.RemoteJID != RemoteJID)
1433 {
1434 await e.IqErrorBadRequest(e.To, "Parameters do not match.", "en");
1435 return;
1436 }
1437
1438 Registration Registration = await this.GetRegistration(JID, string.Empty, string.Empty, string.Empty, null, QueryVersion);
1439
1440 if (Registration is null || Registration.Owner != OwnerJid)
1441 {
1442 await e.IqErrorForbidden(e.To, "Access denied. Owner mismatch.", "en");
1443 return;
1444 }
1445
1446 Log.Informational("Presence subscription rule changed.", JID, OwnerJid, "SubscriptionRule",
1447 new KeyValuePair<string, object>("RemoteJID", RemoteJID),
1448 new KeyValuePair<string, object>("Allowed", AreFrieds));
1449
1450 switch (Range)
1451 {
1452 case RuleRange.Caller:
1453 if (Rule.CanSubscribeToPresence != AreFrieds)
1454 {
1455 Rule.CanSubscribeToPresence = AreFrieds;
1456 await Database.UpdateLazy(Rule);
1457 }
1458 break;
1459
1460 case RuleRange.Domain:
1461 await Database.DeleteLazy(Rule);
1462
1463 string RemoteDomain = new XmppAddress(RemoteJID).Domain;
1464
1465 Rule = await Database.FindFirstDeleteRest<FriendshipRule>(new FilterAnd(
1466 new FilterFieldEqualTo("JID", JID), new FilterFieldEqualTo("RemoteJID", RemoteDomain)));
1467
1468 if (!(Rule is null))
1469 {
1470 if (Rule.CanSubscribeToPresence != AreFrieds)
1471 {
1472 Rule.CanSubscribeToPresence = AreFrieds;
1473 await Database.UpdateLazy(Rule);
1474 }
1475 }
1476 else
1477 {
1478 Rule = new FriendshipRule()
1479 {
1480 JID = JID,
1481 RemoteJID = RemoteDomain,
1482 CanSubscribeToPresence = AreFrieds
1483 };
1484
1485 await Database.InsertLazy(Rule);
1486 }
1487 break;
1488
1489 case RuleRange.All:
1490 await Database.DeleteLazy(Rule);
1491
1492 Rule = await Database.FindFirstDeleteRest<FriendshipRule>(new FilterAnd(
1493 new FilterFieldEqualTo("JID", JID), new FilterFieldEqualTo("RemoteJID", string.Empty)));
1494
1495 if (!(Rule is null))
1496 {
1497 if (Rule.CanSubscribeToPresence != AreFrieds)
1498 {
1499 Rule.CanSubscribeToPresence = AreFrieds;
1500 await Database.UpdateLazy(Rule);
1501 }
1502 }
1503 else
1504 {
1505 Rule = new FriendshipRule()
1506 {
1507 JID = JID,
1508 RemoteJID = string.Empty,
1509 CanSubscribeToPresence = AreFrieds
1510 };
1511
1512 await Database.InsertLazy(Rule);
1513 }
1514 break;
1515
1516 default:
1517 await e.IqErrorBadRequest(e.To, "Invalid range.", "en");
1518 return;
1519 }
1520
1521 await e.IqResult(string.Empty, e.To);
1522
1523 await this.ClearCache(JID, Registration.Version);
1524
1525 if (AreFrieds)
1526 {
1527 Gateway.ScheduleEvent(async (P) =>
1528 {
1529 try
1530 {
1531 await this.RecommendBefriend(RemoteJID, JID, QueryVersion);
1532 }
1533 catch (Exception ex)
1534 {
1535 Log.Exception(ex);
1536 }
1537 }, DateTime.Now.AddSeconds(5), null); // Allows the client some time to prepare for the presence subscription request.
1538 }
1539 }
1540
1549 NamespaceSet Version)
1550 {
1551 await this.Server.GetLastPresence(BareJid1, async (Sender, e) =>
1552 {
1553 try
1554 {
1555 if (e.Ok)
1556 {
1557 await this.Server.SendMessage(string.Empty, string.Empty, this.GetComponentAddress(e.To), e.From, string.Empty,
1558 "<unfriend xmlns='" + NamespaceProvisioningDevice(Version) + "' jid='" + BareJid2 + "'/>");
1559 }
1560 }
1561 catch (Exception ex)
1562 {
1563 Log.Exception(ex);
1564 }
1565 }, null);
1566 }
1567
1568 private XmppAddress GetComponentAddress(XmppAddress ServerAddress)
1569 {
1570 if (ServerAddress.IsEmpty)
1571 return this.MainDomain;
1572 else
1573 return new XmppAddress(this.SubdomainSuffixed + ServerAddress.Address);
1574 }
1575
1581 public async Task ClearCache(CaseInsensitiveString BareJid, NamespaceSet Version)
1582 {
1583 await this.Server.GetLastPresence(BareJid, async (Sender, e) =>
1584 {
1585 try
1586 {
1587 string Xml = "<clearCache xmlns='" + NamespaceProvisioningDevice(Version) + "'/>";
1588 XmppAddress From = this.GetComponentAddress(e.To);
1589
1590 if (e.Ok)
1591 {
1592 await this.Server.SendIqRequest("set", From, e.From, string.Empty, Xml, (sender2, e2) =>
1593 {
1594 if (!e.Ok)
1595 return this.Server.SendMessage(string.Empty, string.Empty, From, new XmppAddress(BareJid), string.Empty, Xml);
1596
1597 return Task.CompletedTask;
1598 }, null);
1599 }
1600 else
1601 await this.Server.SendMessage(string.Empty, string.Empty, From, new XmppAddress(BareJid), string.Empty, Xml);
1602 }
1603 catch (Exception ex)
1604 {
1605 Log.Exception(ex);
1606 }
1607 }, null);
1608 }
1609
1618 NamespaceSet Version)
1619 {
1620 await this.Server.GetLastPresence(BareJid1, async (Sender, e) =>
1621 {
1622 try
1623 {
1624 if (e.Ok)
1625 {
1626 await this.Server.SendMessage(string.Empty, string.Empty, this.GetComponentAddress(e.To), e.From, string.Empty,
1627 "<friend xmlns='" + NamespaceProvisioningDevice(Version) + "' jid='" + BareJid2 + "'/>");
1628 }
1629 }
1630 catch (Exception ex)
1631 {
1632 Log.Exception(ex);
1633 }
1634 }, null);
1635 }
1636
1637 private async Task CanReadRuleHandler(object Sender, IqEventArgs e)
1638 {
1639 CaseInsensitiveString OwnerJid = e.From.BareJid;
1640 CaseInsensitiveString JID = XML.Attribute(e.Query, "jid");
1641 CaseInsensitiveString RemoteJID = XML.Attribute(e.Query, "remoteJid");
1642 string Key = XML.Attribute(e.Query, "key");
1643 bool CanRead = XML.Attribute(e.Query, "result", false);
1645
1646 if (!Guid.TryParse(Key, out Guid ObjectId))
1647 {
1648 await e.IqErrorBadRequest(e.To, "Invalid key.", "en");
1649 return;
1650 }
1651
1652 Rule = await Database.TryLoadObject<ReadoutRule>(ObjectId);
1653 if (Rule is null)
1654 {
1655 await e.IqErrorItemNotFound(e.To, "Key not found.", "en");
1656 return;
1657 }
1658
1659 Rule Condition = null;
1661 Partial Partial = null;
1662
1663 foreach (XmlNode N in e.Query.ChildNodes)
1664 {
1665 if (N is XmlElement E)
1666 {
1667 switch (E.LocalName)
1668 {
1669 case "nd":
1670 Node = this.ParseNodeInfo(E);
1671 break;
1672
1673 case "partial":
1674 List<string> Fields = null;
1675 FieldType FieldTypes = (FieldType)0;
1676
1677 foreach (XmlAttribute Attr in E.Attributes)
1678 {
1679 switch (Attr.Name)
1680 {
1681 case "all":
1682 if (CommonTypes.TryParse(Attr.Value, out bool b) && b)
1683 FieldTypes |= FieldType.All;
1684 break;
1685
1686 case "h":
1687 if (CommonTypes.TryParse(Attr.Value, out b) && b)
1688 FieldTypes |= FieldType.Historical;
1689 break;
1690
1691 case "m":
1692 if (CommonTypes.TryParse(Attr.Value, out b) && b)
1693 FieldTypes |= FieldType.Momentary;
1694 break;
1695
1696 case "p":
1697 if (CommonTypes.TryParse(Attr.Value, out b) && b)
1698 FieldTypes |= FieldType.Peak;
1699 break;
1700
1701 case "s":
1702 if (CommonTypes.TryParse(Attr.Value, out b) && b)
1703 FieldTypes |= FieldType.Status;
1704 break;
1705
1706 case "c":
1707 if (CommonTypes.TryParse(Attr.Value, out b) && b)
1708 FieldTypes |= FieldType.Computed;
1709 break;
1710
1711 case "i":
1712 if (CommonTypes.TryParse(Attr.Value, out b) && b)
1713 FieldTypes |= FieldType.Identity;
1714 break;
1715 }
1716 }
1717
1718 foreach (XmlNode N2 in E.ChildNodes)
1719 {
1720 if (N2 is XmlElement E2)
1721 {
1722 switch (E2.LocalName)
1723 {
1724 case "f":
1725 if (Fields is null)
1726 Fields = new List<string>();
1727
1728 Fields.Add(XML.Attribute(E2, "n"));
1729 break;
1730 }
1731 }
1732 }
1733
1734 Partial = new Partial()
1735 {
1736 Names = Fields?.ToArray(),
1737 Mask = (int)FieldTypes
1738 };
1739 break;
1740
1741 case "fromJid":
1742 Condition = new IfRemoteJid()
1743 {
1744 Value = RemoteJID
1745 };
1746 break;
1747
1748 case "fromDomain":
1749 Condition = new IfRemoteDomain()
1750 {
1751 Value = GetDomain(RemoteJID)
1752 };
1753 break;
1754
1755 case "fromService":
1756 Condition = new IfServiceToken()
1757 {
1758 Value = XML.Attribute(E, "token")
1759 };
1760 break;
1761
1762 case "fromDevice":
1763 Condition = new IfDeviceToken()
1764 {
1765 Value = XML.Attribute(E, "token")
1766 };
1767 break;
1768
1769 case "fromUser":
1770 Condition = new IfUserToken()
1771 {
1772 Value = XML.Attribute(E, "token")
1773 };
1774 break;
1775
1776 case "all":
1777 Condition = new All();
1778 break;
1779 }
1780 }
1781 }
1782
1783 if (Condition is null)
1784 {
1785 await e.IqErrorBadRequest(e.To, "Rule not specified.", "en");
1786 return;
1787 }
1788
1789 if (Rule.JID != JID || Rule.NodeID != Node.NodeId || Rule.SourceID != Node.SourceId || Rule.Partition != Node.Partition)
1790 {
1791 await e.IqErrorBadRequest(e.To, "Parameters do not match.", "en");
1792 return;
1793 }
1794
1795 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
1796 Registration Registration = await this.GetRegistration(JID, Rule.NodeID, Rule.SourceID, Rule.Partition, null, QueryVersion);
1797
1798 if (Registration is null || Registration.Owner != OwnerJid)
1799 {
1800 await e.IqErrorForbidden(e.To, "Access denied. Owner mismatch.", "en");
1801 return;
1802 }
1803
1804 if (CanRead)
1805 {
1806 if (!(Partial is null))
1807 Condition.AddChildRule(Partial);
1808 else
1809 Condition.AddChildRule(new Yes());
1810 }
1811 else
1812 Condition.AddChildRule(new No());
1813
1814 Rule.AddChildRule(Condition);
1815
1816 Log.Informational("Readot rule changed.", JID, OwnerJid, "ReadoutRule",
1817 new KeyValuePair<string, object>("RemoteJID", RemoteJID),
1818 new KeyValuePair<string, object>("Allowed", CanRead));
1819
1820 await Database.UpdateLazy(Rule);
1821
1822 await e.IqResult(string.Empty, e.To);
1823
1824 await this.ClearCache(JID, Registration.Version);
1825 }
1826
1827 private async Task CanControlRuleHandler(object Sender, IqEventArgs e)
1828 {
1829 CaseInsensitiveString OwnerJid = e.From.BareJid;
1830 CaseInsensitiveString JID = XML.Attribute(e.Query, "jid");
1831 CaseInsensitiveString RemoteJID = XML.Attribute(e.Query, "remoteJid");
1832 string Key = XML.Attribute(e.Query, "key");
1833 bool CanControl = XML.Attribute(e.Query, "result", false);
1835
1836 if (!Guid.TryParse(Key, out Guid ObjectId))
1837 {
1838 await e.IqErrorBadRequest(e.To, "Invalid key.", "en");
1839 return;
1840 }
1841
1842 Rule = await Database.TryLoadObject<ControlRule>(ObjectId);
1843 if (Rule is null)
1844 {
1845 await e.IqErrorItemNotFound(e.To, "Key not found.", "en");
1846 return;
1847 }
1848
1849 Rule Condition = null;
1851 Partial Partial = null;
1852
1853 foreach (XmlNode N in e.Query.ChildNodes)
1854 {
1855 if (N is XmlElement E)
1856 {
1857 switch (E.LocalName)
1858 {
1859 case "nd":
1860 Node = this.ParseNodeInfo(E);
1861 break;
1862
1863 case "partial":
1864 List<string> Parameters = null;
1865
1866 foreach (XmlNode N2 in E.ChildNodes)
1867 {
1868 if (N2 is XmlElement E2)
1869 {
1870 switch (E2.LocalName)
1871 {
1872 case "p":
1873 if (Parameters is null)
1874 Parameters = new List<string>();
1875
1876 Parameters.Add(XML.Attribute(E2, "n"));
1877 break;
1878 }
1879 }
1880 }
1881
1882 Partial = new Partial()
1883 {
1884 Names = Parameters?.ToArray()
1885 };
1886 break;
1887
1888 case "fromJid":
1889 Condition = new IfRemoteJid()
1890 {
1891 Value = RemoteJID
1892 };
1893 break;
1894
1895 case "fromDomain":
1896 Condition = new IfRemoteDomain()
1897 {
1898 Value = GetDomain(RemoteJID)
1899 };
1900 break;
1901
1902 case "fromService":
1903 Condition = new IfServiceToken()
1904 {
1905 Value = XML.Attribute(E, "token")
1906 };
1907 break;
1908
1909 case "fromDevice":
1910 Condition = new IfDeviceToken()
1911 {
1912 Value = XML.Attribute(E, "token")
1913 };
1914 break;
1915
1916 case "fromUser":
1917 Condition = new IfUserToken()
1918 {
1919 Value = XML.Attribute(E, "token")
1920 };
1921 break;
1922
1923 case "all":
1924 Condition = new All();
1925 break;
1926 }
1927 }
1928 }
1929
1930 if (Condition is null)
1931 {
1932 await e.IqErrorBadRequest(e.To, "Rule not specified.", "en");
1933 return;
1934 }
1935
1936 if (Rule.JID != JID || Rule.NodeID != Node.NodeId || Rule.SourceID != Node.SourceId || Rule.Partition != Node.Partition)
1937 {
1938 await e.IqErrorBadRequest(e.To, "Parameters do not match.", "en");
1939 return;
1940 }
1941
1942 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
1943 Registration Registration = await this.GetRegistration(JID, Rule.NodeID, Rule.SourceID, Rule.Partition, null, QueryVersion);
1944
1945 if (Registration is null || Registration.Owner != OwnerJid)
1946 {
1947 await e.IqErrorForbidden(e.To, "Access denied. Owner mismatch.", "en");
1948 return;
1949 }
1950
1951 if (CanControl)
1952 {
1953 if (!(Partial is null))
1954 {
1955 Partial.AddChildRule(new Yes());
1956 Condition.AddChildRule(Partial);
1957 }
1958 else
1959 Condition.AddChildRule(new Yes());
1960 }
1961 else
1962 Condition.AddChildRule(new No());
1963
1964 Rule.AddChildRule(Condition);
1965
1966 Log.Informational("Control rule changed.", JID, OwnerJid, "ControlRule",
1967 new KeyValuePair<string, object>("RemoteJID", RemoteJID),
1968 new KeyValuePair<string, object>("Allowed", CanControl));
1969
1970 await Database.UpdateLazy(Rule);
1971
1972 await e.IqResult(string.Empty, e.To);
1973
1974 await this.ClearCache(JID, Registration.Version);
1975 }
1976
1977 private async Task ClearCacheHandler(object Sender, IqEventArgs e)
1978 {
1979 try
1980 {
1981 CaseInsensitiveString Jid = XML.Attribute(e.Query, "jid");
1982 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
1983
1985 {
1986 foreach (Registration R in await Database.Find<Registration>(new FilterFieldEqualTo("Owner", e.From.BareJid)))
1987 {
1988 if (string.IsNullOrEmpty(R.NodeId) && string.IsNullOrEmpty(R.SourceId) && string.IsNullOrEmpty(R.Partition))
1989 await this.ClearCache(R.JID, R.Version);
1990 }
1991 }
1992 else
1993 {
1994 Registration R = await Database.FindFirstDeleteRest<Registration>(new FilterAnd(
1995 new FilterFieldEqualTo("Owner", e.From.BareJid),
1996 new FilterFieldEqualTo("JID", Jid),
1997 new FilterFieldEqualTo("NodeId", string.Empty),
1998 new FilterFieldEqualTo("SourceId", string.Empty),
1999 new FilterFieldEqualTo("Partition", string.Empty)));
2000
2001 if (R is null)
2002 {
2003 await e.IqErrorItemNotFound(e.To, "Device not found, or not owned.", "en");
2004 return;
2005 }
2006
2007 await this.ClearCache(Jid, R.Version);
2008 }
2009
2010 await e.IqResult(string.Empty, e.To);
2011 }
2012 catch (Exception ex)
2013 {
2014 await e.IqError(ex, e.To);
2015 }
2016 }
2017
2018 private async Task GetDevicesHandler(object Sender, IqEventArgs e)
2019 {
2020 try
2021 {
2022 XmlElement E = e.Query;
2023 int Offset = XML.Attribute(E, "offset", 0);
2024 if (Offset < 0)
2025 {
2026 await e.IqErrorBadRequest(e.To, "Offset must be non-negative.", "en");
2027 return;
2028 }
2029
2030 int MaxCount = XML.Attribute(E, "maxCount", 20);
2031 if (MaxCount > 100)
2032 MaxCount = 100;
2033 else if (MaxCount <= 0)
2034 {
2035 await e.IqErrorBadRequest(e.To, "Maximum count must be positive.", "en");
2036 return;
2037 }
2038
2039 List<KeyValuePair<Registration, IEnumerable<MetaDataTag>>> Found = new List<KeyValuePair<Registration, IEnumerable<MetaDataTag>>>();
2040 bool More = false;
2041
2042 IEnumerable<Registration> Registrations;
2043
2044 Registrations = await Database.Find<Registration>(Offset, MaxCount + 1, new FilterFieldEqualTo("Owner", e.From.BareJid),
2045 "JID", "NodeId", "SourceId", "Partition");
2046
2047 foreach (Registration R in Registrations)
2048 {
2049 if (Found.Count >= MaxCount)
2050 {
2051 More = true;
2052 break;
2053 }
2054
2055 Found.Add(new KeyValuePair<Registration, IEnumerable<MetaDataTag>>(R, await LoadTags(R.ObjectId)));
2056 }
2057
2058 await e.IqResult(ResultSet(Found, More, e.Query.NamespaceURI), e.To);
2059 }
2060 catch (Exception ex)
2061 {
2062 await e.IqError(ex, e.To);
2063 }
2064 }
2065
2066 #endregion
2067
2068 #region Thing Registry XEP-0347
2069
2070 private Cache<CaseInsensitiveString, RegistryAccount> registryAccounts = CreateRegistryAccountsCache();
2071
2072 private static Cache<CaseInsensitiveString, RegistryAccount> CreateRegistryAccountsCache()
2073 {
2075 new Cache<CaseInsensitiveString, RegistryAccount>(int.MaxValue, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(5));
2076
2077 Result.Removed += UpdateRegistryAccount;
2078
2079 return Result;
2080 }
2081
2082 private static async Task UpdateRegistryAccount(object Sender, CacheItemEventArgs<CaseInsensitiveString, RegistryAccount> e)
2083 {
2084 try
2085 {
2086 await Database.Update(e.Value);
2087 }
2088 catch (Exception ex)
2089 {
2090 Log.Exception(ex);
2091 }
2092 }
2093
2094 internal static async Task<RegistryAccount> GetAccount(CaseInsensitiveString JID)
2095 {
2096 if (XmppServerModule.Provisioning?.registryAccounts?.TryGetValue(JID, out RegistryAccount Account) ?? false)
2097 return Account;
2098
2099 Account = await Database.FindFirstDeleteRest<RegistryAccount>(new FilterFieldEqualTo("JID", JID));
2100
2101 if (Account is null)
2102 {
2103 DateTime Now = DateTime.Now;
2104 Account = new RegistryAccount()
2105 {
2106 JID = JID,
2107 FirstAction = Now,
2108 LastAction = Now
2109 };
2110
2111 await Database.Insert(Account);
2112 }
2113 else
2114 Account.LastAction = DateTime.Now; // Account updated when purged from cache.
2115
2116 if (XmppServerModule.Provisioning?.registryAccounts?.TryGetValue(JID, out RegistryAccount Account2) ?? false)
2117 {
2118 Account2.LastAction = DateTime.Now;
2119 return Account2;
2120 }
2121 else
2122 {
2123 XmppServerModule.Provisioning?.registryAccounts?.Add(JID, Account);
2124 return Account;
2125 }
2126 }
2127
2128 private async Task RegisterHandler(object _, IqEventArgs e)
2129 {
2130 try
2131 {
2132 XmlElement E = e.Query;
2133 ThingReference Node;
2134 bool SelfOwned = XML.Attribute(E, "selfOwned", false);
2135 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
2136
2137 Node = this.ParseNodeInfo(E);
2138 if (!this.ParseTags(e, E, out Dictionary<string, MetaDataTag> Tags, out string Key))
2139 return;
2140
2142 Registration Registration = await this.GetRegistration(JID, Node.NodeId, Node.SourceId, Node.Partition, QueryVersion, null);
2143
2144 if (!(Registration is null) && !string.IsNullOrEmpty(Registration.Owner))
2145 {
2146 await e.IqResult("<claimed xmlns='" + e.Query.NamespaceURI + "' jid='" + XML.Encode(Registration.Owner) +
2147 "' public='" + CommonTypes.Encode(Registration.IsPublic) + "'/>", e.To);
2148 }
2149 else
2150 {
2151 if (Registration is null)
2152 {
2154 {
2155 JID = JID,
2156 NodeId = Node.NodeId,
2157 SourceId = Node.SourceId,
2158 Partition = Node.Partition,
2159 Key = Key,
2160 NrRegistrations = 1,
2161 NrUpdates = 0,
2162 FirstRegistration = DateTime.Now,
2163 Version = QueryVersion
2164 };
2165
2166 Registration.LastUpdate = Registration.FirstRegistration;
2167
2168 if (SelfOwned)
2169 {
2170 Registration.IsPublic = true;
2171 Registration.Owner = e.From.BareJid;
2172 }
2173 else
2174 {
2175 Registration.IsPublic = false;
2176 Registration.Owner = string.Empty;
2177 }
2178
2180 await SaveNewRegistrationTags(Registration, this.GetArray(Tags));
2181
2182 Log.Informational("Thing registered.", e.From.BareJid, string.Empty, "Register", GetLogParameters(Node, Tags.Values));
2183 }
2184 else
2185 {
2186 Registration.Key = Key;
2188 Registration.LastUpdate = DateTime.Now;
2189
2190 if (SelfOwned)
2191 {
2192 Registration.IsPublic = true;
2193 Registration.Owner = e.From.BareJid;
2194 }
2195 else
2196 {
2197 Registration.IsPublic = false;
2198 Registration.Owner = string.Empty;
2199 }
2200
2202 await DeleteTags(Registration);
2203 await SaveNewRegistrationTags(Registration, this.GetArray(Tags));
2204
2205 Log.Informational("Thing re-registered.", e.From.BareJid, string.Empty, "Register", GetLogParameters(Node, Tags.Values));
2206 }
2207
2208 await e.IqResult(string.Empty, e.To);
2209 }
2210 }
2211 catch (Exception ex)
2212 {
2213 await e.IqError(ex, e.To);
2214 }
2215 }
2216
2217 private async Task<Registration> GetRegistration(CaseInsensitiveString JID, string NodeId, string SourceId, string Partition,
2218 NamespaceSet? ThingVersion, NamespaceSet? OwnerVersion)
2219 {
2220 Registration Result = await Database.FindFirstDeleteRest<Registration>(new FilterAnd(
2221 new FilterFieldEqualTo("JID", JID),
2222 new FilterFieldEqualTo("NodeId", NodeId),
2223 new FilterFieldEqualTo("SourceId", SourceId),
2224 new FilterFieldEqualTo("Partition", Partition)));
2225
2226 if (Result is null)
2227 return null;
2228
2229 bool Updated = false;
2230
2231 if (ThingVersion.HasValue && Result.Version != ThingVersion.Value)
2232 {
2233 Result.Version = ThingVersion.Value;
2234 Updated = true;
2235 }
2236
2237 if (OwnerVersion.HasValue && Result.OwnerVersion != OwnerVersion.Value)
2238 {
2239 Result.OwnerVersion = OwnerVersion.Value;
2240 Updated = true;
2241 }
2242
2243 if (Updated)
2244 await Database.Update(Result);
2245
2246 return Result;
2247 }
2248
2249 private MetaDataTag[] GetArray(Dictionary<string, MetaDataTag> Tags)
2250 {
2251 MetaDataTag[] Result = new MetaDataTag[Tags.Count];
2252 Tags.Values.CopyTo(Result, 0);
2253 return Result;
2254 }
2255
2256 private static KeyValuePair<string, object>[] GetLogParameters(ThingReference Node, IEnumerable<MetaDataTag> Tags)
2257 {
2258 List<KeyValuePair<string, object>> Result = new List<KeyValuePair<string, object>>();
2259
2260 if (!string.IsNullOrEmpty(Node.NodeId))
2261 Result.Add(new KeyValuePair<string, object>("NodeId", Node.NodeId));
2262
2263 if (!string.IsNullOrEmpty(Node.SourceId))
2264 Result.Add(new KeyValuePair<string, object>("SourceId", Node.SourceId));
2265
2266 if (!string.IsNullOrEmpty(Node.Partition))
2267 Result.Add(new KeyValuePair<string, object>("Partition", Node.Partition));
2268
2269 if (!(Tags is null))
2270 {
2271 foreach (MetaDataTag Tag in Tags)
2272 Result.Add(new KeyValuePair<string, object>(Tag.Name, Tag.Value));
2273 }
2274
2275 return Result.ToArray();
2276 }
2277
2278 private static KeyValuePair<string, object>[] GetLogParameters(Registration Node, IEnumerable<MetaDataTag> Tags)
2279 {
2280 List<KeyValuePair<string, object>> Result = new List<KeyValuePair<string, object>>();
2281
2282 if (!string.IsNullOrEmpty(Node.NodeId))
2283 Result.Add(new KeyValuePair<string, object>("NodeId", Node.NodeId));
2284
2285 if (!string.IsNullOrEmpty(Node.SourceId))
2286 Result.Add(new KeyValuePair<string, object>("SourceId", Node.SourceId));
2287
2288 if (!string.IsNullOrEmpty(Node.Partition))
2289 Result.Add(new KeyValuePair<string, object>("Partition", Node.Partition));
2290
2291 foreach (MetaDataTag Tag in Tags)
2292 Result.Add(new KeyValuePair<string, object>(Tag.Name, Tag.Value));
2293
2294 return Result.ToArray();
2295 }
2296
2297 private async Task UpdateHandler(object _, IqEventArgs e)
2298 {
2299 try
2300 {
2301 XmlElement E = e.Query;
2302 ThingReference Node;
2303 CaseInsensitiveString Jid = XML.Attribute(E, "jid", e.From.BareJid).ToLower();
2304 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
2305
2306 Node = this.ParseNodeInfo(E);
2307 if (!this.ParseTags(e, E, out Dictionary<string, MetaDataTag> Tags, out string Key))
2308 return;
2309
2310 Registration Registration = await this.GetRegistration(Jid, Node.NodeId, Node.SourceId, Node.Partition, QueryVersion, null);
2311
2312 if (Registration is null)
2313 {
2314 await e.IqErrorItemNotFound(e.To, "Thing not found.", "en");
2315
2316 if (e.From.BareJid != Jid)
2317 await IncAccountFailedUpdates(e.From.BareJid);
2318 }
2319 else if (string.IsNullOrEmpty(Registration.Owner))
2320 {
2321 await e.IqResult("<disowned xmlns='" + e.Query.NamespaceURI + "'/>", e.To);
2322
2323 if (e.From.BareJid != Jid)
2324 await IncAccountFailedUpdates(e.From.BareJid);
2325 }
2326 else if (e.From.BareJid != Jid && e.From.BareJid != Registration.Owner)
2327 {
2328 await e.IqErrorItemNotFound(e.To, "Thing not found.", "en");
2329
2330 if (e.From.BareJid != Jid)
2331 await IncAccountFailedUpdates(e.From.BareJid);
2332 }
2333 else
2334 {
2336 Registration.LastUpdate = DateTime.Now;
2337
2339 await UpdateRegistrationTags(Registration, this.GetArray(Tags));
2340
2341 await e.IqResult(string.Empty, e.To);
2342
2343 Log.Informational("Thing registration updated.", e.From.BareJid, string.Empty,
2344 "Update", GetLogParameters(Node, Tags.Values));
2345
2346 if (e.From.BareJid != Jid)
2347 await IncAccountSuccessfulUpdates(e.From.BareJid);
2348 }
2349 }
2350 catch (Exception ex)
2351 {
2352 await e.IqError(ex, e.To);
2353 }
2354 }
2355
2356 private static async Task IncAccountSuccessfulUpdates(CaseInsensitiveString JID)
2357 {
2358 RegistryAccount Account = await GetAccount(JID);
2359 Account.NrSuccessfulUpdates++;
2360 }
2361
2362 private static async Task IncAccountFailedUpdates(CaseInsensitiveString JID)
2363 {
2364 RegistryAccount Account = await GetAccount(JID);
2365 Account.NrFailedUpdates++;
2366 }
2367
2368 private async Task UnregisterHandler(object _, IqEventArgs e)
2369 {
2370 try
2371 {
2372 XmlElement E = e.Query;
2373 ThingReference Node;
2374
2375 Node = this.ParseNodeInfo(E);
2376
2377 IEnumerable<Registration> Registrations = await Database.Find<Registration>(new FilterAnd(
2378 new FilterFieldEqualTo("JID", e.From.BareJid),
2379 new FilterFieldEqualTo("NodeId", Node.NodeId),
2380 new FilterFieldEqualTo("SourceId", Node.SourceId),
2381 new FilterFieldEqualTo("Partition", Node.Partition)));
2382
2383 foreach (Registration R in Registrations)
2384 {
2385 MetaDataTag[] Tags = await DeleteTags(R);
2386 await this.DeleteRules(R);
2387 await Database.DeleteLazy(R);
2388 Log.Informational("Thing unregistered.", e.From.BareJid, string.Empty, "Unregister", GetLogParameters(Node, Tags));
2389 }
2390
2391 await e.IqResult(string.Empty, e.To);
2392 }
2393 catch (Exception ex)
2394 {
2395 await e.IqError(ex, e.To);
2396 }
2397 }
2398
2399 private async Task DeleteRules(Registration R)
2400 {
2401 if (string.IsNullOrEmpty(R.NodeId) && string.IsNullOrEmpty(R.SourceId) && string.IsNullOrEmpty(R.Partition))
2403
2405 new FilterFieldEqualTo("NodeID", R.NodeId), new FilterFieldEqualTo("SourceID", R.SourceId), new FilterFieldEqualTo("Partition", R.Partition)));
2406
2408 new FilterFieldEqualTo("NodeID", R.NodeId), new FilterFieldEqualTo("SourceID", R.SourceId), new FilterFieldEqualTo("Partition", R.Partition)));
2409 }
2410
2411 private async Task MineHandler(object _, IqEventArgs e)
2412 {
2413 XmlElement E = e.Query;
2414 bool Public = XML.Attribute(E, "public", false);
2415
2416 if (!this.ParseTags(e, E, out Dictionary<string, MetaDataTag> Tags, out string Key))
2417 {
2418 await e.IqErrorBadRequest(e.To, "Unable to parse tags.", "en");
2419 return;
2420 }
2421
2422 if (string.IsNullOrEmpty(Key))
2423 {
2424 await e.IqErrorForbidden(e.To, "KEY meta tag must be included.", "en");
2425 await IncAccountFailedClaims(e.From.BareJid);
2426 return;
2427 }
2428
2429 IEnumerable<Registration> Registrations = await Database.Find<Registration>(0, int.MaxValue, new FilterFieldEqualTo("Key", Key));
2430 IEnumerable<MetaDataTag> MatchTags;
2431 bool Match;
2432 int c;
2433 foreach (Registration R in Registrations)
2434 {
2435 MatchTags = MatchTags = await Database.Find<MetaDataTag>(new FilterFieldEqualTo("Registration", R.ObjectId));
2436 c = 0;
2437 Match = true;
2438
2439 foreach (MetaDataTag Tag in MatchTags)
2440 {
2441 c++;
2442
2443 if (Tags.TryGetValue(Tag.Name, out MetaDataTag T))
2444 {
2445 if (Tag is MetaDataStringTag StringTag)
2446 {
2447 if (T is MetaDataStringTag T2 && StringTag.StringValue == T2.StringValue)
2448 continue;
2449 }
2450 else if (Tag is MetaDataNumericTag NumericTag)
2451 {
2452 if (T is MetaDataNumericTag T2 && NumericTag.TagValue == T2.TagValue)
2453 continue;
2454 }
2455 }
2456
2457 Match = false;
2458 break;
2459 }
2460
2461 if (!Match || c != Tags.Count)
2462 continue;
2463
2464 await Database.StartBulk();
2465 try
2466 {
2467 R.Key = string.Empty;
2468 R.Owner = e.From.BareJid;
2469 R.IsPublic = Public;
2470 await Database.UpdateLazy(R);
2471
2472 foreach (MetaDataTag Tag in MatchTags)
2473 {
2474 if (Tag.IsPublic != Public)
2475 {
2476 Tag.IsPublic = Public;
2477 await Database.UpdateLazy(Tag);
2478 }
2479 }
2480 }
2481 finally
2482 {
2483 await Database.EndBulk();
2484 }
2485
2486 StringBuilder Response = new StringBuilder();
2487
2488 Response.Append("<claimed xmlns='");
2489 Response.Append(e.Query.NamespaceURI);
2490 Response.Append("' jid='");
2491 Response.Append(XML.Encode(R.JID));
2492
2493 if (!string.IsNullOrEmpty(R.NodeId))
2494 {
2495 Response.Append("' id='");
2496 Response.Append(XML.Encode(R.NodeId));
2497 }
2498
2499 if (!string.IsNullOrEmpty(R.SourceId))
2500 {
2501 Response.Append("' src='");
2502 Response.Append(XML.Encode(R.SourceId));
2503 }
2504
2505 if (!string.IsNullOrEmpty(R.Partition))
2506 {
2507 Response.Append("' pt='");
2508 Response.Append(XML.Encode(R.Partition));
2509 }
2510
2511 Response.Append("'/>");
2512
2513 await e.IqResult(Response.ToString(), e.To);
2514
2515 // Send message to thing, informing it, it has been claimed:
2516
2517 Response.Clear();
2518
2519 Response.Append("<claimed xmlns='");
2520 Response.Append(NamespaceIoTDiscovery(R.Version));
2521 Response.Append("' jid='");
2522 Response.Append(XML.Encode(e.From.BareJid));
2523 Response.Append("' public='");
2524 Response.Append(CommonTypes.Encode(Public));
2525
2526 if (!string.IsNullOrEmpty(R.NodeId))
2527 {
2528 Response.Append("' id='");
2529 Response.Append(XML.Encode(R.NodeId));
2530 }
2531
2532 if (!string.IsNullOrEmpty(R.SourceId))
2533 {
2534 Response.Append("' src='");
2535 Response.Append(XML.Encode(R.SourceId));
2536 }
2537
2538 if (!string.IsNullOrEmpty(R.Partition))
2539 {
2540 Response.Append("' pt='");
2541 Response.Append(XML.Encode(R.Partition));
2542 }
2543
2544 Response.Append("'/>");
2545
2546 await this.Server.GetLastPresence(R.JID, async (sender, e2) =>
2547 {
2548 if (e2.Ok)
2549 await this.Server.SendIqRequest("set", e.To, e2.From, string.Empty, Response.ToString(), null, null);
2550 }, null);
2551
2552 Log.Informational("Thing claimed.", R.JID, e.From.Address, "Claim", GetLogParameters(R, MatchTags));
2553 await IncAccountSuccessfulClaims(e.From.BareJid);
2554 return;
2555 }
2556
2557 await e.IqErrorItemNotFound(e.To, "Thing not found, or already claimed.", "en");
2558 await IncAccountFailedClaims(e.From.BareJid);
2559 }
2560
2561 private static async Task<MetaDataTag[]> DeleteTags(Registration Registration)
2562 {
2563 List<MetaDataTag> Tags = new List<MetaDataTag>();
2564
2565 foreach (MetaDataTag Tag in await Database.FindDelete<MetaDataTag>(new FilterFieldEqualTo("Registration", Registration.ObjectId)))
2566 Tags.Add(Tag);
2567
2568 return Tags.ToArray();
2569 }
2570
2571 private static async Task SaveNewRegistrationTags(Registration Registration, MetaDataTag[] Tags)
2572 {
2573 foreach (MetaDataTag Tag in Tags)
2574 {
2575 if (Tag.IsEmpty)
2576 continue;
2577
2578 Tag.Registration = Registration.ObjectId;
2579 Tag.IsPublic = Registration.IsPublic;
2580
2581 await Database.InsertLazy(Tag);
2582 }
2583 }
2584
2585 private static Task<IEnumerable<MetaDataTag>> LoadTags(Guid RegistrationReference)
2586 {
2587 return Database.Find<MetaDataTag>(new FilterFieldEqualTo("Registration", RegistrationReference));
2588 }
2589
2590 private static async Task<IEnumerable<MetaDataTag>> UpdateRegistrationTags(Registration Registration, MetaDataTag[] Tags)
2591 {
2592 Dictionary<string, MetaDataTag> Tags2 = new Dictionary<string, MetaDataTag>(StringComparer.CurrentCultureIgnoreCase);
2593
2594 foreach (MetaDataTag Tag in await Database.Find<MetaDataTag>(new FilterFieldEqualTo("Registration", Registration.ObjectId)))
2595 Tags2[Tag.Name] = Tag;
2596
2597 if (!(Tags is null))
2598 {
2599 foreach (MetaDataTag Tag in Tags)
2600 {
2601 if (Tags2.TryGetValue(Tag.Name, out MetaDataTag Tag2))
2602 {
2603 if (Tag.IsEmpty)
2604 {
2605 await Database.DeleteLazy(Tag2);
2606 Tags2.Remove(Tag.Name);
2607 }
2608 else
2609 {
2610 Tag.ObjectId = Tag2.ObjectId;
2611 Tag.Registration = Registration.ObjectId;
2612 Tag.IsPublic = Registration.IsPublic;
2613
2614 await Database.UpdateLazy(Tag);
2615 }
2616 }
2617 else if (!Tag.IsEmpty)
2618 {
2619 Tag.Registration = Registration.ObjectId;
2620 Tag.IsPublic = Registration.IsPublic;
2621
2622 await Database.InsertLazy(Tag);
2623 }
2624 }
2625 }
2626
2627 foreach (MetaDataTag Tag in Tags2.Values)
2628 {
2629 if (Tag.IsPublic != Registration.IsPublic)
2630 {
2631 Tag.IsPublic = Registration.IsPublic;
2632 await Database.UpdateLazy(Tag);
2633 }
2634 }
2635
2636 return Tags2.Values;
2637 }
2638
2639 private static async Task IncAccountSuccessfulClaims(CaseInsensitiveString JID)
2640 {
2641 RegistryAccount Account = await GetAccount(JID);
2642 Account.NrSuccessfulClaims++;
2643 }
2644
2645 private static async Task IncAccountFailedClaims(CaseInsensitiveString JID)
2646 {
2647 RegistryAccount Account = await GetAccount(JID);
2648 Account.NrFailedClaims++;
2649 }
2650
2651 private async Task DisownHandler(object _, IqEventArgs e)
2652 {
2653 try
2654 {
2655 XmlElement E = e.Query;
2656 ThingReference Node;
2657 CaseInsensitiveString Jid = XML.Attribute(E, "jid", string.Empty);
2658
2659 Node = this.ParseNodeInfo(E);
2660
2661 IEnumerable<Registration> Registrations = await Database.Find<Registration>(new FilterAnd(
2662 new FilterFieldEqualTo("JID", Jid),
2663 new FilterFieldEqualTo("NodeId", Node.NodeId),
2664 new FilterFieldEqualTo("SourceId", Node.SourceId),
2665 new FilterFieldEqualTo("Partition", Node.Partition)));
2666
2667 foreach (Registration R in Registrations)
2668 {
2669 if (R.Owner == e.From.BareJid)
2670 {
2671 StringBuilder Request = new StringBuilder();
2672
2673 Request.Append("<disowned xmlns='");
2674 Request.Append(NamespaceIoTDiscovery(R.Version));
2675 AppendNodeReference(Request, Node);
2676 Request.Append("'/>");
2677
2678 await this.Server.GetLastPresence(Jid, async (sender, e3) =>
2679 {
2680 try
2681 {
2682 if (e3.Ok)
2683 {
2684 R.Key = Convert.ToBase64String(Gateway.NextBytes(16));
2685 R.IsPublic = false;
2686 R.Owner = string.Empty;
2687
2688 await this.Server.SendIqRequest("set", e.To, e3.From, string.Empty,
2689 Request.ToString(), async (sender2, e4) =>
2690 {
2691 try
2692 {
2693 if (e4.Ok)
2694 {
2695 await e.IqResult(string.Empty, e.To);
2696
2697 IEnumerable<MetaDataTag> Tags = await LoadTags(R.ObjectId);
2698
2699 await Database.StartBulk();
2700 try
2701 {
2702 await Database.UpdateLazy(R);
2703 await Database.DeleteLazy(Tags);
2704 await this.DeleteRules(R);
2705 }
2706 finally
2707 {
2708 await Database.EndBulk();
2709 }
2710
2711 Log.Informational("Thing disowned.", R.JID, Jid, "Disown", GetLogParameters(Node, Tags));
2712
2713 await IncAccountSuccessfulDisownments(e.From.BareJid);
2714 }
2715 else
2716 await e.IqErrorNotAllowed(e.To, "Thing needs to accept disownment.", "en");
2717 }
2718 catch (Exception ex)
2719 {
2720 Log.Exception(ex);
2721 }
2722 }, null);
2723 }
2724 else
2725 await e.IqErrorNotAllowed(e.To, "Unable to reach thing.", "en");
2726 }
2727 catch (Exception ex)
2728 {
2729 Log.Exception(ex);
2730 }
2731 }, null);
2732 }
2733 else
2734 {
2735 await e.IqErrorItemNotFound(e.To, "Thing not found.", "en");
2736 await IncAccountFailedDisownments(e.From.BareJid);
2737 }
2738
2739 return;
2740 }
2741
2742 await e.IqErrorItemNotFound(e.To, "Thing not found.", "en");
2743 await IncAccountFailedDisownments(e.From.BareJid);
2744 }
2745 catch (Exception ex)
2746 {
2747 await e.IqError(ex, e.To);
2748 }
2749 }
2750
2751 private static async Task IncAccountSuccessfulDisownments(CaseInsensitiveString JID)
2752 {
2753 RegistryAccount Account = await GetAccount(JID);
2754 Account.NrSuccessfulDisownments++;
2755 }
2756
2757 private static async Task IncAccountFailedDisownments(CaseInsensitiveString JID)
2758 {
2759 RegistryAccount Account = await GetAccount(JID);
2760 Account.NrFailedDisownments++;
2761 }
2762
2763 private async Task RemoveHandler(object _, IqEventArgs e)
2764 {
2765 try
2766 {
2767 XmlElement E = e.Query;
2768 ThingReference Node;
2769 CaseInsensitiveString Jid = XML.Attribute(E, "jid", string.Empty);
2770
2771 Node = this.ParseNodeInfo(E);
2772
2773 IEnumerable<Registration> Registrations = await Database.Find<Registration>(new FilterAnd(
2774 new FilterFieldEqualTo("JID", Jid),
2775 new FilterFieldEqualTo("NodeId", Node.NodeId),
2776 new FilterFieldEqualTo("SourceId", Node.SourceId),
2777 new FilterFieldEqualTo("Partition", Node.Partition)));
2778
2779 foreach (Registration R in Registrations)
2780 {
2781 if (R.Owner == e.From.BareJid)
2782 {
2783 if (R.IsPublic)
2784 {
2785 R.IsPublic = false;
2786 await Database.UpdateLazy(R);
2787 IEnumerable<MetaDataTag> Tags = await LoadTags(R.ObjectId);
2788
2789 Log.Informational("Thing removed from public registry.", R.JID, Jid, "Remove", GetLogParameters(Node, Tags));
2790 }
2791
2792 await e.IqResult(string.Empty, e.To);
2793
2794 StringBuilder Request = new StringBuilder();
2795
2796 Request.Append("<removed xmlns='");
2797 Request.Append(NamespaceIoTDiscovery(R.Version));
2798 AppendNodeReference(Request, Node);
2799 Request.Append("'/>");
2800
2801 await this.Server.GetLastPresence(Jid, async (sender, e3) =>
2802 {
2803 try
2804 {
2805 if (e3.Ok)
2806 await this.Server.SendIqRequest("set", e.To, e3.From, string.Empty, Request.ToString(), null, null);
2807 }
2808 catch (Exception ex)
2809 {
2810 Log.Exception(ex);
2811 }
2812 }, null);
2813
2814 await IncAccountSuccessfulRemovals(e.From.BareJid);
2815 }
2816 else
2817 {
2818 await e.IqErrorItemNotFound(e.To, "Thing not found.", "en");
2819 await IncAccountFailedRemovals(e.From.BareJid);
2820 }
2821
2822 return;
2823 }
2824
2825 await e.IqErrorItemNotFound(e.To, "Thing not found.", "en");
2826 await IncAccountFailedRemovals(e.From.BareJid);
2827 }
2828 catch (Exception ex)
2829 {
2830 await e.IqError(ex, e.To);
2831 }
2832 }
2833
2834 private static async Task IncAccountSuccessfulRemovals(CaseInsensitiveString JID)
2835 {
2836 RegistryAccount Account = await GetAccount(JID);
2837 Account.NrSuccessfulRemovals++;
2838 }
2839
2840 private static async Task IncAccountFailedRemovals(CaseInsensitiveString JID)
2841 {
2842 RegistryAccount Account = await GetAccount(JID);
2843 Account.NrFailedRemovals++;
2844 }
2845
2846 private async Task SearchHandler(object _, IqEventArgs e)
2847 {
2848 try
2849 {
2850 XmlElement E = e.Query;
2851 XmlElement E2;
2852
2853 int Offset = XML.Attribute(E, "offset", 0);
2854 if (Offset < 0)
2855 {
2856 await e.IqErrorBadRequest(e.To, "Offset must be non-negative.", "en");
2857 return;
2858 }
2859
2860 int MaxCount = XML.Attribute(E, "maxCount", 20);
2861 if (MaxCount > 100)
2862 MaxCount = 100;
2863 else if (MaxCount <= 0)
2864 {
2865 await e.IqErrorBadRequest(e.To, "Maximum count must be positive.", "en");
2866 return;
2867 }
2868
2869 List<SearchOperator> SearchOperators = new List<SearchOperator>();
2870 SearchOperator FirstEquality = null;
2871 object FirstEqualityValue = null;
2872 string Name;
2873
2874 foreach (XmlNode N in E.ChildNodes)
2875 {
2876 E2 = N as XmlElement;
2877 if (E2 is null)
2878 continue;
2879
2880 Name = XML.Attribute(E2, "name").ToUpper();
2881 if (string.IsNullOrEmpty(Name))
2882 {
2883 await e.IqErrorBadRequest(e.To, "Invalid tag name.", "en");
2884 return;
2885 }
2886
2887 if (Name == "KEY")
2888 {
2889 await e.IqResult("<found xmlns='" + e.Query.NamespaceURI + "' more='false'/>", e.To);
2890 await IncAccountFailedSearches(e.From.BareJid);
2891 return;
2892 }
2893
2894 switch (E2.LocalName)
2895 {
2896 case "strEq":
2897 StringTagEqualTo StrOp = new StringTagEqualTo(Name, XML.Attribute(E2, "value"));
2898
2899 SearchOperators.Add(StrOp);
2900
2901 if (FirstEquality is null)
2902 {
2903 FirstEquality = StrOp;
2904 FirstEqualityValue = StrOp.Value;
2905 }
2906 break;
2907
2908 case "strNEq":
2909 SearchOperators.Add(new StringTagNotEqualTo(Name, XML.Attribute(E2, "value")));
2910 break;
2911
2912 case "strGt":
2913 SearchOperators.Add(new StringTagGreaterThan(Name, XML.Attribute(E2, "value")));
2914 break;
2915
2916 case "strGtEq":
2917 SearchOperators.Add(new StringTagGreaterThanOrEqualTo(Name, XML.Attribute(E2, "value")));
2918 break;
2919
2920 case "strLt":
2921 SearchOperators.Add(new StringTagLesserThan(Name, XML.Attribute(E2, "value")));
2922 break;
2923
2924 case "strLtEq":
2925 SearchOperators.Add(new StringTagLesserThanOrEqualTo(Name, XML.Attribute(E2, "value")));
2926 break;
2927
2928 case "strRegEx":
2929 SearchOperators.Add(new StringTagRegEx(Name, XML.Attribute(E2, "value")));
2930 break;
2931
2932 case "strRange":
2933 SearchOperators.Add(new StringTagInRange(Name,
2934 XML.Attribute(E2, "min"),
2935 XML.Attribute(E2, "minIncluded", true),
2936 XML.Attribute(E2, "max"),
2937 XML.Attribute(E2, "maxIncluded", true)));
2938 break;
2939
2940 case "strNRange":
2941 SearchOperators.Add(new StringTagNotInRange(Name,
2942 XML.Attribute(E2, "min"),
2943 XML.Attribute(E2, "minIncluded", true),
2944 XML.Attribute(E2, "max"),
2945 XML.Attribute(E2, "maxIncluded", true)));
2946 break;
2947
2948 case "strMask":
2949 SearchOperators.Add(new StringTagMask(Name,
2950 XML.Attribute(E2, "value"),
2951 XML.Attribute(E2, "wildcard")));
2952 break;
2953
2954 case "numEq":
2955 NumericTagEqualTo NumOp = new NumericTagEqualTo(Name, XML.Attribute(E2, "value", 0.0));
2956
2957 SearchOperators.Add(NumOp);
2958
2959 if (FirstEquality is null)
2960 {
2961 FirstEquality = NumOp;
2962 FirstEqualityValue = NumOp.Value;
2963 }
2964 break;
2965
2966 case "numNEq":
2967 SearchOperators.Add(new NumericTagNotEqualTo(Name, XML.Attribute(E2, "value", 0.0)));
2968 break;
2969
2970 case "numGt":
2971 SearchOperators.Add(new NumericTagGreaterThan(Name, XML.Attribute(E2, "value", 0.0)));
2972 break;
2973
2974 case "numGtEq":
2975 SearchOperators.Add(new NumericTagGreaterThanOrEqualTo(Name, XML.Attribute(E2, "value", 0.0)));
2976 break;
2977
2978 case "numLt":
2979 SearchOperators.Add(new NumericTagLesserThan(Name, XML.Attribute(E2, "value", 0.0)));
2980 break;
2981
2982 case "numLtEq":
2983 SearchOperators.Add(new NumericTagLesserThanOrEqualTo(Name, XML.Attribute(E2, "value", 0.0)));
2984 break;
2985
2986 case "numRange":
2987 SearchOperators.Add(new NumericTagInRange(Name,
2988 XML.Attribute(E2, "min", 0.0),
2989 XML.Attribute(E2, "minIncluded", true),
2990 XML.Attribute(E2, "max", 0.0),
2991 XML.Attribute(E2, "maxIncluded", true)));
2992 break;
2993
2994 case "numNRange":
2995 SearchOperators.Add(new NumericTagNotInRange(Name,
2996 XML.Attribute(E2, "min", 0.0),
2997 XML.Attribute(E2, "minIncluded", true),
2998 XML.Attribute(E2, "max", 0.0),
2999 XML.Attribute(E2, "maxIncluded", true)));
3000 break;
3001
3002 default:
3003 await e.IqResult("<found xmlns='" + e.Query.NamespaceURI + "' more='false'/>", e.To);
3004 await IncAccountFailedSearches(e.From.BareJid);
3005 return;
3006 }
3007 }
3008
3009 if (FirstEquality is null)
3010 {
3011 await e.IqErrorBadRequest(e.To, "Too wide a search. Make sure to include at least one equality operator in the search to limit its scope.", "en");
3012 return;
3013 }
3014
3015 bool Applies;
3016 bool More = false;
3017 List<KeyValuePair<Registration, IEnumerable<MetaDataTag>>> Result = new List<KeyValuePair<Registration, IEnumerable<MetaDataTag>>>();
3018 Dictionary<string, MetaDataTag> TagsSorted = new Dictionary<string, MetaDataTag>(StringComparer.CurrentCultureIgnoreCase);
3019 IEnumerable<MetaDataTag> Tags;
3020
3021 foreach (MetaDataTag Tag in await Database.Find<MetaDataTag>(new FilterAnd(new FilterFieldEqualTo("IsPublic", true),
3022 new FilterFieldEqualTo("Name", FirstEquality.Name), new FilterFieldEqualTo("TagValue", FirstEqualityValue))))
3023 {
3024 Applies = true;
3025
3026 TagsSorted.Clear();
3027 Tags = await LoadTags(Tag.Registration);
3028 foreach (MetaDataTag Tag2 in Tags)
3029 TagsSorted[Tag2.Name] = Tag2;
3030
3031 foreach (SearchOperator Op in SearchOperators)
3032 {
3033 if (!TagsSorted.TryGetValue(Op.Name, out MetaDataTag Tag3) ||
3034 !Op.AppliesTo(Tag3))
3035 {
3036 Applies = false;
3037 break;
3038 }
3039 }
3040
3041 if (!Applies)
3042 continue;
3043
3044 if (Offset > 0)
3045 Offset--;
3046 else if (Result.Count == MaxCount)
3047 {
3048 More = true;
3049 break;
3050 }
3051 else
3052 {
3054 if (R is null)
3055 await Database.DeleteLazy(Tags); // Obsolete tags.
3056 else if (R.IsPublic)
3057 Result.Add(new KeyValuePair<Registration, IEnumerable<MetaDataTag>>(R, Tags));
3058 }
3059 }
3060
3061
3062 await e.IqResult(ResultSet(Result, More, e.Query.NamespaceURI), e.To);
3063
3064 await IncAccountSuccessfulSearches(e.From.BareJid, Result.Count);
3065 }
3066 catch (Exception ex)
3067 {
3068 await e.IqError(ex, e.To);
3069 await IncAccountFailedSearches(e.From.BareJid);
3070 }
3071 }
3072
3073 public static string ResultSet(List<KeyValuePair<Registration, IEnumerable<MetaDataTag>>> Result, bool More, string Namespace)
3074 {
3075 StringBuilder Response = new StringBuilder();
3076
3077 Response.Append("<found xmlns='");
3078 Response.Append(Namespace);
3079 Response.Append("' more='");
3080 Response.Append(CommonTypes.Encode(More));
3081 Response.Append("'>");
3082
3083 foreach (KeyValuePair<Registration, IEnumerable<MetaDataTag>> P in Result)
3084 {
3085 Response.Append("<thing jid='");
3086 Response.Append(XML.Encode(P.Key.JID));
3087
3088 if (!string.IsNullOrEmpty(P.Key.NodeId))
3089 {
3090 Response.Append("' id='");
3091 Response.Append(XML.Encode(P.Key.NodeId));
3092 }
3093
3094 if (!string.IsNullOrEmpty(P.Key.SourceId))
3095 {
3096 Response.Append("' src='");
3097 Response.Append(XML.Encode(P.Key.SourceId));
3098 }
3099
3100 if (!string.IsNullOrEmpty(P.Key.Partition))
3101 {
3102 Response.Append("' pt='");
3103 Response.Append(XML.Encode(P.Key.Partition));
3104 }
3105
3106 Response.Append("'>");
3107
3108 if (!(P.Value is null))
3109 {
3110 foreach (MetaDataTag Tag in P.Value)
3111 {
3112 if (Tag is MetaDataStringTag StrTag)
3113 {
3114 Response.Append("<str name='");
3115 Response.Append(XML.Encode(Tag.Name));
3116 Response.Append("' value='");
3117 Response.Append(XML.Encode(StrTag.TagValue));
3118 Response.Append("'/>");
3119 }
3120 else if (Tag is MetaDataNumericTag NumTag)
3121 {
3122 Response.Append("<num name='");
3123 Response.Append(XML.Encode(Tag.Name));
3124 Response.Append("' value='");
3125 Response.Append(CommonTypes.Encode(NumTag.TagValue));
3126 Response.Append("'/>");
3127 }
3128 }
3129 }
3130
3131 Response.Append("</thing>");
3132 }
3133
3134 Response.Append("</found>");
3135
3136 return Response.ToString();
3137 }
3138
3139 private static async Task IncAccountSuccessfulSearches(CaseInsensitiveString JID, int NrItems)
3140 {
3141 RegistryAccount Account = await GetAccount(JID);
3142 Account.NrSuccessfulSearches++;
3143 Account.NrSearchResultItems += NrItems;
3144 }
3145
3146 private static async Task IncAccountFailedSearches(CaseInsensitiveString JID)
3147 {
3148 RegistryAccount Account = await GetAccount(JID);
3149 Account.NrFailedSearches++;
3150 }
3151
3152 private ThingReference ParseNodeInfo(XmlElement E)
3153 {
3154 string NodeId = XML.Attribute(E, "id");
3155 string SourceId = XML.Attribute(E, "src");
3156 string Partition = XML.Attribute(E, "pt");
3157
3158 if (string.IsNullOrEmpty(NodeId) && string.IsNullOrEmpty(SourceId) && string.IsNullOrEmpty(Partition))
3159 return ThingReference.Empty;
3160 else
3161 return new ThingReference(NodeId, SourceId, Partition);
3162 }
3163
3164 private bool ParseTags(IqEventArgs e, XmlElement E, out Dictionary<string, MetaDataTag> Tags, out string Key)
3165 {
3166 XmlElement E2;
3167
3168 Tags = new Dictionary<string, MetaDataTag>();
3169 Key = null;
3170
3171 foreach (XmlNode N in E.ChildNodes)
3172 {
3173 E2 = N as XmlElement;
3174 if (E2 is null)
3175 continue;
3176
3177 string Name = XML.Attribute(E2, "name").ToUpper();
3178 if (Name.Length > 32)
3179 {
3180 e.IqErrorBadRequest(e.To, "Tag names must not be longer than 32 characters.", "en");
3181 return false;
3182 }
3183
3184 if (Name == "R")
3185 {
3186 e.IqErrorBadRequest(e.To, "The R tag is predefined.", "en");
3187 return false;
3188 }
3189
3190 foreach (char ch in Name)
3191 {
3192 if (ch == ':' || ch == '#' || char.IsWhiteSpace(ch))
3193 {
3194 e.IqErrorBadRequest(e.To, "Illegal character in tag name.", "en");
3195 return false;
3196 }
3197 }
3198
3199 switch (N.LocalName)
3200 {
3201 case "str":
3202 string Value = XML.Attribute(E2, "value");
3203 if (Value.Length > 128)
3204 {
3205 e.IqErrorBadRequest(e.To, "String value must not be longer than 128 characters.", "en");
3206 return false;
3207 }
3208
3209 if (Name == "KEY")
3210 Key = Value;
3211 else
3212 Tags[Name] = new MetaDataStringTag(Name, Value);
3213 break;
3214
3215 case "num":
3216 Tags[Name] = new MetaDataNumericTag(Name, XML.Attribute(E2, "value", 0.0));
3217 break;
3218 }
3219 }
3220
3221 return true;
3222 }
3223
3224 internal static void AppendNodeReference(StringBuilder Request, ThingReference Node)
3225 {
3226 if (!string.IsNullOrEmpty(Node.NodeId))
3227 {
3228 Request.Append("' id='");
3229 Request.Append(XML.Encode(Node.NodeId));
3230 }
3231
3232 if (!string.IsNullOrEmpty(Node.SourceId))
3233 {
3234 Request.Append("' src='");
3235 Request.Append(XML.Encode(Node.SourceId));
3236 }
3237
3238 if (!string.IsNullOrEmpty(Node.Partition))
3239 {
3240 Request.Append("' pt='");
3241 Request.Append(XML.Encode(Node.Partition));
3242 }
3243 }
3244
3245 private async Task DeleteRulesHandler(object Sender, IqEventArgs e)
3246 {
3247 try
3248 {
3249 CaseInsensitiveString Jid = XML.Attribute(e.Query, "jid");
3250 string NodeId = XML.Attribute(e.Query, "id");
3251 string SourceId = XML.Attribute(e.Query, "src");
3252 string Partition = XML.Attribute(e.Query, "pt");
3253 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
3254
3255 if (CaseInsensitiveString.IsNullOrEmpty(Jid) && string.IsNullOrEmpty(NodeId) && string.IsNullOrEmpty(SourceId) && string.IsNullOrEmpty(Partition))
3256 {
3257 Dictionary<CaseInsensitiveString, NamespaceSet> Jids = new Dictionary<CaseInsensitiveString, NamespaceSet>();
3258
3259 foreach (Registration R in await Database.Find<Registration>(new FilterFieldEqualTo("Owner", e.From.BareJid)))
3260 {
3261 await this.DeleteRules(R);
3262 Jids[R.JID] = R.Version;
3263 }
3264
3265 foreach (KeyValuePair<CaseInsensitiveString, NamespaceSet> P in Jids)
3266 await this.ClearCache(P.Key, P.Value);
3267 }
3268 else
3269 {
3270 Registration R = await Database.FindFirstDeleteRest<Registration>(new FilterAnd(
3271 new FilterFieldEqualTo("Owner", e.From.BareJid),
3272 new FilterFieldEqualTo("JID", Jid),
3273 new FilterFieldEqualTo("NodeId", NodeId),
3274 new FilterFieldEqualTo("SourceId", SourceId),
3275 new FilterFieldEqualTo("Partition", Partition)));
3276
3277 if (R is null)
3278 {
3279 await e.IqErrorItemNotFound(e.To, "Device not found, or not owned.", "en");
3280 return;
3281 }
3282
3283 await this.DeleteRules(R);
3284 await this.ClearCache(Jid, R.Version);
3285 }
3286
3287 await e.IqResult(string.Empty, e.To);
3288 }
3289 catch (Exception ex)
3290 {
3291 await e.IqError(ex, e.To);
3292 }
3293 }
3294
3295 #endregion
3296
3297 #region Software Updates
3298
3299 private async Task GetPackageInfoHandler(object Sender, IqEventArgs e)
3300 {
3301 CaseInsensitiveString FileName = XML.Attribute(e.Query, "fileName");
3302 Package Package = await this.GetPackage(FileName, e);
3303 if (Package is null)
3304 return;
3305
3306 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
3307 StringBuilder Xml = new StringBuilder();
3308 Serialize(Package, Xml, QueryVersion);
3309 await e.IqResult(Xml.ToString(), e.To);
3310 }
3311
3312 internal bool CheckSameDomain(IqEventArgs e)
3313 {
3314 if (!this.Server.IsServerDomain(e.From.Domain, true))
3315 {
3316 e.IqErrorForbidden(e.To, "Software updates are not available to domain " + e.From.Domain + ".", "en");
3317 return false;
3318 }
3319 else
3320 return true;
3321 }
3322
3323 internal async Task<Package> GetPackage(CaseInsensitiveString FileName, IqEventArgs e)
3324 {
3325 if (!this.CheckSameDomain(e))
3326 return null;
3327
3329 {
3330 await e.IqErrorBadRequest(e.To, "Missing fileName attribute.", "en");
3331 return null;
3332 }
3333
3334 Package Package = await GetPackage(FileName);
3335 if (Package is null)
3336 {
3337 await e.IqErrorItemNotFound(e.To, "Package with requested file name not found.", "en");
3338 return null;
3339 }
3340
3341 string FullFileName = Path.Combine(XmppServerModule.PackagesFolder, FileName);
3342 if (!File.Exists(FullFileName))
3343 {
3344 await e.IqErrorItemNotFound(e.To, "Package with requested file name not found.", "en");
3345 return null;
3346 }
3347
3348 return Package;
3349 }
3350
3351
3352 private static readonly Dictionary<CaseInsensitiveString, Package> packages = new Dictionary<CaseInsensitiveString, Package>();
3353 private static bool packagesLoaded = false;
3354
3355 internal static async Task<Package> GetPackage(CaseInsensitiveString FileName)
3356 {
3358
3359 lock (packages)
3360 {
3361 if (!packages.TryGetValue(FileName, out Package))
3362 Package = null;
3363 }
3364
3365 if (Package is null)
3366 {
3367 Package = await Database.FindFirstDeleteRest<Package>(new FilterFieldEqualTo("FileName", FileName));
3368 if (Package is null)
3369 return null;
3370
3371 lock (packages)
3372 {
3373 packages[FileName] = Package;
3374 }
3375 }
3376
3377 return Package;
3378 }
3379
3380 private static void Serialize(Package Package, StringBuilder Xml, NamespaceSet? Version)
3381 {
3382 Serialize(Package, "packageInfo", Xml, Version);
3383 }
3384
3385 private static void Serialize(Package Package, string LocalName, StringBuilder Xml, NamespaceSet? Version)
3386 {
3387 Xml.Append('<');
3388 Xml.Append(LocalName);
3389 Xml.Append(" fileName='");
3390 Xml.Append(XML.Encode(Package.FileName));
3391 Xml.Append("' signature='");
3392 Xml.Append(Convert.ToBase64String(Package.Signature));
3393 Xml.Append("' published='");
3394 Xml.Append(XML.Encode(Package.Published));
3395
3396 if (Package.Supersedes != DateTime.MinValue)
3397 {
3398 Xml.Append("' supersedes='");
3399 Xml.Append(XML.Encode(Package.Supersedes));
3400 }
3401
3402 Xml.Append("' created='");
3403 Xml.Append(XML.Encode(Package.Created));
3404 Xml.Append("' url='");
3405 Xml.Append(Package.Url);
3406 Xml.Append("' bytes='");
3407 Xml.Append(Package.Bytes.ToString());
3408
3409 if (Version.HasValue)
3410 {
3411 Xml.Append("' xmlns='");
3412 Xml.Append(NamespaceSoftwareUpdates(Version.Value));
3413 }
3414
3415 Xml.Append("'/>");
3416 }
3417
3418 private async Task GetPackagesHandler(object Sender, IqEventArgs e)
3419 {
3420 StringBuilder Xml = new StringBuilder();
3421
3422 Xml.Append("<packages xmlns='");
3423 Xml.Append(e.Query.NamespaceURI);
3424 Xml.Append("'>");
3425
3426 foreach (Package Package in await GetPackages())
3427 {
3428 string FullFileName = Path.Combine(XmppServerModule.PackagesFolder, Package.FileName);
3429 if (!File.Exists(FullFileName))
3430 continue;
3431
3432 Serialize(Package, Xml, null);
3433 }
3434
3435 Xml.Append("</packages>");
3436
3437 await e.IqResult(Xml.ToString(), e.To);
3438 }
3439
3440 public static async Task<Package[]> GetPackages()
3441 {
3442 if (!packagesLoaded)
3443 {
3444 foreach (Package Package in await Database.Find<Package>("FileName"))
3445 {
3446 lock (packages)
3447 {
3448 packages[Package.FileName] = Package;
3449 }
3450 }
3451
3452 packagesLoaded = true;
3453 }
3454
3455 lock (packages)
3456 {
3457 Package[] Result = new Package[packages.Count];
3458 packages.Values.CopyTo(Result, 0);
3459 return Result;
3460 }
3461 }
3462
3463 internal async Task NewPackage(Package Package)
3464 {
3465 lock (packages)
3466 {
3467 packages[Package.FileName] = Package;
3468 }
3469
3470 string Serializer(NamespaceSet Version)
3471 {
3472 StringBuilder Xml = new StringBuilder();
3473 Serialize(Package, Xml, Version);
3474 return Xml.ToString();
3475 };
3476
3477 await this.Notify(Package, Serializer);
3478 await this.Notify(null, Serializer);
3479 }
3480
3481 internal async Task PackageDeleted(Package Package)
3482 {
3483 lock (packages)
3484 {
3485 packages.Remove(Package.FileName);
3486 }
3487
3488 string Serializer(NamespaceSet Version)
3489 {
3490 StringBuilder Xml = new StringBuilder();
3491
3492 Xml.Append("<packageDeleted fileName='");
3493 Xml.Append(XML.Encode(Package.FileName));
3494 Xml.Append("' xmlns='");
3495 Xml.Append(NamespaceSoftwareUpdates(Version));
3496 Xml.Append("'/>");
3497
3498 return Xml.ToString();
3499 };
3500
3501 await this.Notify(Package, Serializer);
3502 await this.Notify(null, Serializer);
3503 }
3504
3505 private delegate string SerializePackageDelegate(NamespaceSet Version);
3506
3507 private async Task Notify(Package Package, SerializePackageDelegate Serializer)
3508 {
3509 Dictionary<CaseInsensitiveString, bool> Sent = new Dictionary<CaseInsensitiveString, bool>();
3510
3511 foreach (PackageNotification Notification in await Database.Find<PackageNotification>(
3512 new FilterFieldEqualTo("FileName", Package?.FileName ?? "*")))
3513 {
3514 if (string.Compare(Notification.BareJid, Gateway.XmppClient.BareJID, true) == 0)
3515 continue;
3516
3517 XmppAddress To = new XmppAddress(Notification.BareJid);
3518 XmppAddress From = new XmppAddress(Notification.Domain ?? this.MainDomain.Address);
3519
3520 if (!Sent.ContainsKey(To.Address))
3521 {
3522 string Xml = Serializer(Notification.Version);
3523 await XmppServerModule.Server.SendMessage(string.Empty, string.Empty, From, To, string.Empty, Xml);
3524 Sent[To.Address] = true;
3525 }
3526 }
3527 }
3528
3529 private async Task SubscribeHandler(object Sender, IqEventArgs e)
3530 {
3531 if (!this.CheckSameDomain(e))
3532 return;
3533
3534 CaseInsensitiveString FileName = XML.Attribute(e.Query, "fileName");
3535 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
3536
3537 if (FileName == "*")
3538 {
3539 LinkedList<object> ToDelete = null;
3540 bool Found = false;
3541
3542 foreach (PackageNotification Notification in await Database.Find<PackageNotification>(new FilterFieldEqualTo("BareJid", e.From.BareJid)))
3543 {
3544 if (Notification.FileName == FileName)
3545 Found = true;
3546 else
3547 {
3548 if (ToDelete is null)
3549 ToDelete = new LinkedList<object>();
3550
3551 ToDelete.AddLast(Notification);
3552 }
3553 }
3554
3555 if (!Found)
3556 {
3557 PackageNotification Notification = new PackageNotification()
3558 {
3559 FileName = FileName,
3560 BareJid = e.From.BareJid,
3561 Domain = e.To.Domain,
3562 Version = QueryVersion
3563 };
3564
3565 await Database.InsertLazy(Notification);
3566 }
3567
3568 if (!(ToDelete is null))
3569 await Database.DeleteLazy(ToDelete);
3570 }
3571 else
3572 {
3573 Package Package = await this.GetPackage(FileName, e);
3574 if (Package is null)
3575 {
3576 await e.IqErrorItemNotFound(e.To, "No such software package found.", "en");
3577 return;
3578 }
3579
3580 PackageNotification Notification = await Database.FindFirstDeleteRest<PackageNotification>(new FilterAnd(
3581 new FilterFieldEqualTo("FileName", FileName),
3582 new FilterFieldEqualTo("BareJid", e.From.BareJid)));
3583
3584 if (Notification is null)
3585 {
3586 int Count = 0;
3587
3588 foreach (PackageNotification Subscription in await Database.Find<PackageNotification>(new FilterFieldEqualTo("BareJid", e.From.BareJid)))
3589 Count++;
3590
3591 if (Count > 10)
3592 {
3593 await e.IqErrorNotAllowed(e.To, "Maximum number of subscriptions reached. Either unsubscribe, or use a wildcard subscription.", "en");
3594 return;
3595 }
3596
3597 Notification = new PackageNotification()
3598 {
3599 FileName = FileName,
3600 BareJid = e.From.BareJid,
3601 Domain = e.To.Domain,
3602 Version = QueryVersion
3603 };
3604
3605 await Database.InsertLazy(Notification);
3606 }
3607 }
3608
3609 await e.IqResult(string.Empty, e.To);
3610 }
3611
3612 private async Task UnsubscribeHandler(object Sender, IqEventArgs e)
3613 {
3614 if (!this.CheckSameDomain(e))
3615 return;
3616
3617 CaseInsensitiveString FileName = XML.Attribute(e.Query, "fileName");
3618
3619 if (FileName == "*")
3621 else
3622 {
3623 Package Package = await this.GetPackage(FileName, e);
3624 if (!(Package is null))
3625 {
3626 PackageNotification Notification = await Database.FindFirstDeleteRest<PackageNotification>(new FilterAnd(
3627 new FilterFieldEqualTo("FileName", FileName),
3628 new FilterFieldEqualTo("BareJid", e.From.BareJid)));
3629
3630 if (!(Notification is null))
3631 await Database.DeleteLazy(Notification);
3632 }
3633 }
3634
3635 await e.IqResult(string.Empty, e.To);
3636 }
3637
3638 private async Task GetSubscriptionsHandler(object Sender, IqEventArgs e)
3639 {
3640 if (!this.CheckSameDomain(e))
3641 return;
3642
3643 StringBuilder Xml = new StringBuilder();
3644
3645 Xml.Append("<subscriptions xmlns='");
3646 Xml.Append(e.Query.NamespaceURI);
3647 Xml.Append("'>");
3648
3649 foreach (PackageNotification Notification in await Database.Find<PackageNotification>(
3650 new FilterFieldEqualTo("BareJid", e.From.BareJid)))
3651 {
3652 Xml.Append("<subscription>");
3653 Xml.Append(XML.Encode(Notification.FileName));
3654 Xml.Append("</subscription>");
3655 }
3656
3657 Xml.Append("</subscriptions>");
3658
3659 await e.IqResult(Xml.ToString(), e.To);
3660 }
3661
3662 #endregion
3663
3664 }
3665}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static string Encode(bool x)
Encodes a Boolean for use in XML and other formats.
Definition: CommonTypes.cs:594
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:46
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 Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
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 DateTime ScheduleEvent(ScheduledEventCallback Callback, DateTime When, object State)
Schedules a one-time event.
Definition: Gateway.cs:3452
static XmppClient XmppClient
XMPP Client connection of gateway.
Definition: Gateway.cs:3187
Base class for components.
Definition: Component.cs:16
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
void RegisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Get handler.
Definition: Component.cs:149
readonly object synchObject
Synchronization object for thread-safe access to internal structures.
Definition: Component.cs:25
bool IsComponentDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the component domain, or optionally, an alternative component domain.
Definition: Component.cs:123
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
Event arguments for IQ queries.
Definition: IqEventArgs.cs:12
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 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
Event arguments for responses to IQ queries.
object State
State object passed to the original request.
XmppAddress From
From address attribute
XmlElement FirstElement
First child element of the Response element.
bool Ok
If the response is an OK result response (true), or an error response (false).
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
override string ToString()
object.ToString()
Definition: XmppAddress.cs:190
bool IsEmpty
If the address is empty.
Definition: XmppAddress.cs:183
CaseInsensitiveString Domain
Domain
Definition: XmppAddress.cs:97
CaseInsensitiveString Address
XMPP Address
Definition: XmppAddress.cs:37
CaseInsensitiveString BareJid
Bare JID
Definition: XmppAddress.cs:45
static readonly XmppAddress Empty
Empty address.
Definition: XmppAddress.cs:31
Task< bool > SendMessage(string Type, string Id, string From, string To, string Language, string ContentXml)
Sends a Message stanza to a recipient.
Definition: XmppServer.cs:3412
static byte[] GetRandomNumbers(int NrBytes)
Generates a set of random numbers.
Definition: XmppServer.cs:672
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
int IndexOf(CaseInsensitiveString value, StringComparison comparisonType)
Reports the zero-based index of the first occurrence of the specified string in the current System....
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
CaseInsensitiveString Substring(int startIndex, int length)
Retrieves a substring from this instance. The substring starts at a specified character position and ...
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs: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 async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:626
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:95
static Task< object > TryLoadObject(string CollectionName, object ObjectId)
Tries to load an object given its Object ID ObjectId and its collection name CollectionName .
Definition: Database.cs:1079
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
Implements an in-memory cache.
Definition: Cache.cs:15
bool ContainsKey(KeyType Key)
Checks if a key is available in the cache.
Definition: Cache.cs:296
void Dispose()
IDisposable.Dispose
Definition: Cache.cs:74
bool Remove(KeyType Key)
Removes an item from the cache.
Definition: Cache.cs:451
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
Definition: Cache.cs:203
void Add(KeyType Key, ValueType Value)
Adds an item to the cache.
Definition: Cache.cs:338
void Clear()
Clears the cache.
Definition: Cache.cs:484
Event arguments for cache item removal events.
ValueType Value
Value of item that was removed.
const string NamespaceProvisioningOwnerIeeeV1
urn:ieee:iot:prov:o:1.0
async Task ClearCache(CaseInsensitiveString BareJid, NamespaceSet Version)
Notifies the entity its rule cache should be cleared.
const string NamespaceProvisioningTokenNeuroFoundationV1
urn:nf:iot:prov:t:1.0
static string NamespaceProvisioningOwner(NamespaceSet Version)
Returns the namespace for owner provisioning.
static string NamespaceSoftwareUpdates(NamespaceSet Version)
Returns the namespace for Software Updates.
static string NamespaceProvisioningToken(NamespaceSet Version)
Returns the namespace for token provisioning.
bool TryGetCertificate(string Token, out X509Certificate2 Certificate)
Tries to get a certificate for a given token.
const string NamespaceIoTDiscoveryNeuroFoundationV1
urn:nf:iot:disco:1.0
const string NamespaceProvisioningTokenIeeeV1
urn:ieee:iot:prov:t:1.0
ProvisioningComponent(XmppServer Server, CaseInsensitiveString Subdomain, string Name)
Provisioning and registry service component.
const string NamespaceProvisioningDeviceIeeeV1
urn:ieee:iot:prov:d:1.0
override bool SupportsAccounts
If the component supports accounts (true), or if the subdomain name is the only valid address.
static string NamespaceIoTDiscovery(NamespaceSet Version)
Returns the namespace for IoT Discovery.
static string NamespaceProvisioningDevice(NamespaceSet Version)
Returns the namespace for device provisioning.
async Task RecommendBefriend(CaseInsensitiveString BareJid1, CaseInsensitiveString BareJid2, NamespaceSet Version)
Notifies the entity identified by BareJid1 , that it should befriend (subscribe to presence from) the...
async Task RecommendUnfriend(CaseInsensitiveString BareJid1, CaseInsensitiveString BareJid2, NamespaceSet Version)
Notifies the entity identified by BareJid1 , that it should remove friendship (presence subscription ...
const string NamespaceProvisioningOwnerNeuroFoundationV1
urn:nf:iot:prov:o:1.0
const string NamespaceProvisioningDeviceNeuroFoundationV1
urn:nf:iot:prov:d:1.0
Guid ObjectId
Persisted object ID. Is null if object not persisted.
Definition: Registration.cs:39
string Partition
Optional partition in which the Node ID is unique.
Definition: Registration.cs:75
string SourceId
Optional ID of source containing node.
Definition: Registration.cs:66
DateTime FirstRegistration
Timestamp of first registration.
long NrSuccessfulDisownments
Number of successful disownments.
Controls if a device with a remote JID is allowed to control a device with JID.
Definition: ControlRule.cs:11
Controls if a device with a remote JID is allowed to subscribe to the presence of a device with JID.
Controls if a device with a remote JID is allowed to read data from a device with JID.
Definition: ReadoutRule.cs:11
Abstract base class for rules.
Definition: Rule.cs:12
void AddChildRule(Rule Rule)
Adds a child rule.
Definition: Rule.cs:63
virtual ? bool Evaluate(Context Context)
Tries to evaluate the rule.
Definition: Rule.cs:42
Filters things with a named numeric-valued tag equal to a given value.
Filters things with a named numeric-valued tag greater than a given value.
Filters things with a named numeric-valued tag greater than or equal to a given value.
Filters things with a named numeric-valued tag within a given range.
Filters things with a named numeric-valued tag lesser than a given value.
Filters things with a named numeric-valued tag lesser than or equal to a given value.
Filters things with a named numeric-valued tag not equal to a given value.
Filters things with a named numeric-valued tag outside a given range.
abstract bool AppliesTo(MetaDataTag Tag)
Checks if the operator applies to a tag.
Filters things with a named string-valued tag equal to a given value.
Filters things with a named string-valued tag greater than a given value.
Filters things with a named string-valued tag greater than or equal to a given value.
Filters things with a named string-valued tag within a given range.
Filters things with a named string-valued tag lesser than a given value.
Filters things with a named string-valued tag lesser than or equal to a given value.
Filters things with a named string-valued tag like a given value.
Filters things with a named string-valued tag not equal to a given value.
Filters things with a named string-valued tag outside a given range.
Filters things with a named string-valued tag matching a regular expression.
Contains information about a software package.
Definition: Package.cs:18
byte[] Signature
Cryptographic signature of package, as calculated by the issuer of the package.
Definition: Package.cs:46
CaseInsensitiveString FileName
Filename of package.
Definition: Package.cs:40
DateTime Published
When package was published.
Definition: Package.cs:76
DateTime Supersedes
Timestamp of superceded package.
Definition: Package.cs:82
DateTime Created
When package record was created
Definition: Package.cs:88
override string StringValue
String-representation of meta-data tag value.
Abstract base class for all meta-data tags.
Definition: MetaDataTag.cs:15
abstract bool IsEmpty
If the tag value is empty.
Definition: MetaDataTag.cs:91
abstract object Value
Meta-data tag value.
Definition: MetaDataTag.cs:86
Service Module hosting the XMPP broker and its components.
static NamespaceSet GetVersion(string Namespace)
Gets the namespace set version corresponding to a given a namespace.
Base class for all sensor data fields.
Definition: Field.cs:20
Contains a reference to a thing
static ThingReference Empty
Empty thing reference. Can be used by sensors that are not part of a concentrator during readout.
string NodeId
ID of node.
string Partition
Optional partition in which the Node ID is unique.
bool IsEmpty
If the reference is an empty reference.
string SourceId
Optional ID of source containing node.
NamespaceSet
Namespace versions
Definition: NamespaceSet.cs:7
FieldType
Field Type flags
Definition: FieldType.cs:10