Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b4544ca

Browse files
committed
ssh: implement protocol handling for pure SSH-based protocol
With the pure SSH-based protocol, we have a couple different types of messages that can be sent and received. Let's implement generic helpers for these types so we can use them later on when we implement the actual protocol.
1 parent 0981842 commit b4544ca

1 file changed

Lines changed: 252 additions & 0 deletions

File tree

ssh/protocol.go

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
package ssh
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"strconv"
7+
"strings"
8+
"sync"
9+
10+
"github.com/git-lfs/git-lfs/errors"
11+
"github.com/git-lfs/git-lfs/subprocess"
12+
)
13+
14+
type PktlineConnection struct {
15+
mu sync.Mutex
16+
cmd *subprocess.Cmd
17+
pl Pktline
18+
}
19+
20+
func (conn *PktlineConnection) Lock() {
21+
conn.mu.Lock()
22+
}
23+
24+
func (conn *PktlineConnection) Unlock() {
25+
conn.mu.Unlock()
26+
}
27+
28+
func (conn *PktlineConnection) Start() error {
29+
conn.Lock()
30+
defer conn.Unlock()
31+
return conn.negotiateVersion()
32+
}
33+
34+
func (conn *PktlineConnection) End() error {
35+
conn.Lock()
36+
defer conn.Unlock()
37+
err := conn.SendMessage("quit", nil)
38+
if err != nil {
39+
return err
40+
}
41+
_, err = conn.ReadStatus()
42+
conn.cmd.Wait()
43+
return err
44+
}
45+
46+
func (conn *PktlineConnection) negotiateVersion() error {
47+
pkts, err := conn.pl.ReadPacketList()
48+
if err != nil {
49+
return errors.NewProtocolError("Unable to negotiate version with remote side (unable to read capabilities)", err)
50+
}
51+
ok := false
52+
for _, line := range pkts {
53+
if line == "version=1" {
54+
ok = true
55+
}
56+
}
57+
if !ok {
58+
return errors.NewProtocolError("Unable to negotiate version with remote side (missing version=1)", nil)
59+
}
60+
err = conn.SendMessage("version 1", nil)
61+
if err != nil {
62+
return errors.NewProtocolError("Unable to negotiate version with remote side (unable to send version)", err)
63+
}
64+
status, args, _, err := conn.ReadStatusWithLines()
65+
if err != nil {
66+
return errors.NewProtocolError("Unable to negotiate version with remote side (unable to read status)", err)
67+
}
68+
if status != 200 {
69+
text := "no error provided"
70+
if len(args) > 0 {
71+
text = fmt.Sprintf("server said: %q", args[0])
72+
}
73+
return errors.NewProtocolError(fmt.Sprintf("Unable to negotiate version with remote side (unexpected status %d; %s)", status, text), nil)
74+
}
75+
return nil
76+
}
77+
78+
func (conn *PktlineConnection) SendMessage(command string, args []string) error {
79+
err := conn.pl.WritePacketText(command)
80+
if err != nil {
81+
return err
82+
}
83+
for _, arg := range args {
84+
err = conn.pl.WritePacketText(arg)
85+
if err != nil {
86+
return err
87+
}
88+
}
89+
return conn.pl.WriteFlush()
90+
}
91+
92+
func (conn *PktlineConnection) SendMessageWithLines(command string, args []string, lines []string) error {
93+
err := conn.pl.WritePacketText(command)
94+
if err != nil {
95+
return err
96+
}
97+
for _, arg := range args {
98+
err = conn.pl.WritePacketText(arg)
99+
if err != nil {
100+
return err
101+
}
102+
}
103+
err = conn.pl.WriteDelim()
104+
if err != nil {
105+
return err
106+
}
107+
for _, line := range lines {
108+
err = conn.pl.WritePacketText(line)
109+
if err != nil {
110+
return err
111+
}
112+
}
113+
return conn.pl.WriteFlush()
114+
}
115+
116+
func (conn *PktlineConnection) SendMessageWithData(command string, args []string, data io.Reader) error {
117+
err := conn.pl.WritePacketText(command)
118+
if err != nil {
119+
return err
120+
}
121+
for _, arg := range args {
122+
err = conn.pl.WritePacketText(arg)
123+
if err != nil {
124+
return err
125+
}
126+
}
127+
err = conn.pl.WriteDelim()
128+
if err != nil {
129+
return err
130+
}
131+
buf := make([]byte, 32768)
132+
for {
133+
n, err := data.Read(buf)
134+
if n > 0 {
135+
err := conn.pl.WritePacket(buf[0:n])
136+
if err != nil {
137+
return err
138+
}
139+
}
140+
if err != nil {
141+
break
142+
}
143+
}
144+
return conn.pl.WriteFlush()
145+
}
146+
147+
func (conn *PktlineConnection) ReadStatus() (int, error) {
148+
status := 0
149+
seenStatus := false
150+
for {
151+
s, pktLen, err := conn.pl.ReadPacketTextWithLength()
152+
if err != nil {
153+
return 0, errors.NewProtocolError("error reading packet", err)
154+
}
155+
switch {
156+
case pktLen == 0:
157+
if !seenStatus {
158+
return 0, errors.NewProtocolError("no status seen", nil)
159+
}
160+
return status, nil
161+
case !seenStatus:
162+
ok := false
163+
if strings.HasPrefix(s, "status ") {
164+
status, err = strconv.Atoi(s[7:])
165+
ok = err == nil
166+
}
167+
if !ok {
168+
return 0, errors.NewProtocolError(fmt.Sprintf("expected status line, got %q", s), err)
169+
}
170+
seenStatus = true
171+
default:
172+
return 0, errors.NewProtocolError(fmt.Sprintf("unexpected data, got %q", s), err)
173+
}
174+
}
175+
}
176+
177+
// ReadStatusWithData reads a status, arguments, and any binary data. Note that
178+
// the reader must be fully exhausted before invoking any other read methods.
179+
func (conn *PktlineConnection) ReadStatusWithData() (int, []string, io.Reader, error) {
180+
args := make([]string, 0, 100)
181+
status := 0
182+
seenStatus := false
183+
for {
184+
s, pktLen, err := conn.pl.ReadPacketTextWithLength()
185+
if err != nil {
186+
return 0, nil, nil, errors.NewProtocolError("error reading packet", err)
187+
}
188+
if pktLen == 0 {
189+
if !seenStatus {
190+
return 0, nil, nil, errors.NewProtocolError("no status seen", nil)
191+
}
192+
return 0, nil, nil, errors.NewProtocolError("unexpected flush packet", nil)
193+
} else if !seenStatus {
194+
ok := false
195+
if strings.HasPrefix(s, "status ") {
196+
status, err = strconv.Atoi(s[7:])
197+
ok = err == nil
198+
}
199+
if !ok {
200+
return 0, nil, nil, errors.NewProtocolError(fmt.Sprintf("expected status line, got %q", s), err)
201+
}
202+
seenStatus = true
203+
} else if pktLen == 1 {
204+
break
205+
} else {
206+
args = append(args, s)
207+
}
208+
}
209+
210+
return status, args, pktlineReader(conn.pl), nil
211+
}
212+
213+
// ReadStatusWithLines reads a status, arguments, and a set of text lines.
214+
func (conn *PktlineConnection) ReadStatusWithLines() (int, []string, []string, error) {
215+
args := make([]string, 0, 100)
216+
lines := make([]string, 0, 100)
217+
status := 0
218+
seenDelim := false
219+
seenStatus := false
220+
for {
221+
s, pktLen, err := conn.pl.ReadPacketTextWithLength()
222+
if err != nil {
223+
return 0, nil, nil, errors.NewProtocolError("error reading packet", err)
224+
}
225+
switch {
226+
case pktLen == 0:
227+
if !seenStatus {
228+
return 0, nil, nil, errors.NewProtocolError("no status seen", nil)
229+
}
230+
return status, args, lines, nil
231+
case seenDelim:
232+
lines = append(lines, s)
233+
case !seenStatus:
234+
ok := false
235+
if strings.HasPrefix(s, "status ") {
236+
status, err = strconv.Atoi(s[7:])
237+
ok = err == nil
238+
}
239+
if !ok {
240+
return 0, nil, nil, errors.NewProtocolError(fmt.Sprintf("expected status line, got %q", s), err)
241+
}
242+
seenStatus = true
243+
case pktLen == 1:
244+
if seenDelim {
245+
return 0, nil, nil, errors.NewProtocolError("unexpected delimiter packet", nil)
246+
}
247+
seenDelim = true
248+
default:
249+
args = append(args, s)
250+
}
251+
}
252+
}

0 commit comments

Comments
 (0)