7
7
"io"
8
8
"bytes"
9
9
"github.com/problame/go-rwccmd"
10
+ "os/exec"
11
+ "syscall"
10
12
)
11
13
12
14
type Endpoint struct {
@@ -29,7 +31,7 @@ func (e Endpoint) CmdArgs() (cmd string, args []string, env []string) {
29
31
args = make ([]string , 0 , 2 * len (e .Options )+ 4 )
30
32
args = append (args ,
31
33
"-p" , fmt .Sprintf ("%d" , e .Port ),
32
- "-q " ,
34
+ "-T " ,
33
35
"-i" , e .IdentityFile ,
34
36
"-o" , "BatchMode=yes" ,
35
37
)
@@ -110,11 +112,66 @@ var banner_msg = mustMessage("SSHCON_HELO")
110
112
var proxy_error_msg = mustMessage ("SSHCON_PROXY_ERROR" )
111
113
var begin_msg = mustMessage ("SSHCON_BEGIN" )
112
114
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
+
113
168
// Dial connects to the remote endpoint where it expects a command executing Proxy().
114
169
// Dial performs a handshake consisting of the exchange of banner messages before returning the connection.
115
170
// If the handshake cannot be completed before dialCtx is Done(), the underlying ssh command is killed
116
171
// and the dialCtx.Err() returned.
117
172
// 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
118
175
func Dial (dialCtx context.Context , endpoint Endpoint ) (* SSHConn , error ) {
119
176
120
177
sshCmd , sshArgs , sshEnv := endpoint .CmdArgs ()
@@ -131,24 +188,24 @@ func Dial(dialCtx context.Context, endpoint Endpoint) (*SSHConn , error) {
131
188
go func () {
132
189
var buf bytes.Buffer
133
190
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" }
135
192
return
136
193
}
137
194
resp := buf .Bytes ()
138
195
switch {
139
196
case bytes .Equal (resp , banner_msg ):
140
197
break
141
198
case bytes .Equal (resp , proxy_error_msg ):
142
- confErrChan <- fmt . Errorf ( "proxy error, check remote configuration" )
199
+ confErrChan <- ProtocolError { "proxy error, check remote configuration" }
143
200
return
144
201
default :
145
- confErrChan <- fmt .Errorf ("unknown banner message: %v" , resp )
202
+ confErrChan <- ProtocolError { fmt .Sprintf ("unknown banner message: %v" , resp )}
146
203
return
147
204
}
148
205
buf .Reset ()
149
206
buf .Write (begin_msg )
150
207
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" }
152
209
return
153
210
}
154
211
close (confErrChan )
@@ -165,5 +222,5 @@ func Dial(dialCtx context.Context, endpoint Endpoint) (*SSHConn , error) {
165
222
}
166
223
}
167
224
168
- return & SSHConn {cmd }, err
225
+ return & SSHConn {cmd }, nil
169
226
}
0 commit comments