X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/blobdiff_plain/2d0f2abe62a5cc7272f7aeabc761bc7cf2147592..5cc444c89968dda9060d4e2f458b1babb9b603cd:/hotline/server.go diff --git a/hotline/server.go b/hotline/server.go index 6b55cf8..b5bb0d5 100644 --- a/hotline/server.go +++ b/hotline/server.go @@ -2,12 +2,14 @@ package hotline 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" @@ -15,6 +17,7 @@ import ( "math/rand" "net" "os" + "path" "path/filepath" "strings" "sync" @@ -29,6 +32,12 @@ type requestCtx struct { 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 @@ -40,6 +49,7 @@ type Server struct { Config *Config ConfigDir string Logger *zap.SugaredLogger + banner []byte PrivateChatsMu sync.Mutex PrivateChats map[uint32]*PrivateChat @@ -257,7 +267,15 @@ func NewServer(configDir, netInterface string, netPort int, logger *zap.SugaredL return nil, err } - server.Config.FileRoot = filepath.Join(configDir, "Files") + // If the FileRoot is an absolute path, use it, otherwise treat as a relative path to the config dir. + if !filepath.IsAbs(server.Config.FileRoot) { + 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 @@ -272,7 +290,7 @@ func NewServer(configDir, netInterface string, netPort int, logger *zap.SugaredL for { tr := &TrackerRegistration{ UserCount: server.userCount(), - PassID: server.TrackerPassID[:], + PassID: server.TrackerPassID, Name: server.Config.Name, Description: server.Config.Description, } @@ -374,15 +392,14 @@ func (s *Server) NewClientConn(conn io.ReadWriteCloser, remoteAddr string) *Clie 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++ @@ -409,9 +426,26 @@ func (s *Server) NewUser(login, name, password string, access accessBitmap) erro 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 { @@ -420,11 +454,12 @@ func (s *Server) UpdateUser(login, newLogin, name, password string, access acces // 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) } @@ -450,9 +485,14 @@ func (s *Server) DeleteUser(login string) error { 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 { @@ -461,13 +501,16 @@ 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 } @@ -512,8 +555,8 @@ func (s *Server) loadAccounts(userDir string) error { 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 @@ -804,8 +847,8 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro 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 @@ -828,8 +871,8 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro // 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 } } @@ -994,11 +1037,8 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro } 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 @@ -1050,8 +1090,8 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro } // 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 }