@@ -15,149 +15,154 @@ import (
15
15
"golang.org/x/crypto/ssh"
16
16
)
17
17
18
- func handleSession (newChannel ssh.NewChannel ) {
18
+ func handleSession (perms * ssh. Permissions , newChannel ssh.NewChannel ) {
19
19
connection , requests , err := newChannel .Accept ()
20
20
if err != nil {
21
- log .Debug ("Could not accept channel (%s) " , err )
21
+ log .Error ("Could not accept channel" , "error " , err )
22
22
return
23
23
}
24
24
25
- var bashf * os.File
26
-
27
- close := func () {
25
+ closeConn := func () {
28
26
err = connection .Close ()
29
27
if err != nil {
30
- log .Debug ("Could not close connection: %v" , err )
28
+ log .Error ("Could not close connection" , "error" , err )
29
+ return
31
30
}
31
+
32
+ log .Debug ("Connection closed" )
32
33
}
34
+ var once sync.Once
35
+
36
+ var cmdf * os.File
37
+ hasRequestedPty := false
38
+ var ptyPayload []byte
39
+
40
+ execCmd := func (name string , arg ... string ) error {
41
+ cmd := exec .Command (name , arg ... )
42
+ close := func () {
43
+ cmd .Process .Kill ()
44
+ err := cmd .Wait ()
45
+ if err != nil {
46
+ log .Error ("Error waiting for bash to end" , "error" , err )
47
+ }
33
48
34
- // Sessions have out-of-band requests such as "shell", "pty-req" and "env"
35
- go func () {
36
- var once sync.Once
37
- defer once .Do (close )
38
- for req := range requests {
39
- switch req .Type {
40
- case "shell" :
41
- // We only accept the default shell
42
- // (i.e. no command in the Payload)
43
- if len (req .Payload ) == 0 {
44
- req .Reply (true , nil )
45
- } else {
46
- log .Debug ("Non-empty shell payload not yet supported!" )
47
- }
48
- case "pty-req" :
49
+ tb := []byte {0 , 0 , 0 , 0 }
50
+ connection .SendRequest ("exit-status" , false , tb )
49
51
50
- bash := exec . Command ( "bash" )
52
+ once . Do ( closeConn )
51
53
52
- ptyClose := func () {
53
- bash .Process .Kill ()
54
- err := bash .Wait ()
55
- if err != nil {
56
- log .Debug ("Error waiting for bash to end: %v" , err )
57
- }
54
+ log .Debug ("Session closed" )
55
+ }
58
56
59
- tb := []byte {0 , 0 , 0 , 0 }
60
- connection .SendRequest ("exit-status" , false , tb )
57
+ var pipesWait sync.WaitGroup
58
+ pipesWait .Add (2 )
59
+ go func () {
60
+ pipesWait .Wait ()
61
+ close ()
62
+ }()
63
+
64
+ if hasRequestedPty {
65
+ log .Debug ("Creating pty..." )
66
+ cmdf , err = pty .Start (cmd )
67
+ if err != nil {
68
+ return err
69
+ }
61
70
62
- close ()
71
+ var once sync.Once
72
+ go func () {
73
+ io .Copy (connection , cmdf )
74
+ pipesWait .Done ()
75
+ }()
76
+ go func () {
77
+ io .Copy (cmdf , connection )
78
+ pipesWait .Done ()
79
+ }()
80
+
81
+ termLen := ptyPayload [3 ]
82
+ w , h := parseDims (ptyPayload [termLen + 4 :])
83
+ SetWinsize (cmdf .Fd (), w , h )
84
+ } else {
85
+ stdin , err := cmd .StdinPipe ()
86
+ if err != nil {
87
+ return err
88
+ }
63
89
64
- log .Debug ("Session closed" )
65
- }
90
+ stdout , err := cmd .StdoutPipe ()
91
+ if err != nil {
92
+ return err
93
+ }
66
94
67
- // Allocate a terminal for this channel
68
- log .Debug ("Creating pty..." )
69
- bashf , err = pty .Start (bash )
95
+ stderr , err := cmd .StderrPipe ()
96
+ if err != nil {
97
+ return err
98
+ }
99
+
100
+ // we want to wait for stdout and stderr before closing connection, but don't really mind about stdin
101
+ go func () {
102
+ _ , err := io .Copy (stdin , connection )
103
+ log .Debug ("Stdin copy ended" , "error" , err )
104
+ }()
105
+ go func () {
106
+ _ , err := io .Copy (connection , stdout )
107
+ log .Debug ("Stdout copy ended" , "error" , err )
108
+ pipesWait .Done ()
109
+ }()
110
+ go func () {
111
+ _ , err := io .Copy (connection .Stderr (), stderr )
112
+ log .Debug ("Stderr copy ended" , "error" , err )
113
+ pipesWait .Done ()
114
+ }()
115
+
116
+ err = cmd .Start ()
117
+ if err != nil {
118
+ return err
119
+ }
120
+ }
121
+
122
+ return nil
123
+ }
124
+
125
+ // Sessions have out-of-band requests such as "shell", "pty-req" and "exec"
126
+ go func () {
127
+ defer once .Do (closeConn )
128
+ for req := range requests {
129
+ switch req .Type {
130
+ case "shell" :
131
+ // TODO determine and use default shell, don't force bash
132
+ err := execCmd ("bash" )
70
133
if err != nil {
71
- log .Debug ("Could not start pty (%s)" , err )
72
- return
134
+ log .Error ("Can't create shell!" , "error" , err )
73
135
}
74
136
75
- //pipe session to bash and visa-versa
76
- go func () {
77
- io .Copy (connection , bashf )
78
- once .Do (ptyClose )
79
- }()
80
- go func () {
81
- io .Copy (bashf , connection )
82
- once .Do (ptyClose )
83
- }()
84
-
85
- termLen := req .Payload [3 ]
86
- w , h := parseDims (req .Payload [termLen + 4 :])
87
- SetWinsize (bashf .Fd (), w , h )
88
- // Responding true (OK) here will let the client
89
- // know we have a pty ready for input
90
- req .Reply (true , nil )
137
+ if req .WantReply {
138
+ req .Reply (true , nil )
139
+ }
140
+ case "pty-req" :
141
+ hasRequestedPty = true
142
+ ptyPayload = req .Payload
143
+ if req .WantReply {
144
+ req .Reply (true , nil )
145
+ }
91
146
case "window-change" :
92
- if bashf == nil {
93
- log .Debug ("No pty requested!" )
147
+ if cmdf == nil {
148
+ log .Debug ("Tried to change window size but no pty requested!" )
94
149
} else {
95
150
w , h := parseDims (req .Payload )
96
- SetWinsize (bashf .Fd (), w , h )
151
+ SetWinsize (cmdf .Fd (), w , h )
152
+ if req .WantReply {
153
+ req .Reply (true , nil )
154
+ }
97
155
}
98
156
case "exec" :
99
157
cmdStrLen := binary .BigEndian .Uint32 (req .Payload [0 :4 ])
100
158
cmdStr := string (req .Payload [4 : cmdStrLen + 4 ])
101
- cmd := exec .Command ("bash" , "-c" , cmdStr )
102
-
103
- stdin , err := cmd .StdinPipe ()
104
- if err != nil {
105
- log .Debug ("Error creating stdin pipe: %v" , err )
106
- continue
107
- }
108
-
109
- stdout , err := cmd .StdoutPipe ()
159
+ err := execCmd ("bash" , "-c" , cmdStr )
110
160
if err != nil {
111
- log .Debug ("Error creating stdout pipe: %v" , err )
112
- continue
161
+ log .Error ("Can't create shell!" , "error" , err )
113
162
}
114
163
115
- stderr , err := cmd .StderrPipe ()
116
- if err != nil {
117
- log .Debug ("Error creating stderr pipe: %v" , err )
118
- continue
119
- }
120
-
121
- var pipesWait sync.WaitGroup
122
-
123
- // we want to wait for stdout and stderr before closing connection, but don't really mind about stdin
124
- pipesWait .Add (2 )
125
- go func () {
126
- io .Copy (stdin , connection )
127
- }()
128
- go func () {
129
- io .Copy (connection , stdout )
130
- pipesWait .Done ()
131
- }()
132
- go func () {
133
- io .Copy (connection .Stderr (), stderr )
134
- pipesWait .Done ()
135
- }()
136
-
137
- err = cmd .Start ()
138
- if err != nil {
139
- log .Debug ("Error running command %s: %v" , cmdStr , err )
140
- continue
141
- }
142
-
143
- go func () {
144
- err := cmd .Wait ()
145
- if err != nil {
146
- log .Debug ("Error waiting for command %s to end: %v" , cmdStr , err )
147
- }
148
-
149
- pipesWait .Wait ()
150
-
151
- tb := []byte {0 , 0 , 0 , 0 }
152
- connection .SendRequest ("exit-status" , false , tb )
153
-
154
- once .Do (close )
155
- }()
156
-
157
- err = req .Reply (true , nil )
158
- if err != nil {
159
- log .Debug ("Error responding to request: %v" , err )
160
- continue
164
+ if req .WantReply {
165
+ req .Reply (true , nil )
161
166
}
162
167
default :
163
168
log .Debug ("Unkown session request type %s" , req .Type )
0 commit comments