github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/sshconn.go (about)

     1  package git
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"log"
     7  	"os"
     8  	"os/user"
     9  
    10  	"golang.org/x/crypto/ssh"
    11  )
    12  
    13  // an sshConn represents a remote which uses ssh
    14  // for transport (ie. a remote that starts with ssh://)
    15  type sshConn struct {
    16  	// Add functionality shared amongst all types of remotes
    17  	*sharedRemoteConn
    18  
    19  	session *ssh.Session
    20  
    21  	stdin  io.Reader
    22  	stdout io.Writer
    23  }
    24  
    25  var _ RemoteConn = &sshConn{}
    26  
    27  func (s *sshConn) OpenConn(srv GitService) error {
    28  	host := s.uri.Hostname()
    29  	port := s.uri.Port()
    30  	if port == "" {
    31  		port = "22"
    32  	}
    33  	log.Println("Opening ssh connection to", host, " port", port)
    34  
    35  	var username string
    36  	if s.uri.User != nil {
    37  		username = s.uri.User.Username()
    38  	} else {
    39  		u, err := user.Current()
    40  		if err != nil {
    41  			return err
    42  		}
    43  		username = u.Username
    44  	}
    45  	log.Println("Using username", username)
    46  	config := &ssh.ClientConfig{
    47  		User: username,
    48  		Auth: []ssh.AuthMethod{
    49  			ssh.PublicKeysCallback(getSigners),
    50  		},
    51  		HostKeyCallback: hostKeyCallback(),
    52  	}
    53  	conn, err := ssh.Dial("tcp", host+":"+port, config)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	session, err := conn.NewSession()
    58  	if err != nil {
    59  		return err
    60  	}
    61  	session.Stderr = os.Stderr
    62  	s.stdin, session.Stdout = io.Pipe()
    63  	session.Stdin, s.stdout = io.Pipe()
    64  	// We don't check error on setenv because if it failed we'll just fall
    65  	// back on protocol v1
    66  	session.Setenv("GIT_PROTOCOL", "version=2")
    67  	s.session = session
    68  
    69  	if err := session.Start(s.service + " " + s.uri.Path); err != nil {
    70  		return err
    71  	}
    72  
    73  	v, cap, refs, err := parseRemoteInitialConnection(s.stdin, false)
    74  	if err != nil {
    75  		session.Close()
    76  		return err
    77  	}
    78  	s.packProtocolReader = &packProtocolReader{conn: s.stdin, state: PktLineMode}
    79  
    80  	s.protocolversion = v
    81  	s.capabilities = cap
    82  	s.refs = refs
    83  	return nil
    84  }
    85  
    86  func (s sshConn) Close() error {
    87  	fmt.Fprintf(s.stdout, "0000")
    88  	return s.session.Close()
    89  }
    90  
    91  func (s sshConn) GetRefs(opts LsRemoteOptions, patterns []string) ([]Ref, error) {
    92  	switch s.protocolversion {
    93  	case 1:
    94  		return getRefsV1(s.refs, opts, patterns)
    95  	case 2:
    96  		s.SetWriteMode(PktLineMode)
    97  		cmd, err := buildLsRefsCmdV2(opts, patterns)
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  		fmt.Fprintf(s.stdout, cmd)
   102  		line := make([]byte, 65536)
   103  		var vals []Ref
   104  		for {
   105  			n, err := s.Read(line)
   106  			switch err {
   107  			case flushPkt:
   108  				return vals, nil
   109  			case nil: // Nothing
   110  			default:
   111  				return nil, err
   112  			}
   113  			refstr := string(line[0:n])
   114  			ref, err := parseLsRef(refstr)
   115  			if err != nil {
   116  				return nil, err
   117  			}
   118  			vals = append(vals, ref)
   119  		}
   120  	default:
   121  		return nil, fmt.Errorf("Protocol version not supported")
   122  	}
   123  }
   124  
   125  func (s sshConn) Flush() error {
   126  	fmt.Fprintf(s.stdout, "0000")
   127  	return nil
   128  }
   129  
   130  func (s sshConn) Delim() error {
   131  	fmt.Fprintf(s.stdout, "0001")
   132  	return nil
   133  }
   134  
   135  func (s sshConn) Write(data []byte) (int, error) {
   136  	switch s.writemode {
   137  	case PktLineMode:
   138  		l, err := PktLineEncodeNoNl(data)
   139  		if err != nil {
   140  			return 0, err
   141  		}
   142  		fmt.Fprintf(s.stdout, "%s", l)
   143  		return len(data), nil
   144  	case DirectMode:
   145  		return s.stdout.Write(data)
   146  	default:
   147  		return 0, fmt.Errorf("Invalid write mode")
   148  	}
   149  }
   150  
   151  // this should be overridden for various platforms. Plan9/9front should parse
   152  // $home/lib/sshthumbs, unix should parse ~/.ssh/known_hosts, and Windows should..
   153  // do something?
   154  func hostKeyCallback() ssh.HostKeyCallback {
   155  	fmt.Fprintln(os.Stderr, "WARNING: Fingerprint for hostname not validated.")
   156  	return ssh.InsecureIgnoreHostKey()
   157  }