|
@@ -69,6 +69,7 @@ type Client struct {
|
69
|
69
|
rawHostname string
|
70
|
70
|
realname string
|
71
|
71
|
registered bool
|
|
72
|
+ resumeDetails *ResumeDetails
|
72
|
73
|
saslInProgress bool
|
73
|
74
|
saslMechanism string
|
74
|
75
|
saslValue string
|
|
@@ -294,11 +295,109 @@ func (client *Client) Register() {
|
294
|
295
|
return
|
295
|
296
|
}
|
296
|
297
|
|
|
298
|
+ // apply resume details if we're able to.
|
|
299
|
+ client.TryResume()
|
|
300
|
+
|
|
301
|
+ // finish registration
|
297
|
302
|
client.Touch()
|
298
|
303
|
client.updateNickMask("")
|
299
|
304
|
client.server.monitorManager.AlertAbout(client, true)
|
300
|
305
|
}
|
301
|
306
|
|
|
307
|
+// TryResume tries to resume if the client asked us to.
|
|
308
|
+func (client *Client) TryResume() {
|
|
309
|
+ if client.resumeDetails == nil {
|
|
310
|
+ return
|
|
311
|
+ }
|
|
312
|
+
|
|
313
|
+ server := client.server
|
|
314
|
+
|
|
315
|
+ // just grab these mutexes for safety. later we can work out whether we can grab+release them earlier
|
|
316
|
+ server.clients.Lock()
|
|
317
|
+ defer server.clients.Unlock()
|
|
318
|
+ server.channels.Lock()
|
|
319
|
+ defer server.channels.Unlock()
|
|
320
|
+
|
|
321
|
+ oldnick := client.resumeDetails.OldNick
|
|
322
|
+ timestamp := client.resumeDetails.Timestamp
|
|
323
|
+ var timestampString string
|
|
324
|
+ if timestamp != nil {
|
|
325
|
+ timestampString := timestamp.UTC().Format("2006-01-02T15:04:05.999Z")
|
|
326
|
+ }
|
|
327
|
+
|
|
328
|
+ oldClient := server.clients.Get(oldnick)
|
|
329
|
+ if oldClient == nil {
|
|
330
|
+ client.Send(nil, server.name, ERR_CANNOT_RESUME, oldnick, "Cannot resume connection, old client not found")
|
|
331
|
+ return
|
|
332
|
+ }
|
|
333
|
+
|
|
334
|
+ oldAccountName := oldClient.AccountName()
|
|
335
|
+ newAccountName := client.AccountName()
|
|
336
|
+
|
|
337
|
+ if oldAccountName == "" || newAccountName == "" || oldAccountName != newAccountName {
|
|
338
|
+ client.Send(nil, server.name, ERR_CANNOT_RESUME, oldnick, "Cannot resume connection, old and new clients must be logged into the same account")
|
|
339
|
+ return
|
|
340
|
+ }
|
|
341
|
+
|
|
342
|
+ if !oldClient.HasMode(TLS) || !client.HasMode(TLS) {
|
|
343
|
+ client.Send(nil, server.name, ERR_CANNOT_RESUME, oldnick, "Cannot resume connection, old and new clients must have TLS")
|
|
344
|
+ return
|
|
345
|
+ }
|
|
346
|
+
|
|
347
|
+ // send RESUMED to the reconnecting client
|
|
348
|
+ if timestamp == nil {
|
|
349
|
+ client.Send(nil, oldClient.NickMaskString(), "RESUMED", oldClient.nick, client.username, client.Hostname())
|
|
350
|
+ } else {
|
|
351
|
+ client.Send(nil, oldClient.NickMaskString(), "RESUMED", oldClient.nick, client.username, client.Hostname(), timestampString)
|
|
352
|
+ }
|
|
353
|
+
|
|
354
|
+ // send QUIT/RESUMED to friends
|
|
355
|
+ for friend := range oldClient.Friends() {
|
|
356
|
+ if friend.capabilities.Has(caps.Resume) {
|
|
357
|
+ if timestamp == nil {
|
|
358
|
+ friend.Send(nil, oldClient.NickMaskString(), "RESUMED", oldClient.nick, client.username, client.Hostname())
|
|
359
|
+ } else {
|
|
360
|
+ friend.Send(nil, oldClient.NickMaskString(), "RESUMED", oldClient.nick, client.username, client.Hostname(), timestampString)
|
|
361
|
+ }
|
|
362
|
+ } else {
|
|
363
|
+ friend.Send(nil, oldClient.NickMaskString(), "QUIT", "Client reconnected")
|
|
364
|
+ }
|
|
365
|
+ }
|
|
366
|
+
|
|
367
|
+ // apply old client's details to new client
|
|
368
|
+ client.nick = oldClient.nick
|
|
369
|
+
|
|
370
|
+ for channel := range oldClient.channels {
|
|
371
|
+ channel.stateMutex.Lock()
|
|
372
|
+
|
|
373
|
+ oldModeSet := channel.members[oldClient]
|
|
374
|
+ channel.members.Remove(oldClient)
|
|
375
|
+ channel.members[client] = oldModeSet
|
|
376
|
+ channel.regenerateMembersCache()
|
|
377
|
+
|
|
378
|
+ // send join for old clients
|
|
379
|
+ for member := range channel.members {
|
|
380
|
+ if member.capabilities.Has(caps.Resume) {
|
|
381
|
+ continue
|
|
382
|
+ }
|
|
383
|
+
|
|
384
|
+ if member.capabilities.Has(caps.ExtendedJoin) {
|
|
385
|
+ member.Send(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname)
|
|
386
|
+ } else {
|
|
387
|
+ member.Send(nil, client.nickMaskString, "JOIN", channel.name)
|
|
388
|
+ }
|
|
389
|
+
|
|
390
|
+ //TODO(dan): send priv modes
|
|
391
|
+ }
|
|
392
|
+
|
|
393
|
+ channel.stateMutex.Unlock()
|
|
394
|
+ }
|
|
395
|
+
|
|
396
|
+ server.clients.byNick[oldnick] = client
|
|
397
|
+
|
|
398
|
+ oldClient.destroy()
|
|
399
|
+}
|
|
400
|
+
|
302
|
401
|
// IdleTime returns how long this client's been idle.
|
303
|
402
|
func (client *Client) IdleTime() time.Duration {
|
304
|
403
|
client.stateMutex.RLock()
|
|
@@ -494,7 +593,7 @@ func (client *Client) Quit(message string) {
|
494
|
593
|
}
|
495
|
594
|
|
496
|
595
|
// destroy gets rid of a client, removes them from server lists etc.
|
497
|
|
-func (client *Client) destroy() {
|
|
596
|
+func (client *Client) destroy(beingResumed bool) {
|
498
|
597
|
// allow destroy() to execute at most once
|
499
|
598
|
client.stateMutex.Lock()
|
500
|
599
|
isDestroyed := client.isDestroyed
|
|
@@ -504,14 +603,20 @@ func (client *Client) destroy() {
|
504
|
603
|
return
|
505
|
604
|
}
|
506
|
605
|
|
507
|
|
- client.server.logger.Debug("quit", fmt.Sprintf("%s is no longer on the server", client.nick))
|
|
606
|
+ if beingResumed {
|
|
607
|
+ client.server.logger.Debug("quit", fmt.Sprintf("%s is being resumed", client.nick))
|
|
608
|
+ } else {
|
|
609
|
+ client.server.logger.Debug("quit", fmt.Sprintf("%s is no longer on the server", client.nick))
|
|
610
|
+ }
|
508
|
611
|
|
509
|
612
|
// send quit/error message to client if they haven't been sent already
|
510
|
613
|
client.Quit("Connection closed")
|
511
|
614
|
|
512
|
|
- client.server.whoWas.Append(client)
|
513
|
615
|
friends := client.Friends()
|
514
|
616
|
friends.Remove(client)
|
|
617
|
+ if !beingResumed {
|
|
618
|
+ client.server.whoWas.Append(client)
|
|
619
|
+ }
|
515
|
620
|
|
516
|
621
|
// remove from connection limits
|
517
|
622
|
ipaddr := client.IP()
|
|
@@ -527,14 +632,18 @@ func (client *Client) destroy() {
|
527
|
632
|
|
528
|
633
|
// clean up channels
|
529
|
634
|
for _, channel := range client.Channels() {
|
530
|
|
- channel.Quit(client)
|
|
635
|
+ if !beingResumed {
|
|
636
|
+ channel.Quit(client)
|
|
637
|
+ }
|
531
|
638
|
for _, member := range channel.Members() {
|
532
|
639
|
friends.Add(member)
|
533
|
640
|
}
|
534
|
641
|
}
|
535
|
642
|
|
536
|
643
|
// clean up server
|
537
|
|
- client.server.clients.Remove(client)
|
|
644
|
+ if !beingResumed {
|
|
645
|
+ client.server.clients.Remove(client)
|
|
646
|
+ }
|
538
|
647
|
|
539
|
648
|
// clean up self
|
540
|
649
|
if client.idletimer != nil {
|
|
@@ -544,14 +653,20 @@ func (client *Client) destroy() {
|
544
|
653
|
client.socket.Close()
|
545
|
654
|
|
546
|
655
|
// send quit messages to friends
|
547
|
|
- for friend := range friends {
|
548
|
|
- if client.quitMessage == "" {
|
549
|
|
- client.quitMessage = "Exited"
|
|
656
|
+ if !beingResumed {
|
|
657
|
+ for friend := range friends {
|
|
658
|
+ if client.quitMessage == "" {
|
|
659
|
+ client.quitMessage = "Exited"
|
|
660
|
+ }
|
|
661
|
+ friend.Send(nil, client.nickMaskString, "QUIT", client.quitMessage)
|
550
|
662
|
}
|
551
|
|
- friend.Send(nil, client.nickMaskString, "QUIT", client.quitMessage)
|
552
|
663
|
}
|
553
|
664
|
if !client.exitedSnomaskSent {
|
554
|
|
- client.server.snomasks.Send(sno.LocalQuits, fmt.Sprintf(ircfmt.Unescape("%s$r exited the network"), client.nick))
|
|
665
|
+ if beingResumed {
|
|
666
|
+ client.server.snomasks.Send(sno.LocalQuits, fmt.Sprintf(ircfmt.Unescape("%s$r is resuming their connection, old client has been destroyed"), client.nick))
|
|
667
|
+ } else {
|
|
668
|
+ client.server.snomasks.Send(sno.LocalQuits, fmt.Sprintf(ircfmt.Unescape("%s$r exited the network"), client.nick))
|
|
669
|
+ }
|
555
|
670
|
}
|
556
|
671
|
}
|
557
|
672
|
|