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 }