import (
"bufio"
+ "bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"github.com/go-playground/validator/v10"
"go.uber.org/zap"
+ "golang.org/x/text/encoding/charmap"
"gopkg.in/yaml.v3"
"io"
"io/fs"
"math/rand"
"net"
"os"
+ "path"
"path/filepath"
"strings"
"sync"
remoteAddr string
}
+// Converts bytes from Mac Roman encoding to UTF-8
+var txtDecoder = charmap.Macintosh.NewDecoder()
+
+// Converts bytes from UTF-8 to Mac Roman encoding
+var txtEncoder = charmap.Macintosh.NewEncoder()
+
type Server struct {
NetInterface string
Port int
Config *Config
ConfigDir string
Logger *zap.SugaredLogger
+ banner []byte
PrivateChatsMu sync.Mutex
PrivateChats map[uint32]*PrivateChat
server.Config.FileRoot = filepath.Join(configDir, server.Config.FileRoot)
}
+ server.banner, err = os.ReadFile(filepath.Join(server.ConfigDir, server.Config.BannerFile))
+ if err != nil {
+ return nil, fmt.Errorf("error opening banner: %w", err)
+ }
+
*server.NextGuestID = 1
if server.Config.EnableTrackerRegistration {
for {
tr := &TrackerRegistration{
UserCount: server.userCount(),
- PassID: server.TrackerPassID[:],
+ PassID: server.TrackerPassID,
Name: server.Config.Name,
Description: server.Config.Description,
}
Server: s,
Version: []byte{},
AutoReply: []byte{},
- transfers: map[int]map[[4]byte]*FileTransfer{},
RemoteAddr: remoteAddr,
- }
- clientConn.transfers = map[int]map[[4]byte]*FileTransfer{
- FileDownload: {},
- FileUpload: {},
- FolderDownload: {},
- FolderUpload: {},
- bannerDownload: {},
+ transfers: map[int]map[[4]byte]*FileTransfer{
+ FileDownload: {},
+ FileUpload: {},
+ FolderDownload: {},
+ FolderUpload: {},
+ bannerDownload: {},
+ },
}
*s.NextGuestID++
if err != nil {
return err
}
+
+ // Create account file, returning an error if one already exists.
+ file, err := os.OpenFile(
+ filepath.Join(s.ConfigDir, "Users", path.Join("/", login)+".yaml"),
+ os.O_CREATE|os.O_EXCL|os.O_WRONLY,
+ 0644,
+ )
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ _, err = file.Write(out)
+ if err != nil {
+ return fmt.Errorf("error writing account file: %w", err)
+ }
+
s.Accounts[login] = &account
- return s.FS.WriteFile(filepath.Join(s.ConfigDir, "Users", login+".yaml"), out, 0666)
+ return nil
}
func (s *Server) UpdateUser(login, newLogin, name, password string, access accessBitmap) error {
// update renames the user login
if login != newLogin {
- err := os.Rename(filepath.Join(s.ConfigDir, "Users", login+".yaml"), filepath.Join(s.ConfigDir, "Users", newLogin+".yaml"))
+ err := os.Rename(filepath.Join(s.ConfigDir, "Users", path.Join("/", login)+".yaml"), filepath.Join(s.ConfigDir, "Users", path.Join("/", newLogin)+".yaml"))
if err != nil {
- return err
+ return fmt.Errorf("unable to rename account: %w", err)
}
s.Accounts[newLogin] = s.Accounts[login]
+ s.Accounts[newLogin].Login = newLogin
delete(s.Accounts, login)
}
s.mux.Lock()
defer s.mux.Unlock()
+ err := s.FS.Remove(filepath.Join(s.ConfigDir, "Users", path.Join("/", login)+".yaml"))
+ if err != nil {
+ return err
+ }
+
delete(s.Accounts, login)
- return s.FS.Remove(filepath.Join(s.ConfigDir, "Users", login+".yaml"))
+ return nil
}
func (s *Server) connectedUsers() []Field {
var connectedUsers []Field
for _, c := range sortedClients(s.Clients) {
- user := User{
+ b, err := io.ReadAll(&User{
ID: *c.ID,
Icon: c.Icon,
Flags: c.Flags,
Name: string(c.UserName),
+ })
+ if err != nil {
+ return nil
}
- connectedUsers = append(connectedUsers, NewField(FieldUsernameWithInfo, user.Payload()))
+ connectedUsers = append(connectedUsers, NewField(FieldUsernameWithInfo, b))
}
return connectedUsers
}
account := Account{}
decoder := yaml.NewDecoder(fh)
- if err := decoder.Decode(&account); err != nil {
- return err
+ if err = decoder.Decode(&account); err != nil {
+ return fmt.Errorf("error loading account %s: %w", file, err)
}
s.Accounts[account.Login] = &account
switch fileTransfer.Type {
case bannerDownload:
- if err := s.bannerDownload(rwc); err != nil {
- return err
+ if _, err := io.Copy(rwc, bytes.NewBuffer(s.banner)); err != nil {
+ return fmt.Errorf("error sending banner: %w", err)
}
case FileDownload:
s.Stats.DownloadCounter += 1
// if file transfer options are included, that means this is a "quick preview" request from a 1.5+ client
if fileTransfer.options == nil {
- // Start by sending flat file object to client
- if _, err := rwc.Write(fw.ffo.BinaryMarshal()); err != nil {
+ _, err = io.Copy(rwc, fw.ffo)
+ if err != nil {
return err
}
}
}
fileHeader := NewFileHeader(subPath, info.IsDir())
-
- // Send the fileWrapper header to client
- if _, err := rwc.Write(fileHeader.Payload()); err != nil {
- s.Logger.Errorf("error sending file header: %v", err)
- return err
+ if _, err := io.Copy(rwc, &fileHeader); err != nil {
+ return fmt.Errorf("error sending file header: %w", err)
}
// Read the client's Next Action request
}
// Send ffo bytes to client
- if _, err := rwc.Write(hlFile.ffo.BinaryMarshal()); err != nil {
- s.Logger.Error(err)
+ _, err = io.Copy(rwc, hlFile.ffo)
+ if err != nil {
return err
}