Skip to content

Commit 53a2e44

Browse files
committed
proper error reporting on ssh errors
1 parent 984ce91 commit 53a2e44

File tree

2 files changed

+64
-7
lines changed

2 files changed

+64
-7
lines changed

Diff for: Gopkg.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: dial.go

+63-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"io"
88
"bytes"
99
"github.com/problame/go-rwccmd"
10+
"os/exec"
11+
"syscall"
1012
)
1113

1214
type Endpoint struct {
@@ -29,7 +31,7 @@ func (e Endpoint) CmdArgs() (cmd string, args []string, env []string) {
2931
args = make([]string, 0, 2*len(e.Options)+4)
3032
args = append(args,
3133
"-p", fmt.Sprintf("%d", e.Port),
32-
"-q",
34+
"-T",
3335
"-i", e.IdentityFile,
3436
"-o", "BatchMode=yes",
3537
)
@@ -110,11 +112,66 @@ var banner_msg = mustMessage("SSHCON_HELO")
110112
var proxy_error_msg = mustMessage("SSHCON_PROXY_ERROR")
111113
var begin_msg = mustMessage("SSHCON_BEGIN")
112114

115+
type SSHError struct {
116+
RWCError error
117+
WhileActivity string
118+
}
119+
120+
// Error() will try to present a one-line error message unless ssh stderr output is longer than one line
121+
func (e *SSHError) Error() string {
122+
123+
if e.RWCError == io.EOF {
124+
// rwccmd returns io.EOF on exit status 0, but we do not expect ssh to do that
125+
return fmt.Sprintf("ssh exited unexpectedly with exit status 0")
126+
}
127+
128+
exitErr, ok := e.RWCError.(*exec.ExitError)
129+
if !ok {
130+
return fmt.Sprintf("ssh: %s", e.RWCError)
131+
}
132+
133+
ws := exitErr.ProcessState.Sys().(syscall.WaitStatus)
134+
var wsmsg string
135+
if ws.Exited() {
136+
wsmsg = fmt.Sprintf("(exit status %d)", ws.ExitStatus())
137+
} else {
138+
wsmsg = fmt.Sprintf("(%s)", ws.Signal())
139+
}
140+
141+
haveSSHMessage := len(exitErr.Stderr) > 0
142+
sshOnelineStderr := false
143+
if i := bytes.Index(exitErr.Stderr, []byte("\n")); i == len(exitErr.Stderr)-1 {
144+
sshOnelineStderr = true
145+
}
146+
stderr := bytes.TrimSpace(exitErr.Stderr)
147+
148+
if haveSSHMessage {
149+
if sshOnelineStderr {
150+
return fmt.Sprintf("ssh: '%s' %s", stderr, wsmsg) // FIXME proper single-quoting
151+
} else {
152+
return fmt.Sprintf("ssh %s\n%s", wsmsg, stderr)
153+
}
154+
}
155+
156+
return fmt.Sprintf("ssh terminated without stderr output %s", wsmsg)
157+
158+
}
159+
160+
type ProtocolError struct {
161+
What string
162+
}
163+
164+
func (e ProtocolError) Error() string {
165+
return e.What
166+
}
167+
113168
// Dial connects to the remote endpoint where it expects a command executing Proxy().
114169
// Dial performs a handshake consisting of the exchange of banner messages before returning the connection.
115170
// If the handshake cannot be completed before dialCtx is Done(), the underlying ssh command is killed
116171
// and the dialCtx.Err() returned.
117172
// If the handshake completes, dialCtx's deadline does not affect the returned connection.
173+
//
174+
// Errors returned are either dialCtx.Err(), or intances of ProtocolError or *SSHError
118175
func Dial(dialCtx context.Context, endpoint Endpoint) (*SSHConn , error) {
119176

120177
sshCmd, sshArgs, sshEnv := endpoint.CmdArgs()
@@ -131,24 +188,24 @@ func Dial(dialCtx context.Context, endpoint Endpoint) (*SSHConn , error) {
131188
go func() {
132189
var buf bytes.Buffer
133190
if _, err := io.CopyN(&buf, cmd, int64(len(banner_msg))); err != nil {
134-
confErrChan <- fmt.Errorf("error reading banner: %s", err)
191+
confErrChan <- &SSHError{err, "read banner"}
135192
return
136193
}
137194
resp := buf.Bytes()
138195
switch {
139196
case bytes.Equal(resp, banner_msg):
140197
break
141198
case bytes.Equal(resp, proxy_error_msg):
142-
confErrChan <- fmt.Errorf("proxy error, check remote configuration")
199+
confErrChan <- ProtocolError{"proxy error, check remote configuration"}
143200
return
144201
default:
145-
confErrChan <- fmt.Errorf("unknown banner message: %v", resp)
202+
confErrChan <- ProtocolError{fmt.Sprintf("unknown banner message: %v", resp)}
146203
return
147204
}
148205
buf.Reset()
149206
buf.Write(begin_msg)
150207
if _, err := io.Copy(cmd, &buf); err != nil {
151-
confErrChan <- fmt.Errorf("error sending begin message: %s", err)
208+
confErrChan <- &SSHError{err, "send begin message"}
152209
return
153210
}
154211
close(confErrChan)
@@ -165,5 +222,5 @@ func Dial(dialCtx context.Context, endpoint Endpoint) (*SSHConn , error) {
165222
}
166223
}
167224

168-
return &SSHConn{cmd}, err
225+
return &SSHConn{cmd}, nil
169226
}

0 commit comments

Comments
 (0)