github.com/aitjcize/Overlord@v0.0.0-20240314041920-104a804cf5e8/overlord/ghost.go (about) 1 // Copyright 2015 The Chromium OS Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package overlord 6 7 import ( 8 "bufio" 9 "bytes" 10 "crypto/sha1" 11 "crypto/tls" 12 "crypto/x509" 13 "encoding/json" 14 "errors" 15 "fmt" 16 "io" 17 "io/ioutil" 18 "log" 19 "net" 20 "net/http" 21 "net/rpc" 22 "net/rpc/jsonrpc" 23 "os" 24 "os/exec" 25 "path/filepath" 26 "runtime" 27 "strconv" 28 "strings" 29 "sync" 30 "syscall" 31 "time" 32 "unsafe" 33 34 "github.com/creack/pty" 35 "github.com/gorilla/websocket" 36 "github.com/pkg/term/termios" 37 uuid "github.com/satori/go.uuid" 38 ) 39 40 var ghostRPCStubPort = GetenvInt("GHOST_RPC_PORT", 4499) 41 42 const ( 43 defaultShell = "/bin/sh" 44 pingInterval = 10 * time.Second 45 readTimeout = 3 * time.Second 46 connectTimeout = 10 * time.Second 47 httpRequestTimeout = 30 * time.Second 48 retryIntervalSeconds = 2 49 blockSize = 4096 50 ) 51 52 // Exported 53 const ( 54 RandomMID = "##random_mid##" // Random Machine ID identifier 55 ) 56 57 // TLS modes 58 const ( 59 TLSDetect = iota 60 TLSForceDisable 61 TLSForceEnable 62 ) 63 64 // Terminal resize control 65 const ( 66 controlNone = 255 // Control State None 67 controlStart = 128 // Control Start Code 68 controlEnd = 129 // Control End Code 69 ) 70 71 // Stream control 72 const ( 73 stdinClosed = "##STDIN_CLOSED##" 74 ) 75 76 // Registration status 77 const ( 78 statusDisconnected = "disconnected" 79 ) 80 81 type ghostRPCStub struct { 82 ghost *Ghost 83 } 84 85 // EmptyArgs for RPC. 86 type EmptyArgs struct { 87 } 88 89 // EmptyReply for RPC. 90 type EmptyReply struct { 91 } 92 93 func (rpcStub *ghostRPCStub) Reconnect(args *EmptyArgs, reply *EmptyReply) error { 94 rpcStub.ghost.reset = true 95 return nil 96 } 97 98 func (rpcStub *ghostRPCStub) GetStatus(args *EmptyArgs, reply *string) error { 99 *reply = rpcStub.ghost.RegisterStatus 100 if rpcStub.ghost.RegisterStatus == Success { 101 *reply = fmt.Sprintf("%s %s", *reply, rpcStub.ghost.connectedAddr) 102 } 103 return nil 104 } 105 106 func (rpcStub *ghostRPCStub) RegisterTTY(args []string, reply *EmptyReply) error { 107 rpcStub.ghost.RegisterTTY(args[0], args[1]) 108 return nil 109 } 110 111 func (rpcStub *ghostRPCStub) RegisterSession(args []string, reply *EmptyReply) error { 112 rpcStub.ghost.RegisterSession(args[0], args[1]) 113 return nil 114 } 115 116 func (rpcStub *ghostRPCStub) AddToDownloadQueue(args []string, reply *EmptyReply) error { 117 rpcStub.ghost.AddToDownloadQueue(args[0], args[1]) 118 return nil 119 } 120 121 // downloadInfo is a structure that we be place into download queue. 122 // In our case since we always execute 'ghost --download' in our pseudo 123 // terminal so ttyName will always have the form /dev/pts/X 124 type downloadInfo struct { 125 Ttyname string 126 Filename string 127 } 128 129 // fileOperation is a structure for storing file upload/download intent. 130 type fileOperation struct { 131 Action string 132 Filename string 133 Perm int 134 } 135 136 type fileUploadContext struct { 137 Ready bool 138 Data chan []byte 139 } 140 141 type tlsSettings struct { 142 Enabled bool // TLS enabled or not 143 tlsCertFile string // TLS certificate in PEM format 144 verify bool // Wether or not to verify the certificate 145 Config *tls.Config // TLS configuration 146 } 147 148 func newTLSSettings(tlsCertFile string, verify bool) *tlsSettings { 149 return &tlsSettings{false, tlsCertFile, verify, nil} 150 } 151 152 func (t *tlsSettings) updateContext() { 153 if !t.Enabled { 154 t.Config = nil 155 return 156 } 157 158 if t.verify { 159 config := &tls.Config{ 160 InsecureSkipVerify: false, 161 MinVersion: tls.VersionTLS12, 162 RootCAs: nil, 163 } 164 if t.tlsCertFile != "" { 165 log.Println("TLSSettings: using user-supplied ca-certificate") 166 cert, err := ioutil.ReadFile(t.tlsCertFile) 167 if err != nil { 168 log.Fatalln(err) 169 } 170 caCertPool := x509.NewCertPool() 171 caCertPool.AppendCertsFromPEM(cert) 172 config.RootCAs = caCertPool 173 } else { 174 log.Println("TLSSettings: using built-in ca-certificates") 175 } 176 t.Config = config 177 } else { 178 log.Println("TLSSettings: skipping TLS verification!!!") 179 t.Config = &tls.Config{InsecureSkipVerify: true} 180 } 181 } 182 183 func (t *tlsSettings) SetEnabled(enabled bool) { 184 status := "True" 185 if !enabled { 186 status = "False" 187 } 188 189 log.Println("TLSSettings: enabled:", status) 190 if enabled != t.Enabled { 191 t.Enabled = enabled 192 t.updateContext() 193 } 194 } 195 196 // Ghost type is the main context for storing the ghost state. 197 type Ghost struct { 198 *RPCCore 199 addrs []string // List of possible Overlord addresses 200 server *rpc.Server // RPC server handle 201 connectedAddr string // Current connected Overlord address 202 tls *tlsSettings // TLS settings 203 mode int // mode, see constants.go 204 mid string // Machine ID 205 sid string // Session ID 206 terminalSid string // Associated terminal session ID 207 ttyName2Sid sync.Map // Mapping between ttyName and Sid 208 terminalSid2Pid sync.Map // Mapping between terminalSid and pid 209 propFile string // Properties file filename 210 properties map[string]interface{} // Client properties 211 RegisterStatus string // Register status from server response 212 reset bool // Whether to reset the connection 213 quit bool // Whether to quit the connection 214 readChan chan []byte // The incoming data channel 215 readErrChan chan error // The incoming data error channel 216 pauseLanDisc bool // Stop LAN discovery 217 ttyDevice string // Terminal device to open 218 shellCommand string // Shell command to execute 219 fileOp fileOperation // File operation name 220 downloadQueue chan downloadInfo // Download queue 221 uploadContext fileUploadContext // File upload context 222 port int // Port number to forward 223 tlsMode int // TLS mode 224 } 225 226 // NewGhost creates a Ghost object. 227 func NewGhost(addrs []string, tls *tlsSettings, mode int, mid string) *Ghost { 228 var ( 229 finalMid string 230 err error 231 ) 232 233 if mid == RandomMID { 234 finalMid = uuid.NewV4().String() 235 } else if mid != "" { 236 finalMid = mid 237 } else { 238 finalMid, err = GetMachineID() 239 if err != nil { 240 log.Fatalln("Unable to get machine ID:", err) 241 } 242 } 243 return &Ghost{ 244 RPCCore: NewRPCCore(nil), 245 addrs: addrs, 246 tls: tls, 247 mode: mode, 248 mid: finalMid, 249 sid: uuid.NewV4().String(), 250 properties: make(map[string]interface{}), 251 RegisterStatus: statusDisconnected, 252 reset: false, 253 quit: false, 254 pauseLanDisc: false, 255 downloadQueue: make(chan downloadInfo), 256 uploadContext: fileUploadContext{Data: make(chan []byte)}, 257 } 258 } 259 260 // SetSid sets the Session ID for the Ghost instance. 261 func (ghost *Ghost) SetSid(sid string) *Ghost { 262 ghost.sid = sid 263 return ghost 264 } 265 266 // SetTerminalSid sets the terminal session ID for the Ghost instance. 267 func (ghost *Ghost) SetTerminalSid(sid string) *Ghost { 268 ghost.terminalSid = sid 269 return ghost 270 } 271 272 // SetPropFile sets the property file filename. 273 func (ghost *Ghost) SetPropFile(propFile string) *Ghost { 274 ghost.propFile = propFile 275 return ghost 276 } 277 278 // SetTtyDevice sets the TTY device name to open. 279 func (ghost *Ghost) SetTtyDevice(ttyDevice string) *Ghost { 280 ghost.ttyDevice = ttyDevice 281 return ghost 282 } 283 284 // SetShellCommand sets the shell comamnd to execute. 285 func (ghost *Ghost) SetShellCommand(command string) *Ghost { 286 ghost.shellCommand = command 287 return ghost 288 } 289 290 // SetFileOp sets the file operation to perform. 291 func (ghost *Ghost) SetFileOp(operation, filename string, perm int) *Ghost { 292 ghost.fileOp.Action = operation 293 ghost.fileOp.Filename = filename 294 ghost.fileOp.Perm = perm 295 return ghost 296 } 297 298 // SetModeForwardPort sets the port to forward. 299 func (ghost *Ghost) SetModeForwardPort(port int) *Ghost { 300 ghost.port = port 301 return ghost 302 } 303 304 // SetTLSMode sets the mode of tls detection. 305 func (ghost *Ghost) SetTLSMode(mode int) *Ghost { 306 ghost.tlsMode = mode 307 return ghost 308 } 309 310 func (ghost *Ghost) existsInAddr(target string) bool { 311 for _, x := range ghost.addrs { 312 if target == x { 313 return true 314 } 315 } 316 return false 317 } 318 319 func (ghost *Ghost) loadProperties() { 320 if ghost.propFile == "" { 321 return 322 } 323 324 bytes, err := ioutil.ReadFile(ghost.propFile) 325 if err != nil { 326 log.Printf("loadProperties: %s\n", err) 327 return 328 } 329 330 if err := json.Unmarshal(bytes, &ghost.properties); err != nil { 331 log.Printf("loadProperties: %s\n", err) 332 return 333 } 334 } 335 336 func (ghost *Ghost) tlsEnabled(addr string) (bool, error) { 337 conn, err := net.DialTimeout("tcp", addr, connectTimeout) 338 if err != nil { 339 return false, err 340 } 341 defer conn.Close() 342 343 colonPos := strings.LastIndex(addr, ":") 344 tlsConn := tls.Client(conn, &tls.Config{ 345 // Allow any certificate since we only want to check if server talks TLS. 346 InsecureSkipVerify: true, 347 MinVersion: tls.VersionTLS12, 348 RootCAs: nil, 349 ServerName: addr[:colonPos], 350 }) 351 defer tlsConn.Close() 352 353 handshakeTimeout := false 354 355 // Close the connection to stop handshake if it's taking too long. 356 go func() { 357 time.Sleep(connectTimeout) 358 conn.Close() 359 handshakeTimeout = true 360 }() 361 362 err = tlsConn.Handshake() 363 if err != nil || handshakeTimeout { 364 return false, nil 365 } 366 return true, nil 367 } 368 369 // Upgrade starts the upgrade sequence of the ghost instance. 370 func (ghost *Ghost) Upgrade() error { 371 log.Println("Upgrade: initiating upgrade sequence...") 372 373 exePath, err := os.Executable() 374 if err != nil { 375 return errors.New("Upgrade: can not find executable path") 376 } 377 378 var buffer bytes.Buffer 379 var client http.Client 380 381 httpsEnabled, err := ghost.tlsEnabled(ghost.connectedAddr) 382 if err != nil { 383 return errors.New("Upgrade: failed to connect to Overlord HTTP server, " + 384 "abort") 385 } 386 387 if ghost.tls.Enabled && !httpsEnabled { 388 return errors.New("Upgrade: TLS enforced but found Overlord HTTP server " + 389 "without TLS enabled! Possible mis-configuration or DNS/IP spoofing " + 390 "detected, abort") 391 } 392 393 proto := "http" 394 if httpsEnabled { 395 proto = "https" 396 } 397 url := fmt.Sprintf("%s://%s/upgrade/ghost.%s", proto, ghost.connectedAddr, 398 GetPlatformString()) 399 400 if httpsEnabled { 401 tr := &http.Transport{TLSClientConfig: ghost.tls.Config} 402 client = http.Client{Transport: tr, Timeout: httpRequestTimeout} 403 } else { 404 client = http.Client{Timeout: httpRequestTimeout} 405 } 406 407 // Download the sha1sum for ghost for verification 408 resp, err := client.Get(url + ".sha1") 409 if err != nil || resp.StatusCode != 200 { 410 return errors.New("Upgrade: failed to download sha1sum file, abort") 411 } 412 sha1sumBytes := make([]byte, 40) 413 resp.Body.Read(sha1sumBytes) 414 sha1sum := strings.Trim(string(sha1sumBytes), "\n ") 415 defer resp.Body.Close() 416 417 // Compare the current version of ghost, if sha1 is the same, skip upgrading 418 currentSha1sum, _ := GetFileSha1(exePath) 419 420 if currentSha1sum == sha1sum { 421 log.Println("Upgrade: ghost is already up-to-date, skipping upgrade") 422 return nil 423 } 424 425 // Download upgrade version of ghost 426 resp2, err := client.Get(url) 427 if err != nil || resp2.StatusCode != 200 { 428 return errors.New("Upgrade: failed to download upgrade, abort") 429 } 430 defer resp2.Body.Close() 431 432 _, err = buffer.ReadFrom(resp2.Body) 433 if err != nil { 434 return errors.New("Upgrade: failed to download upgrade, abort") 435 } 436 437 // Compare SHA1 sum 438 if sha1sum != fmt.Sprintf("%x", sha1.Sum(buffer.Bytes())) { 439 return errors.New("Upgrade: sha1sum mismatch, abort") 440 } 441 442 os.Remove(exePath) 443 exeFile, err := os.Create(exePath) 444 if err != nil { 445 return errors.New("Upgrade: can not open ghost executable for writing") 446 } 447 _, err = buffer.WriteTo(exeFile) 448 if err != nil { 449 return fmt.Errorf("Upgrade: %s", err) 450 } 451 exeFile.Close() 452 453 err = os.Chmod(exePath, 0755) 454 if err != nil { 455 return fmt.Errorf("Upgrade: %s", err) 456 } 457 458 log.Println("Upgrade: restarting ghost...") 459 os.Args[0] = exePath 460 err = syscall.Exec(exePath, os.Args, os.Environ()) 461 if err != nil { 462 return fmt.Errorf("Upgrade: exec: %s", err) 463 } 464 return nil 465 } 466 467 func (ghost *Ghost) handleTerminalRequest(req *Request) error { 468 type RequestParams struct { 469 Sid string `json:"sid"` 470 TtyDevice string `json:"tty_device"` 471 } 472 473 var params RequestParams 474 if err := json.Unmarshal(req.Params, ¶ms); err != nil { 475 return err 476 } 477 478 go func() { 479 log.Printf("Received terminal command, Terminal agent %s spawned\n", params.Sid) 480 addrs := []string{ghost.connectedAddr} 481 // Terminal sessions are identified with session ID, thus we don't care 482 // machine ID and can make them random. 483 g := NewGhost(addrs, ghost.tls, ModeTerminal, RandomMID).SetSid( 484 params.Sid).SetTtyDevice(params.TtyDevice) 485 g.Start(false, false) 486 }() 487 488 res := NewResponse(req.Rid, Success, nil) 489 return ghost.SendResponse(res) 490 } 491 492 func (ghost *Ghost) handleShellRequest(req *Request) error { 493 type RequestParams struct { 494 Sid string `json:"sid"` 495 Cmd string `json:"command"` 496 } 497 498 var params RequestParams 499 if err := json.Unmarshal(req.Params, ¶ms); err != nil { 500 return err 501 } 502 503 go func() { 504 log.Printf("Received shell command: %s, Shell agent %s spawned\n", params.Cmd, params.Sid) 505 addrs := []string{ghost.connectedAddr} 506 // Shell sessions are identified with session ID, thus we don't care 507 // machine ID and can make them random. 508 g := NewGhost(addrs, ghost.tls, ModeShell, RandomMID).SetSid( 509 params.Sid).SetShellCommand(params.Cmd) 510 g.Start(false, false) 511 }() 512 513 res := NewResponse(req.Rid, Success, nil) 514 return ghost.SendResponse(res) 515 } 516 517 func (ghost *Ghost) handleFileDownloadRequest(req *Request) error { 518 type RequestParams struct { 519 Sid string `json:"sid"` 520 Filename string `json:"filename"` 521 } 522 523 var params RequestParams 524 if err := json.Unmarshal(req.Params, ¶ms); err != nil { 525 return err 526 } 527 528 filename := params.Filename 529 if !strings.HasPrefix(filename, "/") { 530 home := os.Getenv("HOME") 531 if home == "" { 532 home = "/tmp" 533 } 534 filename = filepath.Join(home, filename) 535 } 536 537 f, err := os.Open(filename) 538 if err != nil { 539 res := NewResponse(req.Rid, err.Error(), nil) 540 return ghost.SendResponse(res) 541 } 542 f.Close() 543 544 go func() { 545 log.Printf("Received file_download command, File agent %s spawned\n", params.Sid) 546 addrs := []string{ghost.connectedAddr} 547 g := NewGhost(addrs, ghost.tls, ModeFile, RandomMID).SetSid( 548 params.Sid).SetFileOp("download", filename, 0) 549 g.Start(false, false) 550 }() 551 552 res := NewResponse(req.Rid, Success, nil) 553 return ghost.SendResponse(res) 554 } 555 556 func (ghost *Ghost) handleFileUploadRequest(req *Request) error { 557 type RequestParams struct { 558 Sid string `json:"sid"` 559 TerminalSid string `json:"terminal_sid"` 560 Filename string `json:"filename"` 561 Dest string `json:"dest"` 562 Perm int `json:"perm"` 563 CheckOnly bool `json:"check_only"` 564 } 565 566 var params RequestParams 567 if err := json.Unmarshal(req.Params, ¶ms); err != nil { 568 return err 569 } 570 571 targetDir := os.Getenv("HOME") 572 if targetDir == "" { 573 targetDir = "/tmp" 574 } 575 576 destPath := params.Dest 577 if destPath != "" { 578 if !filepath.IsAbs(destPath) { 579 destPath = filepath.Join(targetDir, destPath) 580 } 581 582 st, err := os.Stat(destPath) 583 if err == nil && st.Mode().IsDir() { 584 destPath = filepath.Join(destPath, params.Filename) 585 } 586 } else { 587 if params.TerminalSid != "" { 588 if pid, ok := ghost.terminalSid2Pid.Load(params.TerminalSid); ok { 589 cwd, err := GetProcessWorkingDirectory(pid.(int)) 590 if err == nil { 591 targetDir = cwd 592 } 593 } 594 } 595 destPath = filepath.Join(targetDir, params.Filename) 596 } 597 598 os.MkdirAll(filepath.Dir(destPath), 0755) 599 600 f, err := os.Create(destPath) 601 if err != nil { 602 res := NewResponse(req.Rid, err.Error(), nil) 603 return ghost.SendResponse(res) 604 } 605 f.Close() 606 607 // If not check_only, spawn ModeFile mode ghost agent to handle upload 608 if !params.CheckOnly { 609 go func() { 610 log.Printf("Received file_upload command, File agent %s spawned\n", 611 params.Sid) 612 addrs := []string{ghost.connectedAddr} 613 g := NewGhost(addrs, ghost.tls, ModeFile, RandomMID).SetSid( 614 params.Sid).SetFileOp("upload", destPath, params.Perm) 615 g.Start(false, false) 616 }() 617 } 618 619 res := NewResponse(req.Rid, Success, nil) 620 return ghost.SendResponse(res) 621 } 622 623 func (ghost *Ghost) handleModeForwardRequest(req *Request) error { 624 type RequestParams struct { 625 Sid string `json:"sid"` 626 Port int `json:"port"` 627 } 628 629 var params RequestParams 630 if err := json.Unmarshal(req.Params, ¶ms); err != nil { 631 return err 632 } 633 634 go func() { 635 log.Printf("Received forward command, ModeForward agent %s spawned\n", params.Sid) 636 addrs := []string{ghost.connectedAddr} 637 g := NewGhost(addrs, ghost.tls, ModeForward, RandomMID).SetSid( 638 params.Sid).SetModeForwardPort(params.Port) 639 g.Start(false, false) 640 }() 641 642 res := NewResponse(req.Rid, Success, nil) 643 return ghost.SendResponse(res) 644 } 645 646 // StartDownloadServer starts the download server. 647 func (ghost *Ghost) StartDownloadServer() error { 648 log.Println("StartDownloadServer: started") 649 650 defer func() { 651 ghost.quit = true 652 ghost.Conn.Close() 653 log.Println("StartDownloadServer: terminated") 654 }() 655 656 file, err := os.Open(ghost.fileOp.Filename) 657 if err != nil { 658 return err 659 } 660 defer file.Close() 661 662 io.Copy(ghost.Conn, file) 663 return nil 664 } 665 666 // StartUploadServer starts the upload server. 667 func (ghost *Ghost) StartUploadServer() error { 668 log.Println("StartUploadServer: started") 669 670 defer func() { 671 log.Println("StartUploadServer: terminated") 672 }() 673 674 filePath := ghost.fileOp.Filename 675 dirPath := filepath.Dir(filePath) 676 if _, err := os.Stat(dirPath); os.IsNotExist(err) { 677 os.MkdirAll(dirPath, 0755) 678 } 679 680 file, err := os.Create(filePath) 681 if err != nil { 682 return err 683 } 684 defer file.Close() 685 686 for { 687 buffer := <-ghost.uploadContext.Data 688 if buffer == nil { 689 break 690 } 691 file.Write(buffer) 692 } 693 694 if ghost.fileOp.Perm > 0 { 695 file.Chmod(os.FileMode(ghost.fileOp.Perm)) 696 } 697 698 return nil 699 } 700 701 func (ghost *Ghost) handleRequest(req *Request) error { 702 var err error 703 switch req.Name { 704 case "upgrade": 705 err = ghost.Upgrade() 706 case "terminal": 707 err = ghost.handleTerminalRequest(req) 708 case "shell": 709 err = ghost.handleShellRequest(req) 710 case "file_download": 711 err = ghost.handleFileDownloadRequest(req) 712 case "clear_to_download": 713 err = ghost.StartDownloadServer() 714 case "file_upload": 715 err = ghost.handleFileUploadRequest(req) 716 case "forward": 717 err = ghost.handleModeForwardRequest(req) 718 default: 719 err = errors.New(`Received unregistered command "` + req.Name + `", ignoring`) 720 } 721 return err 722 } 723 724 func (ghost *Ghost) processRequests(reqs []*Request) error { 725 for _, req := range reqs { 726 if err := ghost.handleRequest(req); err != nil { 727 return err 728 } 729 } 730 return nil 731 } 732 733 // Ping sends a ping message to the overlord server. 734 func (ghost *Ghost) Ping() error { 735 pingHandler := func(res *Response) error { 736 if res == nil { 737 ghost.reset = true 738 return errors.New("Ping timeout") 739 } 740 return nil 741 } 742 req := NewRequest("ping", nil) 743 req.SetTimeout(pingTimeout) 744 return ghost.SendRequest(req, pingHandler) 745 } 746 747 func (ghost *Ghost) handleTTYControl(tty *os.File, controlString string) error { 748 // Terminal Command for ghost 749 // Implements the Message interface. 750 type TerminalCommand struct { 751 Command string `json:"command"` 752 Params json.RawMessage `json:"params"` 753 } 754 755 // winsize stores the Height and Width of a terminal. 756 type winsize struct { 757 height uint16 758 width uint16 759 } 760 761 var control TerminalCommand 762 err := json.Unmarshal([]byte(controlString), &control) 763 if err != nil { 764 log.Println("mal-formed JSON request, ignored") 765 return nil 766 } 767 768 command := control.Command 769 if command == "resize" { 770 var params []int 771 err := json.Unmarshal([]byte(control.Params), ¶ms) 772 if err != nil || len(params) != 2 { 773 log.Println("mal-formed JSON request, ignored") 774 return nil 775 } 776 ws := &winsize{width: uint16(params[1]), height: uint16(params[0])} 777 syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), 778 uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws))) 779 } else { 780 return errors.New("Invalid request command " + command) 781 } 782 return nil 783 } 784 785 func (ghost *Ghost) getTTYName() (string, error) { 786 ttyName, err := os.Readlink(fmt.Sprintf("/proc/%d/fd/0", os.Getpid())) 787 if err != nil { 788 return "", err 789 } 790 return ttyName, nil 791 } 792 793 // SpawnTTYServer Spawns a TTY server and forward I/O to the TCP socket. 794 func (ghost *Ghost) SpawnTTYServer(res *Response) error { 795 log.Println("SpawnTTYServer: started") 796 797 var tty *os.File 798 var err error 799 stopConn := make(chan struct{}) 800 801 defer func() { 802 ghost.quit = true 803 if tty != nil { 804 tty.Close() 805 } 806 ghost.Conn.Close() 807 log.Println("SpawnTTYServer: terminated") 808 }() 809 810 if ghost.ttyDevice == "" { 811 // No TTY device specified, open a PTY (pseudo terminal) instead. 812 shell := os.Getenv("SHELL") 813 if shell == "" { 814 shell = defaultShell 815 } 816 817 home := os.Getenv("HOME") 818 if home == "" { 819 home = "/root" 820 } 821 822 // Add ghost executable to PATH 823 exePath, err := os.Executable() 824 if err == nil { 825 os.Setenv("PATH", fmt.Sprintf("%s:%s", filepath.Dir(exePath), 826 os.Getenv("PATH"))) 827 } 828 829 os.Chdir(home) 830 cmd := exec.Command(shell) 831 tty, err = pty.Start(cmd) 832 if err != nil { 833 return errors.New(`SpawnTTYServer: Cannot start "` + shell + `", abort`) 834 } 835 836 defer func() { 837 cmd.Process.Kill() 838 }() 839 840 // Register the mapping of sid and ttyName 841 ttyName, err := termios.Ptsname(tty.Fd()) 842 if err != nil { 843 return err 844 } 845 846 client, err := ghostRPCStubServer() 847 848 // Ghost could be launched without RPC server, ignore registration 849 if err == nil { 850 err = client.Call("rpc.RegisterTTY", []string{ghost.sid, ttyName}, 851 &EmptyReply{}) 852 if err != nil { 853 return err 854 } 855 856 err = client.Call("rpc.RegisterSession", []string{ 857 ghost.sid, strconv.Itoa(cmd.Process.Pid)}, &EmptyReply{}) 858 if err != nil { 859 return err 860 } 861 } 862 863 go func() { 864 io.Copy(ghost.Conn, tty) 865 cmd.Wait() 866 close(stopConn) 867 }() 868 } else { 869 // Open a TTY device 870 tty, err = os.OpenFile(ghost.ttyDevice, os.O_RDWR, 0) 871 if err != nil { 872 return err 873 } 874 875 var term syscall.Termios 876 err := termios.Tcgetattr(tty.Fd(), &term) 877 if err != nil { 878 return nil 879 } 880 881 termios.Cfmakeraw(&term) 882 term.Iflag &= (syscall.IXON | syscall.IXOFF) 883 term.Cflag |= syscall.CLOCAL 884 term.Ispeed = syscall.B115200 885 term.Ospeed = syscall.B115200 886 887 if err = termios.Tcsetattr(tty.Fd(), termios.TCSANOW, &term); err != nil { 888 return err 889 } 890 891 go func() { 892 io.Copy(ghost.Conn, tty) 893 close(stopConn) 894 }() 895 } 896 897 var controlBuffer bytes.Buffer 898 var writeBuffer bytes.Buffer 899 controlState := controlNone 900 901 processBuffer := func(buffer []byte) error { 902 writeBuffer.Reset() 903 for len(buffer) > 0 { 904 if controlState != controlNone { 905 index := bytes.IndexByte(buffer, controlEnd) 906 if index != -1 { 907 controlBuffer.Write(buffer[:index]) 908 err := ghost.handleTTYControl(tty, controlBuffer.String()) 909 controlState = controlNone 910 controlBuffer.Reset() 911 if err != nil { 912 return err 913 } 914 buffer = buffer[index+1:] 915 } else { 916 controlBuffer.Write(buffer) 917 buffer = buffer[0:0] 918 } 919 } else { 920 index := bytes.IndexByte(buffer, controlStart) 921 if index != -1 { 922 controlState = controlStart 923 writeBuffer.Write(buffer[:index]) 924 buffer = buffer[index+1:] 925 } else { 926 writeBuffer.Write(buffer) 927 buffer = buffer[0:0] 928 } 929 } 930 } 931 if writeBuffer.Len() != 0 { 932 tty.Write(writeBuffer.Bytes()) 933 } 934 return nil 935 } 936 937 if ghost.ReadBuffer != "" { 938 processBuffer([]byte(ghost.ReadBuffer)) 939 ghost.ReadBuffer = "" 940 } 941 942 for { 943 select { 944 case buffer := <-ghost.readChan: 945 err := processBuffer(buffer) 946 if err != nil { 947 log.Println("SpawnTTYServer:", err) 948 } 949 case err := <-ghost.readErrChan: 950 if err == io.EOF { 951 log.Println("SpawnTTYServer: connection terminated") 952 return nil 953 } 954 return err 955 case <-stopConn: 956 return nil 957 } 958 } 959 } 960 961 // SpawnShellServer spawns a Shell server and forward input/output from/to the TCP socket. 962 func (ghost *Ghost) SpawnShellServer(res *Response) error { 963 log.Println("SpawnShellServer: started") 964 965 var err error 966 967 defer func() { 968 ghost.quit = true 969 if err != nil { 970 ghost.Conn.Write([]byte(err.Error() + "\n")) 971 } 972 ghost.Conn.Close() 973 log.Println("SpawnShellServer: terminated") 974 }() 975 976 // Execute shell command from HOME directory 977 home := os.Getenv("HOME") 978 if home == "" { 979 home = "/tmp" 980 } 981 os.Chdir(home) 982 983 // Add ghost executable to PATH 984 exePath, err := os.Executable() 985 if err == nil { 986 os.Setenv("PATH", fmt.Sprintf("%s:%s", os.Getenv("PATH"), 987 filepath.Dir(exePath))) 988 } 989 990 cmd := exec.Command(defaultShell, "-c", ghost.shellCommand) 991 stdout, err := cmd.StdoutPipe() 992 if err != nil { 993 return err 994 } 995 stderr, err := cmd.StderrPipe() 996 if err != nil { 997 return err 998 } 999 stdin, err := cmd.StdinPipe() 1000 if err != nil { 1001 return err 1002 } 1003 1004 stopConn := make(chan struct{}) 1005 1006 if ghost.ReadBuffer != "" { 1007 stdin.Write([]byte(ghost.ReadBuffer)) 1008 ghost.ReadBuffer = "" 1009 } 1010 1011 go io.Copy(ghost.Conn, stdout) 1012 go func() { 1013 io.Copy(ghost.Conn, stderr) 1014 close(stopConn) 1015 }() 1016 1017 if err = cmd.Start(); err != nil { 1018 return err 1019 } 1020 1021 defer func() { 1022 time.Sleep(100 * time.Millisecond) // Wait for process to terminate 1023 1024 process := (*PollableProcess)(cmd.Process) 1025 _, err = process.Poll() 1026 // Check if the process is terminated. If not, send SIGlogcatTypeVT100 to the process, 1027 // then wait for 1 second. Send another SIGKILL to make sure the process is 1028 // terminated. 1029 if err != nil { 1030 cmd.Process.Signal(syscall.SIGTERM) 1031 time.Sleep(time.Second) 1032 cmd.Process.Kill() 1033 cmd.Wait() 1034 } 1035 }() 1036 1037 for { 1038 select { 1039 case buf := <-ghost.readChan: 1040 if len(buf) >= len(stdinClosed)*2 { 1041 idx := bytes.Index(buf, []byte(stdinClosed+stdinClosed)) 1042 if idx != -1 { 1043 stdin.Write(buf[:idx]) 1044 stdin.Close() 1045 continue 1046 } 1047 } 1048 stdin.Write(buf) 1049 case err := <-ghost.readErrChan: 1050 if err == io.EOF { 1051 log.Println("SpawnShellServer: connection terminated") 1052 return nil 1053 } 1054 log.Printf("SpawnShellServer: %s\n", err) 1055 return err 1056 case <-stopConn: 1057 return nil 1058 } 1059 } 1060 } 1061 1062 // InitiatefileOperation initiates a file operation. 1063 // The operation could either be 'download' or 'upload' 1064 // This function starts handshake with overlord then execute download sequence. 1065 func (ghost *Ghost) InitiatefileOperation(res *Response) error { 1066 if ghost.fileOp.Action == "download" { 1067 fi, err := os.Stat(ghost.fileOp.Filename) 1068 if err != nil { 1069 return err 1070 } 1071 1072 req := NewRequest("request_to_download", map[string]interface{}{ 1073 "terminal_sid": ghost.terminalSid, 1074 "filename": filepath.Base(ghost.fileOp.Filename), 1075 "size": fi.Size(), 1076 }) 1077 1078 return ghost.SendRequest(req, nil) 1079 } else if ghost.fileOp.Action == "upload" { 1080 ghost.uploadContext.Ready = true 1081 req := NewRequest("clear_to_upload", nil) 1082 req.SetTimeout(-1) 1083 err := ghost.SendRequest(req, nil) 1084 if err != nil { 1085 return err 1086 } 1087 go ghost.StartUploadServer() 1088 return nil 1089 } 1090 return errors.New("InitiatefileOperation: unknown file operation, ignored") 1091 } 1092 1093 // SpawnPortModeForwardServer spawns a port forwarding server and forward I/O to 1094 // the TCP socket. 1095 func (ghost *Ghost) SpawnPortModeForwardServer(res *Response) error { 1096 log.Println("SpawnPortModeForwardServer: started") 1097 1098 var err error 1099 1100 defer func() { 1101 ghost.quit = true 1102 if err != nil { 1103 ghost.Conn.Write([]byte(err.Error() + "\n")) 1104 } 1105 ghost.Conn.Close() 1106 log.Println("SpawnPortModeForwardServer: terminated") 1107 }() 1108 1109 conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", ghost.port), 1110 connectTimeout) 1111 if err != nil { 1112 return err 1113 } 1114 defer conn.Close() 1115 1116 stopConn := make(chan struct{}) 1117 1118 if ghost.ReadBuffer != "" { 1119 conn.Write([]byte(ghost.ReadBuffer)) 1120 ghost.ReadBuffer = "" 1121 } 1122 1123 go func() { 1124 io.Copy(ghost.Conn, conn) 1125 close(stopConn) 1126 }() 1127 1128 for { 1129 select { 1130 case buf := <-ghost.readChan: 1131 conn.Write(buf) 1132 case err := <-ghost.readErrChan: 1133 if err == io.EOF { 1134 log.Println("SpawnPortModeForwardServer: connection terminated") 1135 return nil 1136 } 1137 return err 1138 case <-stopConn: 1139 return nil 1140 } 1141 } 1142 } 1143 1144 // Register existent to Overlord. 1145 func (ghost *Ghost) Register() error { 1146 for _, addr := range ghost.addrs { 1147 var ( 1148 conn net.Conn 1149 err error 1150 ) 1151 1152 log.Printf("Trying %s ...\n", addr) 1153 ghost.Reset() 1154 1155 // Check if server has TLS enabled. 1156 // Only control channel needs to determine if TLS is enabled. Other mode 1157 // should use the tlsSettings passed in when it was spawned. 1158 if ghost.mode == ModeControl { 1159 var enabled bool 1160 1161 switch ghost.tlsMode { 1162 case TLSDetect: 1163 enabled, err = ghost.tlsEnabled(addr) 1164 if err != nil { 1165 continue 1166 } 1167 case TLSForceEnable: 1168 enabled = true 1169 case TLSForceDisable: 1170 enabled = false 1171 } 1172 1173 ghost.tls.SetEnabled(enabled) 1174 } 1175 1176 proto := "ws" 1177 if ghost.tls.Enabled { 1178 proto = "wss" 1179 } 1180 uri := fmt.Sprintf("%s://%s/connect", proto, addr) 1181 1182 dialer := websocket.DefaultDialer 1183 1184 if ghost.tls.Config != nil { 1185 dialer = &websocket.Dialer{ 1186 Proxy: http.ProxyFromEnvironment, 1187 HandshakeTimeout: 45 * time.Second, 1188 TLSClientConfig: ghost.tls.Config, 1189 } 1190 } 1191 1192 wsConn, _, err := dialer.Dial(uri, http.Header{}) 1193 if err != nil { 1194 log.Printf("error: %s\n", err) 1195 continue 1196 } 1197 1198 conn = wsConn.UnderlyingConn() 1199 1200 log.Println("Connection established, registering...") 1201 1202 ghost.Conn = conn 1203 req := NewRequest("register", map[string]interface{}{ 1204 "mid": ghost.mid, 1205 "sid": ghost.sid, 1206 "mode": ghost.mode, 1207 "properties": ghost.properties, 1208 }) 1209 1210 registered := func(res *Response) error { 1211 if res == nil { 1212 ghost.reset = true 1213 return errors.New("Register request timeout") 1214 } else if res.Response != Success { 1215 log.Println("Register:", res.Response) 1216 } else { 1217 log.Printf("Registered with Overlord at %s", addr) 1218 ghost.connectedAddr = addr 1219 if err := ghost.Upgrade(); err != nil { 1220 log.Println(err) 1221 } 1222 ghost.pauseLanDisc = true 1223 } 1224 ghost.RegisterStatus = res.Response 1225 return nil 1226 } 1227 1228 var handler ResponseHandler 1229 switch ghost.mode { 1230 case ModeControl: 1231 handler = registered 1232 case ModeTerminal: 1233 handler = ghost.SpawnTTYServer 1234 case ModeShell: 1235 handler = ghost.SpawnShellServer 1236 case ModeFile: 1237 handler = ghost.InitiatefileOperation 1238 case ModeForward: 1239 handler = ghost.SpawnPortModeForwardServer 1240 } 1241 err = ghost.SendRequest(req, handler) 1242 return nil 1243 } 1244 1245 return errors.New("Cannot connect to any server") 1246 } 1247 1248 // InitiateDownload initiates a client-initiated download request. 1249 func (ghost *Ghost) InitiateDownload(info downloadInfo) { 1250 go func() { 1251 addrs := []string{ghost.connectedAddr} 1252 1253 val, ok := ghost.ttyName2Sid.Load(info.Ttyname) 1254 if !ok { 1255 log.Printf("Failed to get SID") 1256 return 1257 } 1258 1259 g := NewGhost(addrs, ghost.tls, ModeFile, RandomMID).SetTerminalSid( 1260 val.(string)).SetFileOp("download", info.Filename, 0) 1261 g.Start(false, false) 1262 }() 1263 } 1264 1265 // Reset all states for a new connection. 1266 func (ghost *Ghost) Reset() { 1267 ghost.ClearRequests() 1268 ghost.reset = false 1269 ghost.loadProperties() 1270 ghost.RegisterStatus = statusDisconnected 1271 } 1272 1273 // Listen is the main routine for listen to socket messages. 1274 func (ghost *Ghost) Listen() error { 1275 readChan, readErrChan := ghost.SpawnReaderRoutine() 1276 pingTicker := time.NewTicker(time.Duration(pingInterval)) 1277 reqTicker := time.NewTicker(time.Duration(timeoutCheckInterval)) 1278 1279 ghost.readChan = readChan 1280 ghost.readErrChan = readErrChan 1281 1282 defer func() { 1283 ghost.StopConn() 1284 ghost.pauseLanDisc = false 1285 }() 1286 1287 for { 1288 select { 1289 case buffer := <-readChan: 1290 if ghost.uploadContext.Ready { 1291 if ghost.ReadBuffer != "" { 1292 // Write the leftover from previous ReadBuffer 1293 ghost.uploadContext.Data <- []byte(ghost.ReadBuffer) 1294 ghost.ReadBuffer = "" 1295 } 1296 ghost.uploadContext.Data <- buffer 1297 continue 1298 } 1299 reqs := ghost.ParseRequests(string(buffer), ghost.RegisterStatus != Success) 1300 if ghost.quit { 1301 return nil 1302 } 1303 if err := ghost.processRequests(reqs); err != nil { 1304 log.Println(err) 1305 } 1306 case err := <-readErrChan: 1307 if err == io.EOF { 1308 if ghost.uploadContext.Ready { 1309 ghost.uploadContext.Data <- nil 1310 ghost.quit = true 1311 return nil 1312 } 1313 return errors.New("Connection dropped") 1314 } 1315 return err 1316 case info := <-ghost.downloadQueue: 1317 ghost.InitiateDownload(info) 1318 case <-pingTicker.C: 1319 if ghost.mode == ModeControl { 1320 ghost.Ping() 1321 } 1322 case <-reqTicker.C: 1323 err := ghost.ScanForTimeoutRequests() 1324 if ghost.reset { 1325 if err == nil { 1326 err = errors.New("reset request") 1327 } 1328 return err 1329 } 1330 } 1331 } 1332 } 1333 1334 // RegisterTTY register the TTY to a session. 1335 func (ghost *Ghost) RegisterTTY(sesssionID, ttyName string) { 1336 ghost.ttyName2Sid.Store(ttyName, sesssionID) 1337 } 1338 1339 // RegisterSession register the PID to a session. 1340 func (ghost *Ghost) RegisterSession(sesssionID, pidStr string) { 1341 pid, err := strconv.Atoi(pidStr) 1342 if err != nil { 1343 panic(err) 1344 } 1345 ghost.terminalSid2Pid.Store(sesssionID, pid) 1346 } 1347 1348 // AddToDownloadQueue adds a downloadInfo to the download queue 1349 func (ghost *Ghost) AddToDownloadQueue(ttyName, filename string) { 1350 ghost.downloadQueue <- downloadInfo{ttyName, filename} 1351 } 1352 1353 // StartLanDiscovery starts listening to LAN discovery message. 1354 func (ghost *Ghost) StartLanDiscovery() { 1355 log.Println("LAN discovery: started") 1356 buf := make([]byte, bufferSize) 1357 conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", OverlordLDPort)) 1358 if err != nil { 1359 log.Printf("LAN discovery: %s, abort\n", err) 1360 return 1361 } 1362 1363 defer func() { 1364 conn.Close() 1365 log.Println("LAN discovery: stopped") 1366 }() 1367 1368 for { 1369 conn.SetReadDeadline(time.Now().Add(readTimeout)) 1370 n, remote, err := conn.ReadFrom(buf) 1371 1372 if ghost.pauseLanDisc { 1373 log.Println("LAN discovery: paused") 1374 ticker := time.NewTicker(readTimeout) 1375 waitLoop: 1376 for { 1377 select { 1378 case <-ticker.C: 1379 if !ghost.pauseLanDisc { 1380 break waitLoop 1381 } 1382 } 1383 } 1384 log.Println("LAN discovery: resumed") 1385 continue 1386 } 1387 1388 if err != nil { 1389 continue 1390 } 1391 1392 // LAN discovery packet format: "OVERLORD [host]:port" 1393 data := strings.Split(string(buf[:n]), " ") 1394 if data[0] != "OVERLORD" { 1395 continue 1396 } 1397 1398 overlordAddrParts := strings.Split(data[1], ":") 1399 remoteAddrParts := strings.Split(remote.String(), ":") 1400 1401 var remoteAddr string 1402 if strings.Trim(overlordAddrParts[0], " ") == "" { 1403 remoteAddr = remoteAddrParts[0] + ":" + overlordAddrParts[1] 1404 } else { 1405 remoteAddr = data[1] 1406 } 1407 1408 if !ghost.existsInAddr(remoteAddr) { 1409 log.Printf("LAN discovery: got overlord address %s", remoteAddr) 1410 ghost.addrs = append(ghost.addrs, remoteAddr) 1411 } 1412 } 1413 } 1414 1415 // ServeHTTP method for serving JSON-RPC over HTTP. 1416 func (ghost *Ghost) ServeHTTP(w http.ResponseWriter, req *http.Request) { 1417 var conn, _, err = w.(http.Hijacker).Hijack() 1418 if err != nil { 1419 log.Print("rpc hijacking ", req.RemoteAddr, ": ", err.Error()) 1420 return 1421 } 1422 io.WriteString(conn, "HTTP/1.1 200\n") 1423 io.WriteString(conn, "Content-Type: application/json-rpc\n\n") 1424 ghost.server.ServeCodec(jsonrpc.NewServerCodec(conn)) 1425 } 1426 1427 // StartRPCServer starts a local RPC server used for communication between 1428 // ghost instances. 1429 func (ghost *Ghost) StartRPCServer() { 1430 log.Println("RPC Server: started") 1431 1432 ghost.server = rpc.NewServer() 1433 ghost.server.RegisterName("rpc", &ghostRPCStub{ghost}) 1434 1435 http.Handle("/", ghost) 1436 err := http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", ghostRPCStubPort), nil) 1437 if err != nil { 1438 log.Fatalf("Unable to listen at port %d: %s\n", ghostRPCStubPort, err) 1439 } 1440 } 1441 1442 // ScanGateway scans current network gateway and add it into addrs if not 1443 // already exist. 1444 func (ghost *Ghost) ScanGateway() { 1445 if gateways, err := GetGateWayIP(); err == nil { 1446 for _, gw := range gateways { 1447 addr := fmt.Sprintf("%s:%d", gw, DefaultHTTPSPort) 1448 if !ghost.existsInAddr(addr) { 1449 ghost.addrs = append(ghost.addrs, addr) 1450 } 1451 addr = fmt.Sprintf("%s:%d", gw, DefaultHTTPPort) 1452 if !ghost.existsInAddr(addr) { 1453 ghost.addrs = append(ghost.addrs, addr) 1454 } 1455 } 1456 } 1457 } 1458 1459 // Start bootstraps and start the client. 1460 func (ghost *Ghost) Start(lanDisc bool, RPCServer bool) { 1461 log.Printf("%s started\n", ModeStr(ghost.mode)) 1462 log.Printf("MID: %s\n", ghost.mid) 1463 log.Printf("SID: %s\n", ghost.sid) 1464 1465 if lanDisc { 1466 go ghost.StartLanDiscovery() 1467 } 1468 1469 if RPCServer { 1470 go ghost.StartRPCServer() 1471 } 1472 1473 for { 1474 ghost.ScanGateway() 1475 err := ghost.Register() 1476 if err == nil { 1477 err = ghost.Listen() 1478 } 1479 if ghost.quit { 1480 break 1481 } 1482 log.Printf("%s, retrying in %ds\n", err, retryIntervalSeconds) 1483 time.Sleep(retryIntervalSeconds * time.Second) 1484 ghost.Reset() 1485 } 1486 } 1487 1488 // Returns a ghostRPCStub client object which can be used to call ghostRPCStub methods. 1489 func ghostRPCStubServer() (*rpc.Client, error) { 1490 conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", ghostRPCStubPort)) 1491 if err != nil { 1492 return nil, err 1493 } 1494 1495 io.WriteString(conn, "GET / HTTP/1.1\nHost: 127.0.0.1\n\n") 1496 _, err = http.ReadResponse(bufio.NewReader(conn), nil) 1497 if err == nil { 1498 return jsonrpc.NewClient(conn), nil 1499 } 1500 return nil, err 1501 } 1502 1503 // DownloadFile adds a file to the download queue, which would be pickup by the 1504 // ghost control channel instance and perform download. 1505 func DownloadFile(filename string) { 1506 client, err := ghostRPCStubServer() 1507 if err != nil { 1508 log.Printf("error: %s\n", err) 1509 os.Exit(1) 1510 } 1511 1512 var ttyName string 1513 var f *os.File 1514 1515 absPath, err := filepath.Abs(filename) 1516 if err != nil { 1517 goto fail 1518 } 1519 1520 _, err = os.Stat(absPath) 1521 if err != nil { 1522 goto fail 1523 } 1524 1525 f, err = os.Open(absPath) 1526 if err != nil { 1527 goto fail 1528 } 1529 f.Close() 1530 1531 ttyName, err = Ttyname(os.Stdout.Fd()) 1532 if err != nil { 1533 goto fail 1534 } 1535 1536 err = client.Call("rpc.AddToDownloadQueue", []string{ttyName, absPath}, 1537 &EmptyReply{}) 1538 if err != nil { 1539 goto fail 1540 } 1541 os.Exit(0) 1542 1543 fail: 1544 log.Println(err) 1545 os.Exit(1) 1546 } 1547 1548 // StartGhost starts the Ghost client. 1549 func StartGhost(args []string, mid string, noLanDisc bool, noRPCServer bool, 1550 tlsCertFile string, verify bool, propFile string, download string, 1551 reset bool, status bool, tlsMode int) { 1552 var addrs []string 1553 1554 if status { 1555 client, err := ghostRPCStubServer() 1556 if err != nil { 1557 log.Printf("error: %s\n", err) 1558 os.Exit(1) 1559 } 1560 1561 var reply string 1562 err = client.Call("rpc.GetStatus", &EmptyArgs{}, &reply) 1563 if err != nil { 1564 log.Printf("GetStatus: %s\n", err) 1565 os.Exit(1) 1566 } 1567 fmt.Println(reply) 1568 os.Exit(0) 1569 } 1570 1571 if reset { 1572 client, err := ghostRPCStubServer() 1573 if err != nil { 1574 log.Printf("error: %s\n", err) 1575 os.Exit(1) 1576 } 1577 1578 err = client.Call("rpc.Reconnect", &EmptyArgs{}, &EmptyReply{}) 1579 if err != nil { 1580 log.Printf("Reset: %s\n", err) 1581 os.Exit(1) 1582 } 1583 os.Exit(0) 1584 } 1585 1586 if download != "" { 1587 DownloadFile(download) 1588 } 1589 1590 if len(args) >= 1 { 1591 if strings.Index(args[0], ":") == -1 { 1592 addrs = append(addrs, 1593 fmt.Sprintf("%s:%d", args[0], DefaultHTTPSPort), 1594 fmt.Sprintf("%s:%d", args[0], DefaultHTTPPort)) 1595 } else { 1596 addrs = append(addrs, args[0]) 1597 } 1598 } 1599 addrs = append(addrs, 1600 fmt.Sprintf("127.0.0.1:%d", DefaultHTTPSPort), 1601 fmt.Sprintf("127.0.0.1:%d", DefaultHTTPPort)) 1602 1603 tlsSettings := newTLSSettings(tlsCertFile, verify) 1604 1605 if propFile != "" { 1606 var err error 1607 propFile, err = filepath.Abs(propFile) 1608 if err != nil { 1609 log.Println("propFile:", err) 1610 os.Exit(1) 1611 } 1612 } 1613 1614 g := NewGhost(addrs, tlsSettings, ModeControl, mid) 1615 g.SetPropFile(propFile).SetTLSMode(tlsMode) 1616 go g.Start(!noLanDisc, !noRPCServer) 1617 1618 ticker := time.NewTicker(time.Duration(60 * time.Second)) 1619 1620 for { 1621 select { 1622 case <-ticker.C: 1623 log.Printf("Num of Goroutines: %d\n", runtime.NumGoroutine()) 1624 } 1625 } 1626 }