github.com/vmware/govmomi@v0.51.0/toolbox/command.go (about) 1 // © Broadcom. All Rights Reserved. 2 // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 3 // SPDX-License-Identifier: Apache-2.0 4 5 package toolbox 6 7 import ( 8 "bytes" 9 "encoding/base64" 10 "encoding/binary" 11 "encoding/hex" 12 "fmt" 13 "log" 14 "os" 15 "path/filepath" 16 "runtime" 17 "strings" 18 "time" 19 20 "github.com/vmware/govmomi/toolbox/hgfs" 21 "github.com/vmware/govmomi/toolbox/process" 22 "github.com/vmware/govmomi/toolbox/vix" 23 ) 24 25 type CommandHandler func(vix.CommandRequestHeader, []byte) ([]byte, error) 26 27 type CommandServer struct { 28 Out *ChannelOut 29 30 ProcessManager *process.Manager 31 32 Authenticate func(vix.CommandRequestHeader, []byte) error 33 34 ProcessStartCommand func(*process.Manager, *vix.StartProgramRequest) (int64, error) 35 36 handlers map[uint32]CommandHandler 37 38 FileServer *hgfs.Server 39 } 40 41 func registerCommandServer(service *Service) *CommandServer { 42 server := &CommandServer{ 43 Out: service.out, 44 ProcessManager: process.NewManager(), 45 } 46 47 server.handlers = map[uint32]CommandHandler{ 48 vix.CommandGetToolsState: server.GetToolsState, 49 vix.CommandStartProgram: server.StartCommand, 50 vix.CommandTerminateProcess: server.KillProcess, 51 vix.CommandListProcessesEx: server.ListProcesses, 52 vix.CommandReadEnvVariables: server.ReadEnvironmentVariables, 53 vix.CommandCreateTemporaryFileEx: server.CreateTemporaryFile, 54 vix.CommandCreateTemporaryDirectory: server.CreateTemporaryDirectory, 55 vix.CommandDeleteGuestFileEx: server.DeleteFile, 56 vix.CommandCreateDirectoryEx: server.CreateDirectory, 57 vix.CommandDeleteGuestDirectoryEx: server.DeleteDirectory, 58 vix.CommandMoveGuestFileEx: server.MoveFile, 59 vix.CommandMoveGuestDirectory: server.MoveDirectory, 60 vix.CommandListFiles: server.ListFiles, 61 vix.CommandSetGuestFileAttributes: server.SetGuestFileAttributes, 62 vix.CommandInitiateFileTransferFromGuest: server.InitiateFileTransferFromGuest, 63 vix.CommandInitiateFileTransferToGuest: server.InitiateFileTransferToGuest, 64 vix.HgfsSendPacketCommand: server.ProcessHgfsPacket, 65 } 66 67 server.ProcessStartCommand = DefaultStartCommand 68 69 service.RegisterHandler("Vix_1_Relayed_Command", server.Dispatch) 70 71 return server 72 } 73 74 func commandResult(header vix.CommandRequestHeader, rc int, err error, response []byte) []byte { 75 // All Foundry tools commands return results that start with a foundry error 76 // and a guest-OS-specific error (e.g. errno) 77 errno := 0 78 79 if err != nil { 80 // TODO: inspect err for system error, setting errno 81 82 response = []byte(err.Error()) 83 84 log.Printf("[vix] op=%d error: %s", header.OpCode, err) 85 } 86 87 buf := bytes.NewBufferString(fmt.Sprintf("%d %d ", rc, errno)) 88 89 if header.CommonFlags&vix.CommandGuestReturnsBinary != 0 { 90 // '#' delimits end of ascii and the start of the binary data (see ToolsDaemonTcloReceiveVixCommand) 91 _ = buf.WriteByte('#') 92 } 93 94 _, _ = buf.Write(response) 95 96 if header.CommonFlags&vix.CommandGuestReturnsBinary == 0 { 97 // this is not binary data, so it should be a NULL terminated string (see ToolsDaemonTcloReceiveVixCommand) 98 _ = buf.WriteByte(0) 99 } 100 101 return buf.Bytes() 102 } 103 104 func (c *CommandServer) Dispatch(data []byte) ([]byte, error) { 105 // See ToolsDaemonTcloGetQuotedString 106 if data[0] == '"' { 107 data = data[1:] 108 } 109 110 var name string 111 112 ix := bytes.IndexByte(data, '"') 113 if ix > 0 { 114 name = string(data[:ix]) 115 data = data[ix+1:] 116 } 117 // skip the NULL 118 if data[0] == 0 { 119 data = data[1:] 120 } 121 122 if Trace { 123 fmt.Fprintf(os.Stderr, "vix dispatch %q...\n%s\n", name, hex.Dump(data)) 124 } 125 126 var header vix.CommandRequestHeader 127 buf := bytes.NewBuffer(data) 128 err := binary.Read(buf, binary.LittleEndian, &header) 129 if err != nil { 130 return nil, err 131 } 132 133 if header.Magic != vix.CommandMagicWord { 134 return commandResult(header, vix.InvalidMessageHeader, nil, nil), nil 135 } 136 137 handler, ok := c.handlers[header.OpCode] 138 if !ok { 139 return commandResult(header, vix.UnrecognizedCommandInGuest, nil, nil), nil 140 } 141 142 if header.OpCode != vix.CommandGetToolsState { 143 // Every command expect GetToolsState requires authentication 144 creds := buf.Bytes()[header.BodyLength:] 145 146 err = c.authenticate(header, creds[:header.CredentialLength]) 147 if err != nil { 148 return commandResult(header, vix.AuthenticationFail, err, nil), nil 149 } 150 } 151 152 rc := vix.OK 153 154 response, err := handler(header, buf.Bytes()) 155 if err != nil { 156 rc = vix.ErrorCode(err) 157 } 158 159 return commandResult(header, rc, err, response), nil 160 } 161 162 func (c *CommandServer) RegisterHandler(op uint32, handler CommandHandler) { 163 c.handlers[op] = handler 164 } 165 166 func (c *CommandServer) GetToolsState(_ vix.CommandRequestHeader, _ []byte) ([]byte, error) { 167 hostname, _ := os.Hostname() 168 osname := fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH) 169 170 // Note that vmtoolsd sends back 40 or so of these properties, sticking with the minimal set for now. 171 props := vix.PropertyList{ 172 vix.NewStringProperty(vix.PropertyGuestOsVersion, osname), 173 vix.NewStringProperty(vix.PropertyGuestOsVersionShort, osname), 174 vix.NewStringProperty(vix.PropertyGuestToolsProductNam, "VMware Tools (Go)"), 175 vix.NewStringProperty(vix.PropertyGuestToolsVersion, "10.0.5 build-3227872 (Compatible)"), 176 vix.NewStringProperty(vix.PropertyGuestName, hostname), 177 vix.NewInt32Property(vix.PropertyGuestToolsAPIOptions, 0x0001), // TODO: const VIX_TOOLSFEATURE_SUPPORT_GET_HANDLE_STATE 178 vix.NewInt32Property(vix.PropertyGuestOsFamily, 1), // TODO: const GUEST_OS_FAMILY_* 179 vix.NewBoolProperty(vix.PropertyGuestStartProgramEnabled, true), 180 vix.NewBoolProperty(vix.PropertyGuestTerminateProcessEnabled, true), 181 vix.NewBoolProperty(vix.PropertyGuestListProcessesEnabled, true), 182 vix.NewBoolProperty(vix.PropertyGuestReadEnvironmentVariableEnabled, true), 183 vix.NewBoolProperty(vix.PropertyGuestMakeDirectoryEnabled, true), 184 vix.NewBoolProperty(vix.PropertyGuestDeleteFileEnabled, true), 185 vix.NewBoolProperty(vix.PropertyGuestDeleteDirectoryEnabled, true), 186 vix.NewBoolProperty(vix.PropertyGuestMoveDirectoryEnabled, true), 187 vix.NewBoolProperty(vix.PropertyGuestMoveFileEnabled, true), 188 vix.NewBoolProperty(vix.PropertyGuestCreateTempFileEnabled, true), 189 vix.NewBoolProperty(vix.PropertyGuestCreateTempDirectoryEnabled, true), 190 vix.NewBoolProperty(vix.PropertyGuestListFilesEnabled, true), 191 vix.NewBoolProperty(vix.PropertyGuestChangeFileAttributesEnabled, true), 192 vix.NewBoolProperty(vix.PropertyGuestInitiateFileTransferFromGuestEnabled, true), 193 vix.NewBoolProperty(vix.PropertyGuestInitiateFileTransferToGuestEnabled, true), 194 } 195 196 src, _ := props.MarshalBinary() 197 enc := base64.StdEncoding 198 buf := make([]byte, enc.EncodedLen(len(src))) 199 enc.Encode(buf, src) 200 201 return buf, nil 202 } 203 204 func (c *CommandServer) StartCommand(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 205 r := &vix.StartProgramRequest{ 206 CommandRequestHeader: header, 207 } 208 209 err := r.UnmarshalBinary(data) 210 if err != nil { 211 return nil, err 212 } 213 214 pid, err := c.ProcessStartCommand(c.ProcessManager, r) 215 if err != nil { 216 return nil, err 217 } 218 219 return append([]byte(fmt.Sprintf("%d", pid)), 0), nil 220 } 221 222 func DefaultStartCommand(m *process.Manager, r *vix.StartProgramRequest) (int64, error) { 223 p := process.New() 224 225 switch r.ProgramPath { 226 case "http.RoundTrip": 227 p = process.NewRoundTrip() 228 default: 229 // Standard vmware-tools requires an absolute path, 230 // we'll enable IO redirection by default without an absolute path. 231 if !strings.Contains(r.ProgramPath, "/") { 232 p = p.WithIO() 233 } 234 } 235 236 return m.Start(r, p) 237 } 238 239 func (c *CommandServer) KillProcess(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 240 r := &vix.KillProcessRequest{ 241 CommandRequestHeader: header, 242 } 243 244 err := r.UnmarshalBinary(data) 245 if err != nil { 246 return nil, err 247 } 248 249 if c.ProcessManager.Kill(r.Body.Pid) { 250 return nil, err 251 } 252 253 // TODO: could kill process started outside of toolbox 254 255 return nil, vix.Error(vix.NoSuchProcess) 256 } 257 258 func (c *CommandServer) ListProcesses(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 259 r := &vix.ListProcessesRequest{ 260 CommandRequestHeader: header, 261 } 262 263 err := r.UnmarshalBinary(data) 264 if err != nil { 265 return nil, err 266 } 267 268 state := c.ProcessManager.ListProcesses(r.Pids) 269 270 return state, nil 271 } 272 273 func (c *CommandServer) ReadEnvironmentVariables(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 274 r := &vix.ReadEnvironmentVariablesRequest{ 275 CommandRequestHeader: header, 276 } 277 278 err := r.UnmarshalBinary(data) 279 if err != nil { 280 return nil, err 281 } 282 283 buf := new(bytes.Buffer) 284 285 if len(r.Names) == 0 { 286 for _, e := range os.Environ() { 287 _, _ = buf.WriteString(fmt.Sprintf("<ev>%s</ev>", process.EscapeXML.Replace(e))) 288 } 289 } else { 290 for _, key := range r.Names { 291 val := os.Getenv(key) 292 if val == "" { 293 continue 294 } 295 _, _ = buf.WriteString(fmt.Sprintf("<ev>%s=%s</ev>", process.EscapeXML.Replace(key), process.EscapeXML.Replace(val))) 296 } 297 } 298 299 return buf.Bytes(), nil 300 } 301 302 func (c *CommandServer) CreateTemporaryFile(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 303 r := &vix.CreateTempFileRequest{ 304 CommandRequestHeader: header, 305 } 306 307 err := r.UnmarshalBinary(data) 308 if err != nil { 309 return nil, err 310 } 311 312 f, err := os.CreateTemp(r.DirectoryPath, r.FilePrefix+"vmware") 313 if err != nil { 314 return nil, err 315 } 316 317 _ = f.Close() 318 319 return []byte(f.Name()), nil 320 } 321 322 func (c *CommandServer) CreateTemporaryDirectory(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 323 r := &vix.CreateTempFileRequest{ 324 CommandRequestHeader: header, 325 } 326 327 err := r.UnmarshalBinary(data) 328 if err != nil { 329 return nil, err 330 } 331 332 name, err := os.MkdirTemp(r.DirectoryPath, r.FilePrefix+"vmware") 333 if err != nil { 334 return nil, err 335 } 336 337 return []byte(name), nil 338 } 339 340 func (c *CommandServer) DeleteFile(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 341 r := &vix.FileRequest{ 342 CommandRequestHeader: header, 343 } 344 345 err := r.UnmarshalBinary(data) 346 if err != nil { 347 return nil, err 348 } 349 350 info, err := os.Stat(r.GuestPathName) 351 if err != nil { 352 return nil, err 353 } 354 355 if info.IsDir() { 356 return nil, vix.Error(vix.NotAFile) 357 } 358 359 err = os.Remove(r.GuestPathName) 360 361 return nil, err 362 } 363 364 func (c *CommandServer) DeleteDirectory(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 365 r := &vix.DirRequest{ 366 CommandRequestHeader: header, 367 } 368 369 err := r.UnmarshalBinary(data) 370 if err != nil { 371 return nil, err 372 } 373 374 info, err := os.Stat(r.GuestPathName) 375 if err != nil { 376 return nil, err 377 } 378 379 if !info.IsDir() { 380 return nil, vix.Error(vix.NotADirectory) 381 } 382 383 if r.Body.Recursive { 384 err = os.RemoveAll(r.GuestPathName) 385 } else { 386 err = os.Remove(r.GuestPathName) 387 } 388 389 return nil, err 390 } 391 392 func (c *CommandServer) CreateDirectory(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 393 r := &vix.DirRequest{ 394 CommandRequestHeader: header, 395 } 396 397 err := r.UnmarshalBinary(data) 398 if err != nil { 399 return nil, err 400 } 401 402 mkdir := os.Mkdir 403 404 if r.Body.Recursive { 405 mkdir = os.MkdirAll 406 } 407 408 err = mkdir(r.GuestPathName, 0700) 409 410 return nil, err 411 } 412 413 func (c *CommandServer) MoveDirectory(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 414 r := &vix.RenameFileRequest{ 415 CommandRequestHeader: header, 416 } 417 418 err := r.UnmarshalBinary(data) 419 if err != nil { 420 return nil, err 421 } 422 423 info, err := os.Stat(r.OldPathName) 424 if err != nil { 425 return nil, err 426 } 427 428 if !info.IsDir() { 429 return nil, vix.Error(vix.NotADirectory) 430 } 431 432 if !r.Body.Overwrite { 433 _, err = os.Stat(r.NewPathName) 434 if err == nil { 435 return nil, vix.Error(vix.FileAlreadyExists) 436 } 437 } 438 439 return nil, os.Rename(r.OldPathName, r.NewPathName) 440 } 441 442 func (c *CommandServer) MoveFile(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 443 r := &vix.RenameFileRequest{ 444 CommandRequestHeader: header, 445 } 446 447 err := r.UnmarshalBinary(data) 448 if err != nil { 449 return nil, err 450 } 451 452 info, err := os.Stat(r.OldPathName) 453 if err != nil { 454 return nil, err 455 } 456 457 if info.IsDir() { 458 return nil, vix.Error(vix.NotAFile) 459 } 460 461 if !r.Body.Overwrite { 462 _, err = os.Stat(r.NewPathName) 463 if err == nil { 464 return nil, vix.Error(vix.FileAlreadyExists) 465 } 466 } 467 468 return nil, os.Rename(r.OldPathName, r.NewPathName) 469 } 470 471 func (c *CommandServer) ListFiles(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 472 r := &vix.ListFilesRequest{ 473 CommandRequestHeader: header, 474 } 475 476 err := r.UnmarshalBinary(data) 477 if err != nil { 478 return nil, err 479 } 480 481 info, err := os.Lstat(r.GuestPathName) 482 if err != nil { 483 return nil, err 484 } 485 486 var dir string 487 var files []os.FileInfo 488 489 if info.IsDir() { 490 dir = r.GuestPathName 491 entries, err := os.ReadDir(r.GuestPathName) 492 if err != nil { 493 return nil, err 494 } 495 for _, entry := range entries { 496 file, _ := entry.Info() 497 files = append(files, file) 498 } 499 } else { 500 dir = filepath.Dir(r.GuestPathName) 501 files = append(files, info) 502 } 503 504 offset := r.Body.Offset + uint64(r.Body.Index) 505 total := uint64(len(files)) - offset 506 if int(offset) < len(files) { 507 files = files[offset:] 508 } else { 509 total = 0 // offset is not valid (open-vm-tools behaves the same in this case) 510 } 511 512 var remaining uint64 513 514 if r.Body.MaxResults > 0 && total > uint64(r.Body.MaxResults) { 515 remaining = total - uint64(r.Body.MaxResults) 516 files = files[:r.Body.MaxResults] 517 } 518 519 buf := new(bytes.Buffer) 520 buf.WriteString(fmt.Sprintf("<rem>%d</rem>", remaining)) 521 522 for _, info = range files { 523 buf.WriteString(fileExtendedInfoFormat(dir, info)) 524 } 525 526 return buf.Bytes(), nil 527 } 528 529 func chtimes(r *vix.SetGuestFileAttributesRequest) error { 530 var mtime, atime *time.Time 531 532 if r.IsSet(vix.FileAttributeSetModifyDate) { 533 t := time.Unix(r.Body.ModificationTime, 0) 534 mtime = &t 535 } 536 537 if r.IsSet(vix.FileAttributeSetAccessDate) { 538 t := time.Unix(r.Body.AccessTime, 0) 539 atime = &t 540 } 541 542 if mtime == nil && atime == nil { 543 return nil 544 } 545 546 info, err := os.Stat(r.GuestPathName) 547 if err != nil { 548 return err 549 } 550 551 if mtime == nil { 552 t := info.ModTime() 553 mtime = &t 554 } 555 556 if atime == nil { 557 t := info.ModTime() 558 atime = &t 559 } 560 561 return os.Chtimes(r.GuestPathName, *atime, *mtime) 562 } 563 564 func chown(r *vix.SetGuestFileAttributesRequest) error { 565 uid := -1 566 gid := -1 567 568 if r.IsSet(vix.FileAttributeSetUnixOwnerid) { 569 uid = int(r.Body.OwnerID) 570 } 571 572 if r.IsSet(vix.FileAttributeSetUnixGroupid) { 573 gid = int(r.Body.GroupID) 574 } 575 576 if uid == -1 && gid == -1 { 577 return nil 578 } 579 580 return os.Chown(r.GuestPathName, uid, gid) 581 } 582 583 func chmod(r *vix.SetGuestFileAttributesRequest) error { 584 if r.IsSet(vix.FileAttributeSetUnixPermissions) { 585 return os.Chmod(r.GuestPathName, os.FileMode(r.Body.Permissions).Perm()) 586 } 587 588 return nil 589 } 590 591 func (c *CommandServer) SetGuestFileAttributes(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 592 r := &vix.SetGuestFileAttributesRequest{ 593 CommandRequestHeader: header, 594 } 595 596 err := r.UnmarshalBinary(data) 597 if err != nil { 598 return nil, err 599 } 600 601 for _, set := range []func(*vix.SetGuestFileAttributesRequest) error{chtimes, chown, chmod} { 602 err = set(r) 603 if err != nil { 604 return nil, err 605 } 606 } 607 608 return nil, nil 609 } 610 611 func (c *CommandServer) InitiateFileTransferFromGuest(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 612 r := &vix.ListFilesRequest{ 613 CommandRequestHeader: header, 614 } 615 616 err := r.UnmarshalBinary(data) 617 if err != nil { 618 return nil, err 619 } 620 621 info, err := c.FileServer.Stat(r.GuestPathName) 622 if err != nil { 623 return nil, err 624 } 625 626 if info.Mode()&os.ModeSymlink == os.ModeSymlink { 627 return nil, vix.Error(vix.InvalidArg) 628 } 629 630 if info.IsDir() { 631 return nil, vix.Error(vix.NotAFile) 632 } 633 634 return []byte(fileExtendedInfoFormat("", info)), nil 635 } 636 637 func (c *CommandServer) InitiateFileTransferToGuest(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 638 r := &vix.InitiateFileTransferToGuestRequest{ 639 CommandRequestHeader: header, 640 } 641 642 err := r.UnmarshalBinary(data) 643 if err != nil { 644 return nil, err 645 } 646 647 info, err := c.FileServer.Stat(r.GuestPathName) 648 if err == nil { 649 if info.Mode()&os.ModeSymlink == os.ModeSymlink { 650 return nil, vix.Error(vix.InvalidArg) 651 } 652 653 if info.IsDir() { 654 return nil, vix.Error(vix.NotAFile) 655 } 656 657 if !r.Body.Overwrite { 658 return nil, vix.Error(vix.FileAlreadyExists) 659 } 660 } else if !os.IsNotExist(err) { 661 return nil, err 662 } 663 664 return nil, nil 665 } 666 667 func (c *CommandServer) ProcessHgfsPacket(header vix.CommandRequestHeader, data []byte) ([]byte, error) { 668 r := &vix.CommandHgfsSendPacket{ 669 CommandRequestHeader: header, 670 } 671 672 err := r.UnmarshalBinary(data) 673 if err != nil { 674 return nil, err 675 } 676 677 return c.FileServer.Dispatch(r.Packet) 678 } 679 680 func (c *CommandServer) authenticate(r vix.CommandRequestHeader, data []byte) error { 681 if c.Authenticate != nil { 682 return c.Authenticate(r, data) 683 } 684 685 switch r.UserCredentialType { 686 case vix.UserCredentialTypeNamePassword: 687 var c vix.UserCredentialNamePassword 688 689 if err := c.UnmarshalBinary(data); err != nil { 690 return err 691 } 692 693 if Trace { 694 fmt.Fprintf(traceLog, "ignoring credentials: %q:%q\n", c.Name, c.Password) 695 } 696 697 return nil 698 default: 699 return fmt.Errorf("unsupported UserCredentialType=%d", r.UserCredentialType) 700 } 701 }