github.com/jhalter/mobius@v0.12.1/hotline/server.go (about) 1 package hotline 2 3 import ( 4 "bufio" 5 "context" 6 "encoding/binary" 7 "errors" 8 "fmt" 9 "github.com/go-playground/validator/v10" 10 "go.uber.org/zap" 11 "golang.org/x/text/encoding/charmap" 12 "gopkg.in/yaml.v3" 13 "io" 14 "io/fs" 15 "math/big" 16 "math/rand" 17 "net" 18 "os" 19 "path/filepath" 20 "strings" 21 "sync" 22 "time" 23 ) 24 25 type contextKey string 26 27 var contextKeyReq = contextKey("req") 28 29 type requestCtx struct { 30 remoteAddr string 31 } 32 33 // Converts bytes from Mac Roman encoding to UTF-8 34 var txtDecoder = charmap.Macintosh.NewDecoder() 35 36 // Converts bytes from UTF-8 to Mac Roman encoding 37 var txtEncoder = charmap.Macintosh.NewEncoder() 38 39 type Server struct { 40 NetInterface string 41 Port int 42 Accounts map[string]*Account 43 Agreement []byte 44 Clients map[uint16]*ClientConn 45 fileTransfers map[[4]byte]*FileTransfer 46 47 Config *Config 48 ConfigDir string 49 Logger *zap.SugaredLogger 50 51 PrivateChatsMu sync.Mutex 52 PrivateChats map[uint32]*PrivateChat 53 54 NextGuestID *uint16 55 TrackerPassID [4]byte 56 57 StatsMu sync.Mutex 58 Stats *Stats 59 60 FS FileStore // Storage backend to use for File storage 61 62 outbox chan Transaction 63 mux sync.Mutex 64 65 threadedNewsMux sync.Mutex 66 ThreadedNews *ThreadedNews 67 68 flatNewsMux sync.Mutex 69 FlatNews []byte 70 71 banListMU sync.Mutex 72 banList map[string]*time.Time 73 } 74 75 func (s *Server) CurrentStats() Stats { 76 s.StatsMu.Lock() 77 defer s.StatsMu.Unlock() 78 79 stats := s.Stats 80 stats.CurrentlyConnected = len(s.Clients) 81 82 return *stats 83 } 84 85 type PrivateChat struct { 86 Subject string 87 ClientConn map[uint16]*ClientConn 88 } 89 90 func (s *Server) ListenAndServe(ctx context.Context, cancelRoot context.CancelFunc) error { 91 s.Logger.Infow("Hotline server started", 92 "version", VERSION, 93 "API port", fmt.Sprintf("%s:%v", s.NetInterface, s.Port), 94 "Transfer port", fmt.Sprintf("%s:%v", s.NetInterface, s.Port+1), 95 ) 96 97 var wg sync.WaitGroup 98 99 wg.Add(1) 100 go func() { 101 ln, err := net.Listen("tcp", fmt.Sprintf("%s:%v", s.NetInterface, s.Port)) 102 if err != nil { 103 s.Logger.Fatal(err) 104 } 105 106 s.Logger.Fatal(s.Serve(ctx, ln)) 107 }() 108 109 wg.Add(1) 110 go func() { 111 ln, err := net.Listen("tcp", fmt.Sprintf("%s:%v", s.NetInterface, s.Port+1)) 112 if err != nil { 113 s.Logger.Fatal(err) 114 } 115 116 s.Logger.Fatal(s.ServeFileTransfers(ctx, ln)) 117 }() 118 119 wg.Wait() 120 121 return nil 122 } 123 124 func (s *Server) ServeFileTransfers(ctx context.Context, ln net.Listener) error { 125 for { 126 conn, err := ln.Accept() 127 if err != nil { 128 return err 129 } 130 131 go func() { 132 defer func() { _ = conn.Close() }() 133 134 err = s.handleFileTransfer( 135 context.WithValue(ctx, contextKeyReq, requestCtx{ 136 remoteAddr: conn.RemoteAddr().String(), 137 }), 138 conn, 139 ) 140 141 if err != nil { 142 s.Logger.Errorw("file transfer error", "reason", err) 143 } 144 }() 145 } 146 } 147 148 func (s *Server) sendTransaction(t Transaction) error { 149 clientID, err := byteToInt(*t.clientID) 150 if err != nil { 151 return err 152 } 153 154 s.mux.Lock() 155 client := s.Clients[uint16(clientID)] 156 s.mux.Unlock() 157 if client == nil { 158 return fmt.Errorf("invalid client id %v", *t.clientID) 159 } 160 161 b, err := t.MarshalBinary() 162 if err != nil { 163 return err 164 } 165 166 _, err = client.Connection.Write(b) 167 if err != nil { 168 return err 169 } 170 171 return nil 172 } 173 174 func (s *Server) processOutbox() { 175 for { 176 t := <-s.outbox 177 go func() { 178 if err := s.sendTransaction(t); err != nil { 179 s.Logger.Errorw("error sending transaction", "err", err) 180 } 181 }() 182 } 183 } 184 185 func (s *Server) Serve(ctx context.Context, ln net.Listener) error { 186 go s.processOutbox() 187 188 for { 189 conn, err := ln.Accept() 190 if err != nil { 191 s.Logger.Errorw("error accepting connection", "err", err) 192 } 193 connCtx := context.WithValue(ctx, contextKeyReq, requestCtx{ 194 remoteAddr: conn.RemoteAddr().String(), 195 }) 196 197 go func() { 198 s.Logger.Infow("Connection established", "RemoteAddr", conn.RemoteAddr()) 199 200 defer conn.Close() 201 if err := s.handleNewConnection(connCtx, conn, conn.RemoteAddr().String()); err != nil { 202 if err == io.EOF { 203 s.Logger.Infow("Client disconnected", "RemoteAddr", conn.RemoteAddr()) 204 } else { 205 s.Logger.Errorw("error serving request", "RemoteAddr", conn.RemoteAddr(), "err", err) 206 } 207 } 208 }() 209 } 210 } 211 212 const ( 213 agreementFile = "Agreement.txt" 214 ) 215 216 // NewServer constructs a new Server from a config dir 217 func NewServer(configDir, netInterface string, netPort int, logger *zap.SugaredLogger, fs FileStore) (*Server, error) { 218 server := Server{ 219 NetInterface: netInterface, 220 Port: netPort, 221 Accounts: make(map[string]*Account), 222 Config: new(Config), 223 Clients: make(map[uint16]*ClientConn), 224 fileTransfers: make(map[[4]byte]*FileTransfer), 225 PrivateChats: make(map[uint32]*PrivateChat), 226 ConfigDir: configDir, 227 Logger: logger, 228 NextGuestID: new(uint16), 229 outbox: make(chan Transaction), 230 Stats: &Stats{Since: time.Now()}, 231 ThreadedNews: &ThreadedNews{}, 232 FS: fs, 233 banList: make(map[string]*time.Time), 234 } 235 236 var err error 237 238 // generate a new random passID for tracker registration 239 if _, err := rand.Read(server.TrackerPassID[:]); err != nil { 240 return nil, err 241 } 242 243 server.Agreement, err = os.ReadFile(filepath.Join(configDir, agreementFile)) 244 if err != nil { 245 return nil, err 246 } 247 248 if server.FlatNews, err = os.ReadFile(filepath.Join(configDir, "MessageBoard.txt")); err != nil { 249 return nil, err 250 } 251 252 // try to load the ban list, but ignore errors as this file may not be present or may be empty 253 _ = server.loadBanList(filepath.Join(configDir, "Banlist.yaml")) 254 255 if err := server.loadThreadedNews(filepath.Join(configDir, "ThreadedNews.yaml")); err != nil { 256 return nil, err 257 } 258 259 if err := server.loadConfig(filepath.Join(configDir, "config.yaml")); err != nil { 260 return nil, err 261 } 262 263 if err := server.loadAccounts(filepath.Join(configDir, "Users/")); err != nil { 264 return nil, err 265 } 266 267 // If the FileRoot is an absolute path, use it, otherwise treat as a relative path to the config dir. 268 if !filepath.IsAbs(server.Config.FileRoot) { 269 server.Config.FileRoot = filepath.Join(configDir, server.Config.FileRoot) 270 } 271 272 *server.NextGuestID = 1 273 274 if server.Config.EnableTrackerRegistration { 275 server.Logger.Infow( 276 "Tracker registration enabled", 277 "frequency", fmt.Sprintf("%vs", trackerUpdateFrequency), 278 "trackers", server.Config.Trackers, 279 ) 280 281 go func() { 282 for { 283 tr := &TrackerRegistration{ 284 UserCount: server.userCount(), 285 PassID: server.TrackerPassID[:], 286 Name: server.Config.Name, 287 Description: server.Config.Description, 288 } 289 binary.BigEndian.PutUint16(tr.Port[:], uint16(server.Port)) 290 for _, t := range server.Config.Trackers { 291 if err := register(t, tr); err != nil { 292 server.Logger.Errorw("unable to register with tracker %v", "error", err) 293 } 294 server.Logger.Debugw("Sent Tracker registration", "addr", t) 295 } 296 297 time.Sleep(trackerUpdateFrequency * time.Second) 298 } 299 }() 300 } 301 302 // Start Client Keepalive go routine 303 go server.keepaliveHandler() 304 305 return &server, nil 306 } 307 308 func (s *Server) userCount() int { 309 s.mux.Lock() 310 defer s.mux.Unlock() 311 312 return len(s.Clients) 313 } 314 315 func (s *Server) keepaliveHandler() { 316 for { 317 time.Sleep(idleCheckInterval * time.Second) 318 s.mux.Lock() 319 320 for _, c := range s.Clients { 321 c.IdleTime += idleCheckInterval 322 if c.IdleTime > userIdleSeconds && !c.Idle { 323 c.Idle = true 324 325 flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(c.Flags))) 326 flagBitmap.SetBit(flagBitmap, UserFlagAway, 1) 327 binary.BigEndian.PutUint16(c.Flags, uint16(flagBitmap.Int64())) 328 329 c.sendAll( 330 TranNotifyChangeUser, 331 NewField(FieldUserID, *c.ID), 332 NewField(FieldUserFlags, c.Flags), 333 NewField(FieldUserName, c.UserName), 334 NewField(FieldUserIconID, c.Icon), 335 ) 336 } 337 } 338 s.mux.Unlock() 339 } 340 } 341 342 func (s *Server) writeBanList() error { 343 s.banListMU.Lock() 344 defer s.banListMU.Unlock() 345 346 out, err := yaml.Marshal(s.banList) 347 if err != nil { 348 return err 349 } 350 err = os.WriteFile( 351 filepath.Join(s.ConfigDir, "Banlist.yaml"), 352 out, 353 0666, 354 ) 355 return err 356 } 357 358 func (s *Server) writeThreadedNews() error { 359 s.threadedNewsMux.Lock() 360 defer s.threadedNewsMux.Unlock() 361 362 out, err := yaml.Marshal(s.ThreadedNews) 363 if err != nil { 364 return err 365 } 366 err = s.FS.WriteFile( 367 filepath.Join(s.ConfigDir, "ThreadedNews.yaml"), 368 out, 369 0666, 370 ) 371 return err 372 } 373 374 func (s *Server) NewClientConn(conn io.ReadWriteCloser, remoteAddr string) *ClientConn { 375 s.mux.Lock() 376 defer s.mux.Unlock() 377 378 clientConn := &ClientConn{ 379 ID: &[]byte{0, 0}, 380 Icon: []byte{0, 0}, 381 Flags: []byte{0, 0}, 382 UserName: []byte{}, 383 Connection: conn, 384 Server: s, 385 Version: []byte{}, 386 AutoReply: []byte{}, 387 transfers: map[int]map[[4]byte]*FileTransfer{}, 388 RemoteAddr: remoteAddr, 389 } 390 clientConn.transfers = map[int]map[[4]byte]*FileTransfer{ 391 FileDownload: {}, 392 FileUpload: {}, 393 FolderDownload: {}, 394 FolderUpload: {}, 395 bannerDownload: {}, 396 } 397 398 *s.NextGuestID++ 399 ID := *s.NextGuestID 400 401 binary.BigEndian.PutUint16(*clientConn.ID, ID) 402 s.Clients[ID] = clientConn 403 404 return clientConn 405 } 406 407 // NewUser creates a new user account entry in the server map and config file 408 func (s *Server) NewUser(login, name, password string, access accessBitmap) error { 409 s.mux.Lock() 410 defer s.mux.Unlock() 411 412 account := Account{ 413 Login: login, 414 Name: name, 415 Password: hashAndSalt([]byte(password)), 416 Access: access, 417 } 418 out, err := yaml.Marshal(&account) 419 if err != nil { 420 return err 421 } 422 s.Accounts[login] = &account 423 424 return s.FS.WriteFile(filepath.Join(s.ConfigDir, "Users", login+".yaml"), out, 0666) 425 } 426 427 func (s *Server) UpdateUser(login, newLogin, name, password string, access accessBitmap) error { 428 s.mux.Lock() 429 defer s.mux.Unlock() 430 431 // update renames the user login 432 if login != newLogin { 433 err := os.Rename(filepath.Join(s.ConfigDir, "Users", login+".yaml"), filepath.Join(s.ConfigDir, "Users", newLogin+".yaml")) 434 if err != nil { 435 return err 436 } 437 s.Accounts[newLogin] = s.Accounts[login] 438 delete(s.Accounts, login) 439 } 440 441 account := s.Accounts[newLogin] 442 account.Access = access 443 account.Name = name 444 account.Password = password 445 446 out, err := yaml.Marshal(&account) 447 if err != nil { 448 return err 449 } 450 451 if err := os.WriteFile(filepath.Join(s.ConfigDir, "Users", newLogin+".yaml"), out, 0666); err != nil { 452 return err 453 } 454 455 return nil 456 } 457 458 // DeleteUser deletes the user account 459 func (s *Server) DeleteUser(login string) error { 460 s.mux.Lock() 461 defer s.mux.Unlock() 462 463 delete(s.Accounts, login) 464 465 return s.FS.Remove(filepath.Join(s.ConfigDir, "Users", login+".yaml")) 466 } 467 468 func (s *Server) connectedUsers() []Field { 469 s.mux.Lock() 470 defer s.mux.Unlock() 471 472 var connectedUsers []Field 473 for _, c := range sortedClients(s.Clients) { 474 user := User{ 475 ID: *c.ID, 476 Icon: c.Icon, 477 Flags: c.Flags, 478 Name: string(c.UserName), 479 } 480 connectedUsers = append(connectedUsers, NewField(FieldUsernameWithInfo, user.Payload())) 481 } 482 return connectedUsers 483 } 484 485 func (s *Server) loadBanList(path string) error { 486 fh, err := os.Open(path) 487 if err != nil { 488 return err 489 } 490 decoder := yaml.NewDecoder(fh) 491 492 return decoder.Decode(s.banList) 493 } 494 495 // loadThreadedNews loads the threaded news data from disk 496 func (s *Server) loadThreadedNews(threadedNewsPath string) error { 497 fh, err := os.Open(threadedNewsPath) 498 if err != nil { 499 return err 500 } 501 decoder := yaml.NewDecoder(fh) 502 503 return decoder.Decode(s.ThreadedNews) 504 } 505 506 // loadAccounts loads account data from disk 507 func (s *Server) loadAccounts(userDir string) error { 508 matches, err := filepath.Glob(filepath.Join(userDir, "*.yaml")) 509 if err != nil { 510 return err 511 } 512 513 if len(matches) == 0 { 514 return errors.New("no user accounts found in " + userDir) 515 } 516 517 for _, file := range matches { 518 fh, err := s.FS.Open(file) 519 if err != nil { 520 return err 521 } 522 523 account := Account{} 524 decoder := yaml.NewDecoder(fh) 525 if err := decoder.Decode(&account); err != nil { 526 return err 527 } 528 529 s.Accounts[account.Login] = &account 530 } 531 return nil 532 } 533 534 func (s *Server) loadConfig(path string) error { 535 fh, err := s.FS.Open(path) 536 if err != nil { 537 return err 538 } 539 540 decoder := yaml.NewDecoder(fh) 541 err = decoder.Decode(s.Config) 542 if err != nil { 543 return err 544 } 545 546 validate := validator.New() 547 err = validate.Struct(s.Config) 548 if err != nil { 549 return err 550 } 551 return nil 552 } 553 554 // handleNewConnection takes a new net.Conn and performs the initial login sequence 555 func (s *Server) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser, remoteAddr string) error { 556 defer dontPanic(s.Logger) 557 558 if err := Handshake(rwc); err != nil { 559 return err 560 } 561 562 // Create a new scanner for parsing incoming bytes into transaction tokens 563 scanner := bufio.NewScanner(rwc) 564 scanner.Split(transactionScanner) 565 566 scanner.Scan() 567 568 // Make a new []byte slice and copy the scanner bytes to it. This is critical to avoid a data race as the 569 // scanner re-uses the buffer for subsequent scans. 570 buf := make([]byte, len(scanner.Bytes())) 571 copy(buf, scanner.Bytes()) 572 573 var clientLogin Transaction 574 if _, err := clientLogin.Write(buf); err != nil { 575 return err 576 } 577 578 // check if remoteAddr is present in the ban list 579 if banUntil, ok := s.banList[strings.Split(remoteAddr, ":")[0]]; ok { 580 // permaban 581 if banUntil == nil { 582 t := NewTransaction( 583 TranServerMsg, 584 &[]byte{0, 0}, 585 NewField(FieldData, []byte("You are permanently banned on this server")), 586 NewField(FieldChatOptions, []byte{0, 0}), 587 ) 588 589 b, err := t.MarshalBinary() 590 if err != nil { 591 return err 592 } 593 594 _, err = rwc.Write(b) 595 if err != nil { 596 return err 597 } 598 599 time.Sleep(1 * time.Second) 600 return nil 601 } 602 603 // temporary ban 604 if time.Now().Before(*banUntil) { 605 t := NewTransaction( 606 TranServerMsg, 607 &[]byte{0, 0}, 608 NewField(FieldData, []byte("You are temporarily banned on this server")), 609 NewField(FieldChatOptions, []byte{0, 0}), 610 ) 611 b, err := t.MarshalBinary() 612 if err != nil { 613 return err 614 } 615 616 _, err = rwc.Write(b) 617 if err != nil { 618 return err 619 } 620 621 time.Sleep(1 * time.Second) 622 return nil 623 } 624 } 625 626 c := s.NewClientConn(rwc, remoteAddr) 627 defer c.Disconnect() 628 629 encodedLogin := clientLogin.GetField(FieldUserLogin).Data 630 encodedPassword := clientLogin.GetField(FieldUserPassword).Data 631 c.Version = clientLogin.GetField(FieldVersion).Data 632 633 var login string 634 for _, char := range encodedLogin { 635 login += string(rune(255 - uint(char))) 636 } 637 if login == "" { 638 login = GuestAccount 639 } 640 641 c.logger = s.Logger.With("remoteAddr", remoteAddr, "login", login) 642 643 // If authentication fails, send error reply and close connection 644 if !c.Authenticate(login, encodedPassword) { 645 t := c.NewErrReply(&clientLogin, "Incorrect login.") 646 b, err := t.MarshalBinary() 647 if err != nil { 648 return err 649 } 650 if _, err := rwc.Write(b); err != nil { 651 return err 652 } 653 654 c.logger.Infow("Login failed", "clientVersion", fmt.Sprintf("%x", c.Version)) 655 656 return nil 657 } 658 659 if clientLogin.GetField(FieldUserIconID).Data != nil { 660 c.Icon = clientLogin.GetField(FieldUserIconID).Data 661 } 662 663 c.Account = c.Server.Accounts[login] 664 665 if clientLogin.GetField(FieldUserName).Data != nil { 666 if c.Authorize(accessAnyName) { 667 c.UserName = clientLogin.GetField(FieldUserName).Data 668 } else { 669 c.UserName = []byte(c.Account.Name) 670 } 671 } 672 673 if c.Authorize(accessDisconUser) { 674 c.Flags = []byte{0, 2} 675 } 676 677 s.outbox <- c.NewReply(&clientLogin, 678 NewField(FieldVersion, []byte{0x00, 0xbe}), 679 NewField(FieldCommunityBannerID, []byte{0, 0}), 680 NewField(FieldServerName, []byte(s.Config.Name)), 681 ) 682 683 // Send user access privs so client UI knows how to behave 684 c.Server.outbox <- *NewTransaction(TranUserAccess, c.ID, NewField(FieldUserAccess, c.Account.Access[:])) 685 686 // Accounts with accessNoAgreement do not receive the server agreement on login. The behavior is different between 687 // client versions. For 1.2.3 client, we do not send TranShowAgreement. For other client versions, we send 688 // TranShowAgreement but with the NoServerAgreement field set to 1. 689 if c.Authorize(accessNoAgreement) { 690 // If client version is nil, then the client uses the 1.2.3 login behavior 691 if c.Version != nil { 692 c.Server.outbox <- *NewTransaction(TranShowAgreement, c.ID, NewField(FieldNoServerAgreement, []byte{1})) 693 } 694 } else { 695 c.Server.outbox <- *NewTransaction(TranShowAgreement, c.ID, NewField(FieldData, s.Agreement)) 696 } 697 698 // If the client has provided a username as part of the login, we can infer that it is using the 1.2.3 login 699 // flow and not the 1.5+ flow. 700 if len(c.UserName) != 0 { 701 // Add the client username to the logger. For 1.5+ clients, we don't have this information yet as it comes as 702 // part of TranAgreed 703 c.logger = c.logger.With("name", string(c.UserName)) 704 705 c.logger.Infow("Login successful", "clientVersion", "Not sent (probably 1.2.3)") 706 707 // Notify other clients on the server that the new user has logged in. For 1.5+ clients we don't have this 708 // information yet, so we do it in TranAgreed instead 709 for _, t := range c.notifyOthers( 710 *NewTransaction( 711 TranNotifyChangeUser, nil, 712 NewField(FieldUserName, c.UserName), 713 NewField(FieldUserID, *c.ID), 714 NewField(FieldUserIconID, c.Icon), 715 NewField(FieldUserFlags, c.Flags), 716 ), 717 ) { 718 c.Server.outbox <- t 719 } 720 } 721 722 c.Server.Stats.ConnectionCounter += 1 723 if len(s.Clients) > c.Server.Stats.ConnectionPeak { 724 c.Server.Stats.ConnectionPeak = len(s.Clients) 725 } 726 727 // Scan for new transactions and handle them as they come in. 728 for scanner.Scan() { 729 // Make a new []byte slice and copy the scanner bytes to it. This is critical to avoid a data race as the 730 // scanner re-uses the buffer for subsequent scans. 731 buf := make([]byte, len(scanner.Bytes())) 732 copy(buf, scanner.Bytes()) 733 734 var t Transaction 735 if _, err := t.Write(buf); err != nil { 736 return err 737 } 738 739 if err := c.handleTransaction(t); err != nil { 740 c.logger.Errorw("Error handling transaction", "err", err) 741 } 742 } 743 return nil 744 } 745 746 func (s *Server) NewPrivateChat(cc *ClientConn) []byte { 747 s.PrivateChatsMu.Lock() 748 defer s.PrivateChatsMu.Unlock() 749 750 randID := make([]byte, 4) 751 rand.Read(randID) 752 data := binary.BigEndian.Uint32(randID) 753 754 s.PrivateChats[data] = &PrivateChat{ 755 ClientConn: make(map[uint16]*ClientConn), 756 } 757 s.PrivateChats[data].ClientConn[cc.uint16ID()] = cc 758 759 return randID 760 } 761 762 const dlFldrActionSendFile = 1 763 const dlFldrActionResumeFile = 2 764 const dlFldrActionNextFile = 3 765 766 // handleFileTransfer receives a client net.Conn from the file transfer server, performs the requested transfer type, then closes the connection 767 func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) error { 768 defer dontPanic(s.Logger) 769 770 txBuf := make([]byte, 16) 771 if _, err := io.ReadFull(rwc, txBuf); err != nil { 772 return err 773 } 774 775 var t transfer 776 if _, err := t.Write(txBuf); err != nil { 777 return err 778 } 779 780 defer func() { 781 s.mux.Lock() 782 delete(s.fileTransfers, t.ReferenceNumber) 783 s.mux.Unlock() 784 785 // Wait a few seconds before closing the connection: this is a workaround for problems 786 // observed with Windows clients where the client must initiate close of the TCP connection before 787 // the server does. This is gross and seems unnecessary. TODO: Revisit? 788 time.Sleep(3 * time.Second) 789 }() 790 791 s.mux.Lock() 792 fileTransfer, ok := s.fileTransfers[t.ReferenceNumber] 793 s.mux.Unlock() 794 if !ok { 795 return errors.New("invalid transaction ID") 796 } 797 798 defer func() { 799 fileTransfer.ClientConn.transfersMU.Lock() 800 delete(fileTransfer.ClientConn.transfers[fileTransfer.Type], t.ReferenceNumber) 801 fileTransfer.ClientConn.transfersMU.Unlock() 802 }() 803 804 rLogger := s.Logger.With( 805 "remoteAddr", ctx.Value(contextKeyReq).(requestCtx).remoteAddr, 806 "login", fileTransfer.ClientConn.Account.Login, 807 "name", string(fileTransfer.ClientConn.UserName), 808 ) 809 810 fullPath, err := readPath(s.Config.FileRoot, fileTransfer.FilePath, fileTransfer.FileName) 811 if err != nil { 812 return err 813 } 814 815 switch fileTransfer.Type { 816 case bannerDownload: 817 if err := s.bannerDownload(rwc); err != nil { 818 return err 819 } 820 case FileDownload: 821 s.Stats.DownloadCounter += 1 822 s.Stats.DownloadsInProgress += 1 823 defer func() { 824 s.Stats.DownloadsInProgress -= 1 825 }() 826 827 var dataOffset int64 828 if fileTransfer.fileResumeData != nil { 829 dataOffset = int64(binary.BigEndian.Uint32(fileTransfer.fileResumeData.ForkInfoList[0].DataSize[:])) 830 } 831 832 fw, err := newFileWrapper(s.FS, fullPath, 0) 833 if err != nil { 834 return err 835 } 836 837 rLogger.Infow("File download started", "filePath", fullPath) 838 839 // if file transfer options are included, that means this is a "quick preview" request from a 1.5+ client 840 if fileTransfer.options == nil { 841 // Start by sending flat file object to client 842 if _, err := rwc.Write(fw.ffo.BinaryMarshal()); err != nil { 843 return err 844 } 845 } 846 847 file, err := fw.dataForkReader() 848 if err != nil { 849 return err 850 } 851 852 br := bufio.NewReader(file) 853 if _, err := br.Discard(int(dataOffset)); err != nil { 854 return err 855 } 856 857 if _, err = io.Copy(rwc, io.TeeReader(br, fileTransfer.bytesSentCounter)); err != nil { 858 return err 859 } 860 861 // if the client requested to resume transfer, do not send the resource fork header, or it will be appended into the fileWrapper data 862 if fileTransfer.fileResumeData == nil { 863 err = binary.Write(rwc, binary.BigEndian, fw.rsrcForkHeader()) 864 if err != nil { 865 return err 866 } 867 } 868 869 rFile, err := fw.rsrcForkFile() 870 if err != nil { 871 return nil 872 } 873 874 if _, err = io.Copy(rwc, io.TeeReader(rFile, fileTransfer.bytesSentCounter)); err != nil { 875 return err 876 } 877 878 case FileUpload: 879 s.Stats.UploadCounter += 1 880 s.Stats.UploadsInProgress += 1 881 defer func() { s.Stats.UploadsInProgress -= 1 }() 882 883 var file *os.File 884 885 // A file upload has three possible cases: 886 // 1) Upload a new file 887 // 2) Resume a partially transferred file 888 // 3) Replace a fully uploaded file 889 // We have to infer which case applies by inspecting what is already on the filesystem 890 891 // 1) Check for existing file: 892 _, err = os.Stat(fullPath) 893 if err == nil { 894 return errors.New("existing file found at " + fullPath) 895 } 896 if errors.Is(err, fs.ErrNotExist) { 897 // If not found, open or create a new .incomplete file 898 file, err = os.OpenFile(fullPath+incompleteFileSuffix, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 899 if err != nil { 900 return err 901 } 902 } 903 904 f, err := newFileWrapper(s.FS, fullPath, 0) 905 if err != nil { 906 return err 907 } 908 909 rLogger.Infow("File upload started", "dstFile", fullPath) 910 911 rForkWriter := io.Discard 912 iForkWriter := io.Discard 913 if s.Config.PreserveResourceForks { 914 rForkWriter, err = f.rsrcForkWriter() 915 if err != nil { 916 return err 917 } 918 919 iForkWriter, err = f.infoForkWriter() 920 if err != nil { 921 return err 922 } 923 } 924 925 if err := receiveFile(rwc, file, rForkWriter, iForkWriter, fileTransfer.bytesSentCounter); err != nil { 926 s.Logger.Error(err) 927 } 928 929 if err := file.Close(); err != nil { 930 return err 931 } 932 933 if err := s.FS.Rename(fullPath+".incomplete", fullPath); err != nil { 934 return err 935 } 936 937 rLogger.Infow("File upload complete", "dstFile", fullPath) 938 939 case FolderDownload: 940 s.Stats.DownloadCounter += 1 941 s.Stats.DownloadsInProgress += 1 942 defer func() { s.Stats.DownloadsInProgress -= 1 }() 943 944 // Folder Download flow: 945 // 1. Get filePath from the transfer 946 // 2. Iterate over files 947 // 3. For each fileWrapper: 948 // Send fileWrapper header to client 949 // The client can reply in 3 ways: 950 // 951 // 1. If type is an odd number (unknown type?), or fileWrapper download for the current fileWrapper is completed: 952 // client sends []byte{0x00, 0x03} to tell the server to continue to the next fileWrapper 953 // 954 // 2. If download of a fileWrapper is to be resumed: 955 // client sends: 956 // []byte{0x00, 0x02} // download folder action 957 // [2]byte // Resume data size 958 // []byte fileWrapper resume data (see myField_FileResumeData) 959 // 960 // 3. Otherwise, download of the fileWrapper is requested and client sends []byte{0x00, 0x01} 961 // 962 // When download is requested (case 2 or 3), server replies with: 963 // [4]byte - fileWrapper size 964 // []byte - Flattened File Object 965 // 966 // After every fileWrapper download, client could request next fileWrapper with: 967 // []byte{0x00, 0x03} 968 // 969 // This notifies the server to send the next item header 970 971 basePathLen := len(fullPath) 972 973 rLogger.Infow("Start folder download", "path", fullPath) 974 975 nextAction := make([]byte, 2) 976 if _, err := io.ReadFull(rwc, nextAction); err != nil { 977 return err 978 } 979 980 i := 0 981 err = filepath.Walk(fullPath+"/", func(path string, info os.FileInfo, err error) error { 982 s.Stats.DownloadCounter += 1 983 i += 1 984 985 if err != nil { 986 return err 987 } 988 989 // skip dot files 990 if strings.HasPrefix(info.Name(), ".") { 991 return nil 992 } 993 994 hlFile, err := newFileWrapper(s.FS, path, 0) 995 if err != nil { 996 return err 997 } 998 999 subPath := path[basePathLen+1:] 1000 rLogger.Debugw("Sending fileheader", "i", i, "path", path, "fullFilePath", fullPath, "subPath", subPath, "IsDir", info.IsDir()) 1001 1002 if i == 1 { 1003 return nil 1004 } 1005 1006 fileHeader := NewFileHeader(subPath, info.IsDir()) 1007 1008 // Send the fileWrapper header to client 1009 if _, err := rwc.Write(fileHeader.Payload()); err != nil { 1010 s.Logger.Errorf("error sending file header: %v", err) 1011 return err 1012 } 1013 1014 // Read the client's Next Action request 1015 if _, err := io.ReadFull(rwc, nextAction); err != nil { 1016 return err 1017 } 1018 1019 rLogger.Debugw("Client folder download action", "action", fmt.Sprintf("%X", nextAction[0:2])) 1020 1021 var dataOffset int64 1022 1023 switch nextAction[1] { 1024 case dlFldrActionResumeFile: 1025 // get size of resumeData 1026 resumeDataByteLen := make([]byte, 2) 1027 if _, err := io.ReadFull(rwc, resumeDataByteLen); err != nil { 1028 return err 1029 } 1030 1031 resumeDataLen := binary.BigEndian.Uint16(resumeDataByteLen) 1032 resumeDataBytes := make([]byte, resumeDataLen) 1033 if _, err := io.ReadFull(rwc, resumeDataBytes); err != nil { 1034 return err 1035 } 1036 1037 var frd FileResumeData 1038 if err := frd.UnmarshalBinary(resumeDataBytes); err != nil { 1039 return err 1040 } 1041 dataOffset = int64(binary.BigEndian.Uint32(frd.ForkInfoList[0].DataSize[:])) 1042 case dlFldrActionNextFile: 1043 // client asked to skip this file 1044 return nil 1045 } 1046 1047 if info.IsDir() { 1048 return nil 1049 } 1050 1051 rLogger.Infow("File download started", 1052 "fileName", info.Name(), 1053 "TransferSize", fmt.Sprintf("%x", hlFile.ffo.TransferSize(dataOffset)), 1054 ) 1055 1056 // Send file size to client 1057 if _, err := rwc.Write(hlFile.ffo.TransferSize(dataOffset)); err != nil { 1058 s.Logger.Error(err) 1059 return err 1060 } 1061 1062 // Send ffo bytes to client 1063 if _, err := rwc.Write(hlFile.ffo.BinaryMarshal()); err != nil { 1064 s.Logger.Error(err) 1065 return err 1066 } 1067 1068 file, err := s.FS.Open(path) 1069 if err != nil { 1070 return err 1071 } 1072 1073 // wr := bufio.NewWriterSize(rwc, 1460) 1074 if _, err = io.Copy(rwc, io.TeeReader(file, fileTransfer.bytesSentCounter)); err != nil { 1075 return err 1076 } 1077 1078 if nextAction[1] != 2 && hlFile.ffo.FlatFileHeader.ForkCount[1] == 3 { 1079 err = binary.Write(rwc, binary.BigEndian, hlFile.rsrcForkHeader()) 1080 if err != nil { 1081 return err 1082 } 1083 1084 rFile, err := hlFile.rsrcForkFile() 1085 if err != nil { 1086 return err 1087 } 1088 1089 if _, err = io.Copy(rwc, io.TeeReader(rFile, fileTransfer.bytesSentCounter)); err != nil { 1090 return err 1091 } 1092 } 1093 1094 // Read the client's Next Action request. This is always 3, I think? 1095 if _, err := io.ReadFull(rwc, nextAction); err != nil { 1096 return err 1097 } 1098 1099 return nil 1100 }) 1101 1102 if err != nil { 1103 return err 1104 } 1105 1106 case FolderUpload: 1107 s.Stats.UploadCounter += 1 1108 s.Stats.UploadsInProgress += 1 1109 defer func() { s.Stats.UploadsInProgress -= 1 }() 1110 rLogger.Infow( 1111 "Folder upload started", 1112 "dstPath", fullPath, 1113 "TransferSize", binary.BigEndian.Uint32(fileTransfer.TransferSize), 1114 "FolderItemCount", fileTransfer.FolderItemCount, 1115 ) 1116 1117 // Check if the target folder exists. If not, create it. 1118 if _, err := s.FS.Stat(fullPath); os.IsNotExist(err) { 1119 if err := s.FS.Mkdir(fullPath, 0777); err != nil { 1120 return err 1121 } 1122 } 1123 1124 // Begin the folder upload flow by sending the "next file action" to client 1125 if _, err := rwc.Write([]byte{0, dlFldrActionNextFile}); err != nil { 1126 return err 1127 } 1128 1129 fileSize := make([]byte, 4) 1130 1131 for i := 0; i < fileTransfer.ItemCount(); i++ { 1132 s.Stats.UploadCounter += 1 1133 1134 var fu folderUpload 1135 if _, err := io.ReadFull(rwc, fu.DataSize[:]); err != nil { 1136 return err 1137 } 1138 if _, err := io.ReadFull(rwc, fu.IsFolder[:]); err != nil { 1139 return err 1140 } 1141 if _, err := io.ReadFull(rwc, fu.PathItemCount[:]); err != nil { 1142 return err 1143 } 1144 1145 fu.FileNamePath = make([]byte, binary.BigEndian.Uint16(fu.DataSize[:])-4) // -4 to subtract the path separator bytes 1146 1147 if _, err := io.ReadFull(rwc, fu.FileNamePath); err != nil { 1148 return err 1149 } 1150 1151 rLogger.Infow( 1152 "Folder upload continued", 1153 "FormattedPath", fu.FormattedPath(), 1154 "IsFolder", fmt.Sprintf("%x", fu.IsFolder), 1155 "PathItemCount", binary.BigEndian.Uint16(fu.PathItemCount[:]), 1156 ) 1157 1158 if fu.IsFolder == [2]byte{0, 1} { 1159 if _, err := os.Stat(filepath.Join(fullPath, fu.FormattedPath())); os.IsNotExist(err) { 1160 if err := os.Mkdir(filepath.Join(fullPath, fu.FormattedPath()), 0777); err != nil { 1161 return err 1162 } 1163 } 1164 1165 // Tell client to send next file 1166 if _, err := rwc.Write([]byte{0, dlFldrActionNextFile}); err != nil { 1167 return err 1168 } 1169 } else { 1170 nextAction := dlFldrActionSendFile 1171 1172 // Check if we have the full file already. If so, send dlFldrAction_NextFile to client to skip. 1173 _, err = os.Stat(filepath.Join(fullPath, fu.FormattedPath())) 1174 if err != nil && !errors.Is(err, fs.ErrNotExist) { 1175 return err 1176 } 1177 if err == nil { 1178 nextAction = dlFldrActionNextFile 1179 } 1180 1181 // Check if we have a partial file already. If so, send dlFldrAction_ResumeFile to client to resume upload. 1182 incompleteFile, err := os.Stat(filepath.Join(fullPath, fu.FormattedPath()+incompleteFileSuffix)) 1183 if err != nil && !errors.Is(err, fs.ErrNotExist) { 1184 return err 1185 } 1186 if err == nil { 1187 nextAction = dlFldrActionResumeFile 1188 } 1189 1190 if _, err := rwc.Write([]byte{0, uint8(nextAction)}); err != nil { 1191 return err 1192 } 1193 1194 switch nextAction { 1195 case dlFldrActionNextFile: 1196 continue 1197 case dlFldrActionResumeFile: 1198 offset := make([]byte, 4) 1199 binary.BigEndian.PutUint32(offset, uint32(incompleteFile.Size())) 1200 1201 file, err := os.OpenFile(fullPath+"/"+fu.FormattedPath()+incompleteFileSuffix, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 1202 if err != nil { 1203 return err 1204 } 1205 1206 fileResumeData := NewFileResumeData([]ForkInfoList{*NewForkInfoList(offset)}) 1207 1208 b, _ := fileResumeData.BinaryMarshal() 1209 1210 bs := make([]byte, 2) 1211 binary.BigEndian.PutUint16(bs, uint16(len(b))) 1212 1213 if _, err := rwc.Write(append(bs, b...)); err != nil { 1214 return err 1215 } 1216 1217 if _, err := io.ReadFull(rwc, fileSize); err != nil { 1218 return err 1219 } 1220 1221 if err := receiveFile(rwc, file, io.Discard, io.Discard, fileTransfer.bytesSentCounter); err != nil { 1222 s.Logger.Error(err) 1223 } 1224 1225 err = os.Rename(fullPath+"/"+fu.FormattedPath()+".incomplete", fullPath+"/"+fu.FormattedPath()) 1226 if err != nil { 1227 return err 1228 } 1229 1230 case dlFldrActionSendFile: 1231 if _, err := io.ReadFull(rwc, fileSize); err != nil { 1232 return err 1233 } 1234 1235 filePath := filepath.Join(fullPath, fu.FormattedPath()) 1236 1237 hlFile, err := newFileWrapper(s.FS, filePath, 0) 1238 if err != nil { 1239 return err 1240 } 1241 1242 rLogger.Infow("Starting file transfer", "path", filePath, "fileNum", i+1, "fileSize", binary.BigEndian.Uint32(fileSize)) 1243 1244 incWriter, err := hlFile.incFileWriter() 1245 if err != nil { 1246 return err 1247 } 1248 1249 rForkWriter := io.Discard 1250 iForkWriter := io.Discard 1251 if s.Config.PreserveResourceForks { 1252 iForkWriter, err = hlFile.infoForkWriter() 1253 if err != nil { 1254 return err 1255 } 1256 1257 rForkWriter, err = hlFile.rsrcForkWriter() 1258 if err != nil { 1259 return err 1260 } 1261 } 1262 if err := receiveFile(rwc, incWriter, rForkWriter, iForkWriter, fileTransfer.bytesSentCounter); err != nil { 1263 return err 1264 } 1265 1266 if err := os.Rename(filePath+".incomplete", filePath); err != nil { 1267 return err 1268 } 1269 } 1270 1271 // Tell client to send next fileWrapper 1272 if _, err := rwc.Write([]byte{0, dlFldrActionNextFile}); err != nil { 1273 return err 1274 } 1275 } 1276 } 1277 rLogger.Infof("Folder upload complete") 1278 } 1279 1280 return nil 1281 }