go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/connection/ssh/sftp/sftp.go (about)

     1  // Copyright © 2015 Jerry Jacobs <jerry.jacobs@xor-gate.org>.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package sftp
    15  
    16  import (
    17  	"os"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/cockroachdb/errors"
    22  	"github.com/pkg/sftp"
    23  	"github.com/spf13/afero"
    24  	"go.mondoo.com/cnquery/providers/os/connection/ssh/cat"
    25  	"golang.org/x/crypto/ssh"
    26  )
    27  
    28  // Fs is a afero.Fs implementation that uses functions provided by the sftp package.
    29  //
    30  // For details in any method, check the documentation of the sftp package
    31  // (github.com/pkg/sftp).
    32  type Fs struct {
    33  	client *sftp.Client
    34  	catFs  *cat.Fs
    35  }
    36  
    37  func New(commandRunner cat.CommandRunner, client *ssh.Client) (afero.Fs, error) {
    38  	ftpClient, err := sftpClient(client)
    39  	if err != nil {
    40  		return nil, errors.Wrap(err, "could not initialize sftp backend")
    41  	}
    42  
    43  	return &Fs{
    44  		client: ftpClient,
    45  		catFs:  cat.New(commandRunner),
    46  	}, nil
    47  }
    48  
    49  func sftpClient(sshClient *ssh.Client) (*sftp.Client, error) {
    50  	c, err := sftp.NewClient(sshClient, sftp.MaxPacket(1<<15))
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	return c, nil
    55  }
    56  
    57  func (s Fs) Name() string { return "sftpfs" }
    58  
    59  func (s Fs) Create(name string) (afero.File, error) {
    60  	return FileCreate(s.client, name)
    61  }
    62  
    63  func (s Fs) Mkdir(name string, perm os.FileMode) error {
    64  	err := s.client.Mkdir(name)
    65  	if err != nil {
    66  		return err
    67  	}
    68  	return s.client.Chmod(name, perm)
    69  }
    70  
    71  func (s Fs) MkdirAll(path string, perm os.FileMode) error {
    72  	// Fast path: if we can tell whether path is a directory or file, stop with success or error.
    73  	dir, err := s.Stat(path)
    74  	if err == nil {
    75  		if dir.IsDir() {
    76  			return nil
    77  		}
    78  		return err
    79  	}
    80  
    81  	// Slow path: make sure parent exists and then call Mkdir for path.
    82  	i := len(path)
    83  	for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
    84  		i--
    85  	}
    86  
    87  	j := i
    88  	for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
    89  		j--
    90  	}
    91  
    92  	if j > 1 {
    93  		// Create parent
    94  		err = s.MkdirAll(path[0:j-1], perm)
    95  		if err != nil {
    96  			return err
    97  		}
    98  	}
    99  
   100  	// Parent now exists; invoke Mkdir and use its result.
   101  	err = s.Mkdir(path, perm)
   102  	if err != nil {
   103  		// Handle arguments like "foo/." by
   104  		// double-checking that directory doesn't exist.
   105  		dir, err1 := s.Lstat(path)
   106  		if err1 == nil && dir.IsDir() {
   107  			return nil
   108  		}
   109  		return err
   110  	}
   111  	return nil
   112  }
   113  
   114  func (s Fs) Open(path string) (afero.File, error) {
   115  	// NOTE: procfs cannot be read via scp, so we fall-back to catfs all paths there
   116  	if strings.HasPrefix(path, "/proc") {
   117  		return s.catFs.Open(path)
   118  	}
   119  
   120  	return FileOpen(s.client, path)
   121  }
   122  
   123  func (s Fs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
   124  	// sftp client does not support mode
   125  	sshfsFile, err := s.client.OpenFile(name, flag)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	return &File{fd: sshfsFile}, nil
   130  }
   131  
   132  func (s Fs) Remove(name string) error {
   133  	return s.client.Remove(name)
   134  }
   135  
   136  func (s Fs) RemoveAll(path string) error {
   137  	// TODO have a look at os.RemoveAll
   138  	// https://github.com/golang/go/blob/master/src/os/path.go#L66
   139  	return errors.New("removeall not implemented")
   140  }
   141  
   142  func (s Fs) Rename(oldname, newname string) error {
   143  	return s.client.Rename(oldname, newname)
   144  }
   145  
   146  func (s Fs) Stat(name string) (os.FileInfo, error) {
   147  	return s.client.Stat(name)
   148  }
   149  
   150  func (s Fs) Lstat(p string) (os.FileInfo, error) {
   151  	return s.client.Lstat(p)
   152  }
   153  
   154  func (s Fs) Chmod(name string, mode os.FileMode) error {
   155  	return s.client.Chmod(name, mode)
   156  }
   157  
   158  func (s Fs) Chtimes(name string, atime time.Time, mtime time.Time) error {
   159  	return s.client.Chtimes(name, atime, mtime)
   160  }
   161  
   162  func (s Fs) Chown(name string, uid, gid int) error {
   163  	return s.client.Chown(name, uid, gid)
   164  }