1
2
3
4
5 package ssh
6
7
8
9
10 import (
11 "bytes"
12 "encoding/binary"
13 "errors"
14 "fmt"
15 "io"
16 "sync"
17 )
18
19 type Signal string
20
21
22 const (
23 SIGABRT Signal = "ABRT"
24 SIGALRM Signal = "ALRM"
25 SIGFPE Signal = "FPE"
26 SIGHUP Signal = "HUP"
27 SIGILL Signal = "ILL"
28 SIGINT Signal = "INT"
29 SIGKILL Signal = "KILL"
30 SIGPIPE Signal = "PIPE"
31 SIGQUIT Signal = "QUIT"
32 SIGSEGV Signal = "SEGV"
33 SIGTERM Signal = "TERM"
34 SIGUSR1 Signal = "USR1"
35 SIGUSR2 Signal = "USR2"
36 )
37
38 var signals = map[Signal]int{
39 SIGABRT: 6,
40 SIGALRM: 14,
41 SIGFPE: 8,
42 SIGHUP: 1,
43 SIGILL: 4,
44 SIGINT: 2,
45 SIGKILL: 9,
46 SIGPIPE: 13,
47 SIGQUIT: 3,
48 SIGSEGV: 11,
49 SIGTERM: 15,
50 }
51
52 type TerminalModes map[uint8]uint32
53
54
55 const (
56 tty_OP_END = 0
57 VINTR = 1
58 VQUIT = 2
59 VERASE = 3
60 VKILL = 4
61 VEOF = 5
62 VEOL = 6
63 VEOL2 = 7
64 VSTART = 8
65 VSTOP = 9
66 VSUSP = 10
67 VDSUSP = 11
68 VREPRINT = 12
69 VWERASE = 13
70 VLNEXT = 14
71 VFLUSH = 15
72 VSWTCH = 16
73 VSTATUS = 17
74 VDISCARD = 18
75 IGNPAR = 30
76 PARMRK = 31
77 INPCK = 32
78 ISTRIP = 33
79 INLCR = 34
80 IGNCR = 35
81 ICRNL = 36
82 IUCLC = 37
83 IXON = 38
84 IXANY = 39
85 IXOFF = 40
86 IMAXBEL = 41
87 IUTF8 = 42
88 ISIG = 50
89 ICANON = 51
90 XCASE = 52
91 ECHO = 53
92 ECHOE = 54
93 ECHOK = 55
94 ECHONL = 56
95 NOFLSH = 57
96 TOSTOP = 58
97 IEXTEN = 59
98 ECHOCTL = 60
99 ECHOKE = 61
100 PENDIN = 62
101 OPOST = 70
102 OLCUC = 71
103 ONLCR = 72
104 OCRNL = 73
105 ONOCR = 74
106 ONLRET = 75
107 CS7 = 90
108 CS8 = 91
109 PARENB = 92
110 PARODD = 93
111 TTY_OP_ISPEED = 128
112 TTY_OP_OSPEED = 129
113 )
114
115
116 type Session struct {
117
118
119
120 Stdin io.Reader
121
122
123
124
125
126
127
128
129
130 Stdout io.Writer
131 Stderr io.Writer
132
133 ch Channel
134 started bool
135 copyFuncs []func() error
136 errors chan error
137
138
139 stdinpipe, stdoutpipe, stderrpipe bool
140
141
142
143
144 stdinPipeWriter io.WriteCloser
145
146 exitStatus chan error
147 }
148
149
150
151 func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
152 return s.ch.SendRequest(name, wantReply, payload)
153 }
154
155 func (s *Session) Close() error {
156 return s.ch.Close()
157 }
158
159
160 type setenvRequest struct {
161 Name string
162 Value string
163 }
164
165
166
167 func (s *Session) Setenv(name, value string) error {
168 msg := setenvRequest{
169 Name: name,
170 Value: value,
171 }
172 ok, err := s.ch.SendRequest("env", true, Marshal(&msg))
173 if err == nil && !ok {
174 err = errors.New("ssh: setenv failed")
175 }
176 return err
177 }
178
179
180 type ptyRequestMsg struct {
181 Term string
182 Columns uint32
183 Rows uint32
184 Width uint32
185 Height uint32
186 Modelist string
187 }
188
189
190 func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error {
191 var tm []byte
192 for k, v := range termmodes {
193 kv := struct {
194 Key byte
195 Val uint32
196 }{k, v}
197
198 tm = append(tm, Marshal(&kv)...)
199 }
200 tm = append(tm, tty_OP_END)
201 req := ptyRequestMsg{
202 Term: term,
203 Columns: uint32(w),
204 Rows: uint32(h),
205 Width: uint32(w * 8),
206 Height: uint32(h * 8),
207 Modelist: string(tm),
208 }
209 ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req))
210 if err == nil && !ok {
211 err = errors.New("ssh: pty-req failed")
212 }
213 return err
214 }
215
216
217 type subsystemRequestMsg struct {
218 Subsystem string
219 }
220
221
222
223 func (s *Session) RequestSubsystem(subsystem string) error {
224 msg := subsystemRequestMsg{
225 Subsystem: subsystem,
226 }
227 ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg))
228 if err == nil && !ok {
229 err = errors.New("ssh: subsystem request failed")
230 }
231 return err
232 }
233
234
235 type ptyWindowChangeMsg struct {
236 Columns uint32
237 Rows uint32
238 Width uint32
239 Height uint32
240 }
241
242
243 func (s *Session) WindowChange(h, w int) error {
244 req := ptyWindowChangeMsg{
245 Columns: uint32(w),
246 Rows: uint32(h),
247 Width: uint32(w * 8),
248 Height: uint32(h * 8),
249 }
250 _, err := s.ch.SendRequest("window-change", false, Marshal(&req))
251 return err
252 }
253
254
255 type signalMsg struct {
256 Signal string
257 }
258
259
260
261 func (s *Session) Signal(sig Signal) error {
262 msg := signalMsg{
263 Signal: string(sig),
264 }
265
266 _, err := s.ch.SendRequest("signal", false, Marshal(&msg))
267 return err
268 }
269
270
271 type execMsg struct {
272 Command string
273 }
274
275
276
277
278 func (s *Session) Start(cmd string) error {
279 if s.started {
280 return errors.New("ssh: session already started")
281 }
282 req := execMsg{
283 Command: cmd,
284 }
285
286 ok, err := s.ch.SendRequest("exec", true, Marshal(&req))
287 if err == nil && !ok {
288 err = fmt.Errorf("ssh: command %v failed", cmd)
289 }
290 if err != nil {
291 return err
292 }
293 return s.start()
294 }
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309 func (s *Session) Run(cmd string) error {
310 err := s.Start(cmd)
311 if err != nil {
312 return err
313 }
314 return s.Wait()
315 }
316
317
318 func (s *Session) Output(cmd string) ([]byte, error) {
319 if s.Stdout != nil {
320 return nil, errors.New("ssh: Stdout already set")
321 }
322 var b bytes.Buffer
323 s.Stdout = &b
324 err := s.Run(cmd)
325 return b.Bytes(), err
326 }
327
328 type singleWriter struct {
329 b bytes.Buffer
330 mu sync.Mutex
331 }
332
333 func (w *singleWriter) Write(p []byte) (int, error) {
334 w.mu.Lock()
335 defer w.mu.Unlock()
336 return w.b.Write(p)
337 }
338
339
340
341 func (s *Session) CombinedOutput(cmd string) ([]byte, error) {
342 if s.Stdout != nil {
343 return nil, errors.New("ssh: Stdout already set")
344 }
345 if s.Stderr != nil {
346 return nil, errors.New("ssh: Stderr already set")
347 }
348 var b singleWriter
349 s.Stdout = &b
350 s.Stderr = &b
351 err := s.Run(cmd)
352 return b.b.Bytes(), err
353 }
354
355
356
357 func (s *Session) Shell() error {
358 if s.started {
359 return errors.New("ssh: session already started")
360 }
361
362 ok, err := s.ch.SendRequest("shell", true, nil)
363 if err == nil && !ok {
364 return errors.New("ssh: could not start shell")
365 }
366 if err != nil {
367 return err
368 }
369 return s.start()
370 }
371
372 func (s *Session) start() error {
373 s.started = true
374
375 type F func(*Session)
376 for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
377 setupFd(s)
378 }
379
380 s.errors = make(chan error, len(s.copyFuncs))
381 for _, fn := range s.copyFuncs {
382 go func(fn func() error) {
383 s.errors <- fn()
384 }(fn)
385 }
386 return nil
387 }
388
389
390
391
392
393
394
395
396
397
398
399 func (s *Session) Wait() error {
400 if !s.started {
401 return errors.New("ssh: session not started")
402 }
403 waitErr := <-s.exitStatus
404
405 if s.stdinPipeWriter != nil {
406 s.stdinPipeWriter.Close()
407 }
408 var copyError error
409 for range s.copyFuncs {
410 if err := <-s.errors; err != nil && copyError == nil {
411 copyError = err
412 }
413 }
414 if waitErr != nil {
415 return waitErr
416 }
417 return copyError
418 }
419
420 func (s *Session) wait(reqs <-chan *Request) error {
421 wm := Waitmsg{status: -1}
422
423 for msg := range reqs {
424 switch msg.Type {
425 case "exit-status":
426 wm.status = int(binary.BigEndian.Uint32(msg.Payload))
427 case "exit-signal":
428 var sigval struct {
429 Signal string
430 CoreDumped bool
431 Error string
432 Lang string
433 }
434 if err := Unmarshal(msg.Payload, &sigval); err != nil {
435 return err
436 }
437
438
439 wm.signal = sigval.Signal
440 wm.msg = sigval.Error
441 wm.lang = sigval.Lang
442 default:
443
444
445 if msg.WantReply {
446 msg.Reply(false, nil)
447 }
448 }
449 }
450 if wm.status == 0 {
451 return nil
452 }
453 if wm.status == -1 {
454
455 if wm.signal == "" {
456
457
458
459
460 return &ExitMissingError{}
461 }
462 wm.status = 128
463 if _, ok := signals[Signal(wm.signal)]; ok {
464 wm.status += signals[Signal(wm.signal)]
465 }
466 }
467
468 return &ExitError{wm}
469 }
470
471
472
473 type ExitMissingError struct{}
474
475 func (e *ExitMissingError) Error() string {
476 return "wait: remote command exited without exit status or exit signal"
477 }
478
479 func (s *Session) stdin() {
480 if s.stdinpipe {
481 return
482 }
483 var stdin io.Reader
484 if s.Stdin == nil {
485 stdin = new(bytes.Buffer)
486 } else {
487 r, w := io.Pipe()
488 go func() {
489 _, err := io.Copy(w, s.Stdin)
490 w.CloseWithError(err)
491 }()
492 stdin, s.stdinPipeWriter = r, w
493 }
494 s.copyFuncs = append(s.copyFuncs, func() error {
495 _, err := io.Copy(s.ch, stdin)
496 if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF {
497 err = err1
498 }
499 return err
500 })
501 }
502
503 func (s *Session) stdout() {
504 if s.stdoutpipe {
505 return
506 }
507 if s.Stdout == nil {
508 s.Stdout = io.Discard
509 }
510 s.copyFuncs = append(s.copyFuncs, func() error {
511 _, err := io.Copy(s.Stdout, s.ch)
512 return err
513 })
514 }
515
516 func (s *Session) stderr() {
517 if s.stderrpipe {
518 return
519 }
520 if s.Stderr == nil {
521 s.Stderr = io.Discard
522 }
523 s.copyFuncs = append(s.copyFuncs, func() error {
524 _, err := io.Copy(s.Stderr, s.ch.Stderr())
525 return err
526 })
527 }
528
529
530 type sessionStdin struct {
531 io.Writer
532 ch Channel
533 }
534
535 func (s *sessionStdin) Close() error {
536 return s.ch.CloseWrite()
537 }
538
539
540
541 func (s *Session) StdinPipe() (io.WriteCloser, error) {
542 if s.Stdin != nil {
543 return nil, errors.New("ssh: Stdin already set")
544 }
545 if s.started {
546 return nil, errors.New("ssh: StdinPipe after process started")
547 }
548 s.stdinpipe = true
549 return &sessionStdin{s.ch, s.ch}, nil
550 }
551
552
553
554
555
556
557
558 func (s *Session) StdoutPipe() (io.Reader, error) {
559 if s.Stdout != nil {
560 return nil, errors.New("ssh: Stdout already set")
561 }
562 if s.started {
563 return nil, errors.New("ssh: StdoutPipe after process started")
564 }
565 s.stdoutpipe = true
566 return s.ch, nil
567 }
568
569
570
571
572
573
574
575 func (s *Session) StderrPipe() (io.Reader, error) {
576 if s.Stderr != nil {
577 return nil, errors.New("ssh: Stderr already set")
578 }
579 if s.started {
580 return nil, errors.New("ssh: StderrPipe after process started")
581 }
582 s.stderrpipe = true
583 return s.ch.Stderr(), nil
584 }
585
586
587 func newSession(ch Channel, reqs <-chan *Request) (*Session, error) {
588 s := &Session{
589 ch: ch,
590 }
591 s.exitStatus = make(chan error, 1)
592 go func() {
593 s.exitStatus <- s.wait(reqs)
594 }()
595
596 return s, nil
597 }
598
599
600 type ExitError struct {
601 Waitmsg
602 }
603
604 func (e *ExitError) Error() string {
605 return e.Waitmsg.String()
606 }
607
608
609
610 type Waitmsg struct {
611 status int
612 signal string
613 msg string
614 lang string
615 }
616
617
618 func (w Waitmsg) ExitStatus() int {
619 return w.status
620 }
621
622
623
624 func (w Waitmsg) Signal() string {
625 return w.signal
626 }
627
628
629 func (w Waitmsg) Msg() string {
630 return w.msg
631 }
632
633
634 func (w Waitmsg) Lang() string {
635 return w.lang
636 }
637
638 func (w Waitmsg) String() string {
639 str := fmt.Sprintf("Process exited with status %v", w.status)
640 if w.signal != "" {
641 str += fmt.Sprintf(" from signal %v", w.signal)
642 }
643 if w.msg != "" {
644 str += fmt.Sprintf(". Reason was: %v", w.msg)
645 }
646 return str
647 }
648
View as plain text