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 }