1 #include "SSH.h"
2
3 namespace Upp {
4
5 #define LLOG(x) do { if(SSH::sTrace) RLOG(SSH::GetName(ssh->otype, ssh->oid) << x); } while(false)
6 #define LDUMPHEX(x) do { if(SSH::sTraceVerbose) RDUMPHEX(x); } while(false)
7
Run(int mode_,const String & terminal,Size pagesize,const String & tmodes)8 bool SshShell::Run(int mode_, const String& terminal, Size pagesize, const String& tmodes)
9 {
10 mode = mode_;
11 psize = pagesize;
12
13 if(RequestTerminal(terminal, psize, tmodes) && X11Init() && RequestShell() && ConsoleInit())
14 ProcessEvents(queue);
15 return Shut(IsError() ? GetErrorDesc() : Null);
16 }
17
ReadWrite(String & in,const void * out,int out_len)18 void SshShell::ReadWrite(String& in, const void* out, int out_len)
19 {
20 switch(mode) {
21 case GENERIC: {
22 if(out_len > 0)
23 WhenOutput(out, out_len);
24 #ifdef PLATFORM_POSIX
25 if(xenabled)
26 X11Loop();
27 #endif
28 WhenInput();
29 break;
30 }
31 case CONSOLE: {
32 if(out_len > 0)
33 ConsoleWrite(out, out_len);
34 #ifdef PLATFORM_POSIX
35 if(xenabled)
36 X11Loop();
37 ConsoleRead();
38 #if defined(PLATFORM_MACOS)
39 // We are using sigtimedwait on POSIX-compliant systems.
40 // Unfortunately MacOS didn't implement it. This is a simple workaround for MacOS.
41 // It relies on ioctl, which is implemented on MacOS.
42 Size sz = GetConsolePageSize();
43 resized = !IsNull(sz) && sz != psize;
44 if(resized)
45 LLOG("Window size changed.");
46 #else
47 // We need to catch the WINCH signal. To this end we'll use a POSIX compliant kernel
48 // function: sigtimedwait. To speed up, we'll simply poll for the monitored event.
49 sigset_t set;
50 sigemptyset(&set);
51 sigaddset(&set, SIGWINCH);
52 sigprocmask(SIG_BLOCK, &set, nullptr);
53
54 struct timespec timeout;
55 Zero(timeout); // Instead of waiting, we simply poll.
56
57 auto rc = sigtimedwait(&set, nullptr, &timeout);
58 if(rc < 0 && errno != EAGAIN)
59 SetError(-1, "sigtimedwait() failed.");
60 if(rc > 0)
61 LLOG("SIGWINCH received.");
62 resized = rc > 0;
63 #endif
64 #elif PLATFORM_WIN32
65 // This part is a little bit tricky. We need to handle Windows console events here.
66 // But we cannot simply ignore the events we don't look for. We need to actively
67 // remove them from the event queue. Otherwise they'll cause erratic behaviour, and
68 // a lot of head ache. Thus to filter out these unwanted events, or the event's that
69 // we don't want to get in our way, we'll first peek at the console event queue to
70 // see if they met our criteria and remove them one by one as we encounter, using
71 // the ReadConsoleInput method.
72
73 auto rc = WaitForSingleObject(stdinput, ssh->waitstep);
74 switch(rc) {
75 case WAIT_OBJECT_0:
76 break;
77 case WAIT_TIMEOUT:
78 case WAIT_ABANDONED:
79 return;
80 default:
81 SetError(-1, "WaitForSingleObject() failed.");
82 }
83
84 DWORD n = 0;
85 INPUT_RECORD ir[1];
86
87 if(!PeekConsoleInput(stdinput, ir, 1, &n))
88 SetError(-1, "Unable to peek console input events.");
89 if(n) {
90 switch(ir[0].EventType) {
91 case KEY_EVENT:
92 // Ignore key-ups.
93 if(!ir[0].Event.KeyEvent.bKeyDown)
94 break;
95 ConsoleRead();
96 return;
97 case WINDOW_BUFFER_SIZE_EVENT:
98 LLOG("WINDOW_BUFFER_SIZE_EVENT received.");
99 resized = true;
100 break;
101 case MENU_EVENT:
102 case MOUSE_EVENT:
103 case FOCUS_EVENT:
104 break;
105 default:
106 SetError(-1, "Unknown console event type encountered.");
107 }
108 if(!ReadConsoleInput(stdinput, ir, 1, &n))
109 SetError(-1, "Unable to filter console input events.");
110 }
111 #endif
112 break;
113 }
114 default:
115 NEVER();
116 }
117 if(resized)
118 Resize();
119 }
120
Resize()121 void SshShell::Resize()
122 {
123 if(mode == CONSOLE)
124 PageSize(GetConsolePageSize());
125
126 int n = 0;
127 do {
128 n = SetPtySz(psize);
129 if(n < 0)
130 Wait();
131 }
132 while(!IsTimeout() && !IsEof() && n < 0);
133 resized = false;
134 }
135
ConsoleInit()136 bool SshShell::ConsoleInit()
137 {
138 if(mode != CONSOLE)
139 return true;
140
141 return Ssh::Run([=]() mutable {
142 #ifdef PLATFORM_WIN32
143 stdinput = GetStdHandle(STD_INPUT_HANDLE);
144 if(!stdinput)
145 SetError(-1, "Unable to obtain a handle for stdin.");
146 stdoutput = GetStdHandle(STD_OUTPUT_HANDLE);
147 if(!stdoutput)
148 SetError(-1, "Unable to obtain a handle for stdout.");
149 #endif
150 ConsoleRawMode();
151 return true;
152 });
153 }
154
155 #ifdef PLATFORM_POSIX
ConsoleRead()156 void SshShell::ConsoleRead()
157 {
158 if(!EventWait(STDIN_FILENO, WAIT_READ, 0))
159 return;
160 Buffer<char> buffer(ssh->chunk_size);
161 auto n = read(STDIN_FILENO, buffer, ssh->chunk_size);
162 if(n > 0)
163 Send(String(buffer, n));
164 else
165 if(n == -1 && errno != EAGAIN)
166 SetError(-1, "Couldn't read input from console.");
167 }
168
ConsoleWrite(const void * buffer,int len)169 void SshShell::ConsoleWrite(const void* buffer, int len)
170 {
171 if(!EventWait(STDOUT_FILENO, WAIT_WRITE, 0))
172 return;
173 auto n = write(STDOUT_FILENO, buffer, len);
174 if(n == -1 && errno != EAGAIN)
175 SetError(-1, "Couldn't write output to console.");
176 }
177
ConsoleRawMode(bool b)178 void SshShell::ConsoleRawMode(bool b)
179 {
180 if(!channel || mode != CONSOLE)
181 return;
182
183 if(b) {
184 termios nflags;
185 Zero(nflags);
186 Zero(tflags);
187 tcgetattr(STDIN_FILENO, &nflags);
188 tflags = nflags;
189 cfmakeraw(&nflags);
190 tcsetattr(STDIN_FILENO, TCSANOW, &nflags);
191 }
192 else
193 if(rawmode)
194 tcsetattr(STDIN_FILENO, TCSANOW, &tflags);
195 rawmode = b;
196 }
197
GetConsolePageSize()198 Size SshShell::GetConsolePageSize()
199 {
200 winsize wsz;
201 Zero(wsz);
202 if(ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz) == 0)
203 return Size(wsz.ws_col, wsz.ws_row);
204 LLOG("Warning: ioctl() failed. Couldn't read local terminal page size.");
205 return Null;
206 }
207
208 #elif PLATFORM_WIN32
209
ConsoleRead()210 void SshShell::ConsoleRead()
211 {
212 DWORD n = 0;
213 const int RBUFSIZE = 1024 * 16;
214 Buffer<char> buffer(RBUFSIZE);
215 if(!ReadConsole(stdinput, buffer, RBUFSIZE, &n, nullptr))
216 SetError(-1, "Couldn't read input from console.");
217 if(n > 0)
218 Send(String(buffer, n));
219 }
220
ConsoleWrite(const void * buffer,int len)221 void SshShell::ConsoleWrite(const void* buffer, int len)
222 {
223 DWORD n = 0;
224 if(!WriteConsole(stdoutput, buffer, len, &n, nullptr))
225 SetError(-1, "Couldn't Write output to console.");
226 }
227
ConsoleRawMode(bool b)228 void SshShell::ConsoleRawMode(bool b)
229 {
230 if(!channel || mode != CONSOLE)
231 return;
232
233 if(b) {
234 GetConsoleMode(stdinput, &tflags);
235 DWORD nflags = tflags;
236 nflags &= ~ENABLE_LINE_INPUT;
237 nflags &= ~ENABLE_ECHO_INPUT;
238 nflags |= ENABLE_WINDOW_INPUT;
239 SetConsoleMode(stdinput, nflags);
240 }
241 else
242 if(rawmode)
243 SetConsoleMode(stdinput, tflags);
244 rawmode = b;
245 }
246
GetConsolePageSize()247 Size SshShell::GetConsolePageSize()
248 {
249 CONSOLE_SCREEN_BUFFER_INFO cinf;
250 Zero(cinf);
251 if(GetConsoleScreenBufferInfo((HANDLE) _get_osfhandle(1), &cinf))
252 return Size(cinf.dwSize.X, cinf.dwSize.Y);
253 LLOG("Warning: Couldn't read local terminal page size.");
254 return Null;
255 }
256
257 #endif
258
X11Init()259 bool SshShell::X11Init()
260 {
261 if(!xenabled)
262 return true;
263
264 return Ssh::Run([=]() mutable {
265 #ifdef PLATFORM_POSIX
266 int rc = libssh2_channel_x11_req(*channel, xscreen);
267 if(!WouldBlock(rc) && rc < 0)
268 SetError(rc);
269 if(!rc)
270 LLOG("X11 tunnel succesfully initialized.");
271 return !rc;
272 #elif PLATFORM_WIN32
273 SetError(-1, "X11 tunneling is not (yet) supported on Windows platform");
274 return false;
275 #endif
276 });
277 }
278
X11Loop()279 void SshShell::X11Loop()
280 {
281 #ifdef PLATFORM_POSIX
282 if(xrequests.IsEmpty())
283 return;
284
285 for(int i = 0; i < xrequests.GetCount(); i++) {
286 SshX11Handle xhandle = xrequests[i].a;
287 SOCKET sock = xrequests[i].b;
288
289 if(EventWait(sock, WAIT_WRITE, 0)) {
290 int rc = libssh2_channel_read(xhandle, xbuffer, xbuflen);
291 if(!WouldBlock(rc) && rc < 0)
292 SetError(-1, "[X11]: Read failed.");
293 if(rc > 0)
294 write(sock, xbuffer, rc);
295 }
296 if(EventWait(sock, WAIT_READ, 0)) {
297 int rc = read(sock, xbuffer, xbuflen);
298 if(rc > 0)
299 libssh2_channel_write(xhandle, (const char*) xbuffer, rc);
300 }
301 if(libssh2_channel_eof(xhandle) == 1) {
302 LLOG("[X11] EOF received.");
303 close(sock);
304 xrequests.Remove(i);
305 i = 0;
306 }
307 }
308 #endif
309 }
310
ForwardX11(const String & host,int display,int screen,int bufsize)311 SshShell& SshShell::ForwardX11(const String& host, int display, int screen, int bufsize)
312 {
313 if(!xenabled) {
314 xenabled = true;
315 #ifdef PLATFORM_POSIX
316 xhost = host;
317 xdisplay = display;
318 xscreen = screen;
319 xbuflen = clamp(bufsize, ssh->chunk_size, INT_MAX);
320 xbuffer.Alloc(xbuflen);
321 #endif
322 }
323 return *this;
324 }
325
AcceptX11(SshX11Handle xhandle)326 bool SshShell::AcceptX11(SshX11Handle xhandle)
327 {
328 #ifdef PLATFORM_POSIX
329 if(xhandle && xenabled) {
330 auto sock = socket(AF_UNIX, SOCK_STREAM, 0);
331 if(sock < 0) {
332 LLOG("Couldn't create UNIX socket.");
333 return false;
334 }
335 auto path = Format("%s/.X11-unix/X%d", GetTempPath(), xdisplay);
336
337 struct sockaddr_un addr;
338 Zero(addr);
339 addr.sun_family = AF_UNIX;
340 memcpy(addr.sun_path, ~path, path.GetLength());
341
342 if(connect(sock, (struct sockaddr*) &addr, sizeof(addr)) == -1) {
343 LLOG("Couldn't connect to " << path);
344 close(sock);
345 return false;
346 }
347
348 LLOG("X11 connection accepted.");
349
350 auto& xr = xrequests.Add();
351 xr.a = xhandle;
352 xr.b = sock;
353 return true;
354 }
355 #endif
356 return false;
357 }
358
359
SshShell(SshSession & session)360 SshShell::SshShell(SshSession& session)
361 : SshChannel(session)
362 , mode(GENERIC)
363 , rawmode(false)
364 , resized(false)
365 , xenabled(false)
366 #ifdef PLATFORM_POSIX
367 , xdisplay(0)
368 , xscreen(0)
369 , xbuflen(1024 * 1024)
370 #elif PLATFORM_WIN32
371 , stdinput(nullptr)
372 , stdoutput(nullptr)
373 #endif
374 {
375 ssh->otype = SHELL;
376 ssh->noblock = true;
377 Zero(tflags);
378 }
379
~SshShell()380 SshShell::~SshShell()
381 {
382 ConsoleRawMode(false);
383
384 }
385 }