import (
"bufio"
- "bytes"
"context"
"encoding/binary"
"errors"
name string
}
-const (
- userIdleSeconds = 300 // time in seconds before an inactive user is marked idle
- idleCheckInterval = 10 // time in seconds to check for idle users
- trackerUpdateFrequency = 300 // time in seconds between tracker re-registration
-)
-
-var nostalgiaVersion = []byte{0, 0, 2, 0x2c} // version ID used by the Nostalgia client
-
type Server struct {
Port int
Accounts map[string]*Account
s.mux.Lock()
client := s.Clients[uint16(clientID)]
+ s.mux.Unlock()
if client == nil {
return fmt.Errorf("invalid client id %v", *t.clientID)
}
- s.mux.Unlock()
-
b, err := t.MarshalBinary()
if err != nil {
return err
}
- if _, err := client.Connection.Write(b); err != nil {
+ _, err = client.Connection.Write(b)
+ if err != nil {
return err
}
Version: []byte{},
AutoReply: []byte{},
transfers: map[int]map[[4]byte]*FileTransfer{},
- Agreed: false,
RemoteAddr: remoteAddr,
}
clientConn.transfers = map[int]map[[4]byte]*FileTransfer{
var connectedUsers []Field
for _, c := range sortedClients(s.Clients) {
- if !c.Agreed {
- continue
- }
user := User{
ID: *c.ID,
Icon: c.Icon,
scanner.Scan()
+ // Make a new []byte slice and copy the scanner bytes to it. This is critical to avoid a data race as the
+ // scanner re-uses the buffer for subsequent scans.
+ buf := make([]byte, len(scanner.Bytes()))
+ copy(buf, scanner.Bytes())
+
var clientLogin Transaction
- if _, err := clientLogin.Write(scanner.Bytes()); err != nil {
+ if _, err := clientLogin.Write(buf); err != nil {
return err
}
c.Server.outbox <- *NewTransaction(tranShowAgreement, c.ID, NewField(fieldData, s.Agreement))
}
- // Used simplified hotline v1.2.3 login flow for clients that do not send login info in tranAgreed
- if c.Version == nil || bytes.Equal(c.Version, nostalgiaVersion) {
- c.Agreed = true
+ // If the client has provided a username as part of the login, we can infer that it is using the 1.2.3 login
+ // flow and not the 1.5+ flow.
+ if len(c.UserName) != 0 {
+ // Add the client username to the logger. For 1.5+ clients, we don't have this information yet as it comes as
+ // part of tranAgreed
c.logger = c.logger.With("name", string(c.UserName))
- c.logger.Infow("Login successful", "clientVersion", fmt.Sprintf("%v", func() int { i, _ := byteToInt(c.Version); return i }()))
+ c.logger.Infow("Login successful", "clientVersion", "Not sent (probably 1.2.3)")
+
+ // Notify other clients on the server that the new user has logged in. For 1.5+ clients we don't have this
+ // information yet, so we do it in tranAgreed instead
for _, t := range c.notifyOthers(
*NewTransaction(
tranNotifyChangeUser, nil,
delete(s.fileTransfers, t.ReferenceNumber)
s.mux.Unlock()
+ // Wait a few seconds before closing the connection: this is a workaround for problems
+ // observed with Windows clients where the client must initiate close of the TCP connection before
+ // the server does. This is gross and seems unnecessary. TODO: Revisit?
+ time.Sleep(3 * time.Second)
}()
s.mux.Lock()
case FileDownload:
s.Stats.DownloadCounter += 1
s.Stats.DownloadsInProgress += 1
- defer func() { s.Stats.DownloadsInProgress -= 1 }()
+ defer func() {
+ s.Stats.DownloadsInProgress -= 1
+ }()
var dataOffset int64
if fileTransfer.fileResumeData != nil {