Line data Source code
1 : /*
2 : Mosh: the mobile shell
3 : Copyright 2012 Keith Winstein
4 :
5 : This program is free software: you can redistribute it and/or modify
6 : it under the terms of the GNU General Public License as published by
7 : the Free Software Foundation, either version 3 of the License, or
8 : (at your option) any later version.
9 :
10 : This program is distributed in the hope that it will be useful,
11 : but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public License
16 : along with this program. If not, see <http://www.gnu.org/licenses/>.
17 :
18 : In addition, as a special exception, the copyright holders give
19 : permission to link the code of portions of this program with the
20 : OpenSSL library under certain conditions as described in each
21 : individual source file, and distribute linked combinations including
22 : the two.
23 :
24 : You must obey the GNU General Public License in all respects for all
25 : of the code used other than OpenSSL. If you modify file(s) with this
26 : exception, you may extend this exception to your version of the
27 : file(s), but you are not obligated to do so. If you do not wish to do
28 : so, delete this exception statement from your version. If you delete
29 : this exception statement from all source files in the program, then
30 : also delete it here.
31 : */
32 :
33 : #include "config.h"
34 :
35 : #include <err.h>
36 : #include <errno.h>
37 : #include <locale.h>
38 : #include <string.h>
39 : #include <unistd.h>
40 : #include <stdio.h>
41 : #include <stdlib.h>
42 : #include <sys/ioctl.h>
43 : #include <sys/types.h>
44 : #include <pwd.h>
45 : #include <signal.h>
46 : #include <time.h>
47 :
48 : #if HAVE_PTY_H
49 : #include <pty.h>
50 : #elif HAVE_UTIL_H
51 : #include <util.h>
52 : #endif
53 :
54 : #include "stmclient.h"
55 : #include "swrite.h"
56 : #include "completeterminal.h"
57 : #include "user.h"
58 : #include "fatal_assert.h"
59 : #include "locale_utils.h"
60 : #include "pty_compat.h"
61 : #include "select.h"
62 : #include "timestamp.h"
63 :
64 : #include "networktransport-impl.h"
65 :
66 : using std::wstring;
67 :
68 0 : void STMClient::resume( void )
69 : {
70 : /* Restore termios state */
71 0 : if ( tcsetattr( STDIN_FILENO, TCSANOW, &raw_termios ) < 0 ) {
72 0 : perror( "tcsetattr" );
73 0 : exit( 1 );
74 : }
75 :
76 : /* Put terminal in application-cursor-key mode */
77 0 : swrite( STDOUT_FILENO, display.open().c_str() );
78 :
79 : /* Flag that outer terminal state is unknown */
80 0 : repaint_requested = true;
81 0 : }
82 :
83 448 : void STMClient::init( void )
84 : {
85 448 : if ( !is_utf8_locale() ) {
86 0 : LocaleVar native_ctype = get_ctype();
87 0 : string native_charset( locale_charset() );
88 :
89 0 : fprintf( stderr, "mosh-client needs a UTF-8 native locale to run.\n\n"
90 : "Unfortunately, the client's environment (%s) specifies\n"
91 : "the character set \"%s\".\n\n",
92 0 : native_ctype.str().c_str(), native_charset.c_str() );
93 0 : int unused __attribute((unused)) = system( "locale" );
94 0 : exit( 1 );
95 0 : }
96 :
97 : /* Verify terminal configuration */
98 448 : if ( tcgetattr( STDIN_FILENO, &saved_termios ) < 0 ) {
99 0 : perror( "tcgetattr" );
100 0 : exit( 1 );
101 : }
102 :
103 : /* Put terminal driver in raw mode */
104 448 : raw_termios = saved_termios;
105 :
106 : #ifdef HAVE_IUTF8
107 448 : if ( !(raw_termios.c_iflag & IUTF8) ) {
108 : // fprintf( stderr, "Warning: Locale is UTF-8 but termios IUTF8 flag not set. Setting IUTF8 flag.\n" );
109 : /* Probably not really necessary since we are putting terminal driver into raw mode anyway. */
110 2 : raw_termios.c_iflag |= IUTF8;
111 : }
112 : #endif /* HAVE_IUTF8 */
113 :
114 448 : cfmakeraw( &raw_termios );
115 :
116 448 : if ( tcsetattr( STDIN_FILENO, TCSANOW, &raw_termios ) < 0 ) {
117 0 : perror( "tcsetattr" );
118 0 : exit( 1 );
119 : }
120 :
121 : /* Put terminal in application-cursor-key mode */
122 448 : swrite( STDOUT_FILENO, display.open().c_str() );
123 :
124 : /* Add our name to window title */
125 448 : if ( !getenv( "MOSH_TITLE_NOPREFIX" ) ) {
126 1344 : overlays.set_title_prefix( wstring( L"[mosh] " ) );
127 : }
128 :
129 : /* Set terminal escape key. */
130 448 : const char *escape_key_env;
131 448 : if ( (escape_key_env = getenv( "MOSH_ESCAPE_KEY" )) != NULL ) {
132 0 : if ( strlen( escape_key_env ) == 1 ) {
133 0 : escape_key = (int)escape_key_env[0];
134 0 : if ( escape_key > 0 && escape_key < 128 ) {
135 0 : if ( escape_key < 32 ) {
136 : /* If escape is ctrl-something, pass it with repeating the key without ctrl. */
137 0 : escape_pass_key = escape_key + (int)'@';
138 : } else {
139 : /* If escape is something else, pass it with repeating the key itself. */
140 0 : escape_pass_key = escape_key;
141 : }
142 0 : if ( escape_pass_key >= 'A' && escape_pass_key <= 'Z' ) {
143 : /* If escape pass is an upper case character, define optional version
144 : as lower case of the same. */
145 0 : escape_pass_key2 = escape_pass_key + (int)'a' - (int)'A';
146 : } else {
147 0 : escape_pass_key2 = escape_pass_key;
148 : }
149 : } else {
150 0 : escape_key = 0x1E;
151 0 : escape_pass_key = '^';
152 0 : escape_pass_key2 = '^';
153 : }
154 0 : } else if ( strlen( escape_key_env ) == 0 ) {
155 0 : escape_key = -1;
156 : } else {
157 0 : escape_key = 0x1E;
158 0 : escape_pass_key = '^';
159 0 : escape_pass_key2 = '^';
160 : }
161 : } else {
162 448 : escape_key = 0x1E;
163 448 : escape_pass_key = '^';
164 448 : escape_pass_key2 = '^';
165 : }
166 :
167 : /* There are so many better ways to shoot oneself into leg than
168 : setting escape key to Ctrl-C, Ctrl-D, NewLine, Ctrl-L or CarriageReturn
169 : that we just won't allow that. */
170 448 : if ( escape_key == 0x03 || escape_key == 0x04 || escape_key == 0x0A || escape_key == 0x0C || escape_key == 0x0D ) {
171 0 : escape_key = 0x1E;
172 0 : escape_pass_key = '^';
173 0 : escape_pass_key2 = '^';
174 : }
175 :
176 : /* Adjust escape help differently if escape is a control character. */
177 448 : if ( escape_key > 0 ) {
178 448 : char escape_pass_name_buf[16];
179 448 : char escape_key_name_buf[16];
180 448 : snprintf(escape_pass_name_buf, sizeof escape_pass_name_buf, "\"%c\"", escape_pass_key);
181 448 : if (escape_key < 32) {
182 448 : snprintf(escape_key_name_buf, sizeof escape_key_name_buf, "Ctrl-%c", escape_pass_key);
183 448 : escape_requires_lf = false;
184 : } else {
185 0 : snprintf(escape_key_name_buf, sizeof escape_key_name_buf, "\"%c\"", escape_key);
186 0 : escape_requires_lf = true;
187 : }
188 448 : string tmp;
189 448 : tmp = string( escape_pass_name_buf );
190 448 : wstring escape_pass_name = std::wstring(tmp.begin(), tmp.end());
191 448 : tmp = string( escape_key_name_buf );
192 448 : wstring escape_key_name = std::wstring(tmp.begin(), tmp.end());
193 896 : escape_key_help = L"Commands: Ctrl-Z suspends, \".\" quits, " + escape_pass_name + L" gives literal " + escape_key_name;
194 448 : overlays.get_notification_engine().set_escape_key_string( tmp );
195 448 : }
196 448 : wchar_t tmp[ 128 ];
197 448 : swprintf( tmp, 128, L"Nothing received from server on UDP port %s.", port.c_str() );
198 448 : connecting_notification = wstring( tmp );
199 448 : }
200 :
201 448 : void STMClient::shutdown( void )
202 : {
203 : /* Restore screen state */
204 448 : overlays.get_notification_engine().set_notification_string( wstring( L"" ) );
205 448 : overlays.get_notification_engine().server_heard( timestamp() );
206 896 : overlays.set_title_prefix( wstring( L"" ) );
207 448 : output_new_frame();
208 :
209 : /* Restore terminal and terminal-driver state */
210 448 : swrite( STDOUT_FILENO, display.close().c_str() );
211 :
212 448 : if ( tcsetattr( STDIN_FILENO, TCSANOW, &saved_termios ) < 0 ) {
213 0 : perror( "tcsetattr" );
214 0 : exit( 1 );
215 : }
216 :
217 448 : if ( still_connecting() ) {
218 0 : fprintf( stderr, "\nmosh did not make a successful connection to %s:%s.\n"
219 : "Please verify that UDP port %s is not firewalled and can reach the server.\n\n"
220 : "(By default, mosh uses a UDP port between 60000 and 61000. The -p option\n"
221 : "selects a specific UDP port number.)\n", ip.c_str(), port.c_str(), port.c_str() );
222 448 : } else if ( network && !clean_shutdown ) {
223 0 : fputs( "\n\nmosh did not shut down cleanly. Please note that the\n"
224 : "mosh-server process may still be running on the server.\n", stderr );
225 : }
226 448 : }
227 :
228 448 : void STMClient::main_init( void )
229 : {
230 448 : Select &sel = Select::get_instance();
231 448 : sel.add_signal( SIGWINCH );
232 448 : sel.add_signal( SIGTERM );
233 448 : sel.add_signal( SIGINT );
234 448 : sel.add_signal( SIGHUP );
235 448 : sel.add_signal( SIGPIPE );
236 448 : sel.add_signal( SIGCONT );
237 :
238 : /* get initial window size */
239 448 : if ( ioctl( STDIN_FILENO, TIOCGWINSZ, &window_size ) < 0 ) {
240 0 : perror( "ioctl TIOCGWINSZ" );
241 0 : return;
242 : }
243 :
244 : /* local state */
245 448 : local_framebuffer = Terminal::Framebuffer( window_size.ws_col, window_size.ws_row );
246 448 : new_state = Terminal::Framebuffer( 1, 1 );
247 :
248 : /* initialize screen */
249 448 : string init = display.new_frame( false, local_framebuffer, local_framebuffer );
250 448 : swrite( STDOUT_FILENO, init.data(), init.size() );
251 :
252 : /* open network */
253 448 : Network::UserStream blank;
254 448 : Terminal::Complete local_terminal( window_size.ws_col, window_size.ws_row );
255 896 : network = NetworkPointer( new NetworkType( blank, local_terminal, key.c_str(), ip.c_str(), port.c_str() ) );
256 :
257 448 : network->set_send_delay( 1 ); /* minimal delay on outgoing keystrokes */
258 :
259 : /* tell server the size of the terminal */
260 448 : network->get_current_state().push_back( Parser::Resize( window_size.ws_col, window_size.ws_row ) );
261 :
262 : /* be noisy as necessary */
263 448 : network->set_verbose( verbose );
264 448 : Select::set_verbose( verbose );
265 896 : }
266 :
267 6536 : void STMClient::output_new_frame( void )
268 : {
269 6536 : if ( !network ) { /* clean shutdown even when not initialized */
270 0 : return;
271 : }
272 :
273 : /* fetch target state */
274 6536 : new_state = network->get_latest_remote_state().state.get_fb();
275 :
276 : /* apply local overlays */
277 6536 : overlays.apply( new_state );
278 :
279 : /* calculate minimal difference from where we are */
280 6536 : const string diff( display.new_frame( !repaint_requested,
281 6536 : local_framebuffer,
282 6536 : new_state ) );
283 6536 : swrite( STDOUT_FILENO, diff.data(), diff.size() );
284 :
285 6536 : repaint_requested = false;
286 :
287 6536 : local_framebuffer = new_state;
288 6536 : }
289 :
290 3333 : void STMClient::process_network_input( void )
291 : {
292 3333 : network->recv();
293 :
294 : /* Now give hints to the overlays */
295 3333 : overlays.get_notification_engine().server_heard( network->get_latest_remote_state().timestamp );
296 3333 : overlays.get_notification_engine().server_acked( network->get_sent_state_acked_timestamp() );
297 :
298 3333 : overlays.get_prediction_engine().set_local_frame_acked( network->get_sent_state_acked() );
299 3333 : overlays.get_prediction_engine().set_send_interval( network->send_interval() );
300 3333 : overlays.get_prediction_engine().set_local_frame_late_acked( network->get_latest_remote_state().state.get_echo_ack() );
301 3333 : }
302 :
303 659 : bool STMClient::process_user_input( int fd )
304 : {
305 659 : const int buf_size = 16384;
306 659 : char buf[ buf_size ];
307 :
308 : /* fill buffer if possible */
309 659 : ssize_t bytes_read = read( fd, buf, buf_size );
310 659 : if ( bytes_read == 0 ) { /* EOF */
311 : return false;
312 659 : } else if ( bytes_read < 0 ) {
313 0 : perror( "read" );
314 0 : return false;
315 : }
316 :
317 659 : NetworkType &net = *network;
318 :
319 659 : if ( net.shutdown_in_progress() ) {
320 : return true;
321 : }
322 659 : overlays.get_prediction_engine().set_local_frame_sent( net.get_sent_state_last() );
323 :
324 : /* Don't predict for bulk data. */
325 659 : bool paste = bytes_read > 100;
326 659 : if ( paste ) {
327 0 : overlays.get_prediction_engine().reset();
328 : }
329 :
330 1842 : for ( int i = 0; i < bytes_read; i++ ) {
331 1183 : char the_byte = buf[ i ];
332 :
333 1183 : if ( !paste ) {
334 1183 : overlays.get_prediction_engine().new_user_byte( the_byte, local_framebuffer );
335 : }
336 :
337 1183 : if ( quit_sequence_started ) {
338 0 : if ( the_byte == '.' ) { /* Quit sequence is Ctrl-^ . */
339 0 : if ( net.has_remote_addr() && (!net.shutdown_in_progress()) ) {
340 0 : overlays.get_notification_engine().set_notification_string( wstring( L"Exiting on user request..." ), true );
341 0 : net.start_shutdown();
342 0 : return true;
343 : }
344 : return false;
345 0 : } else if ( the_byte == 0x1a ) { /* Suspend sequence is escape_key Ctrl-Z */
346 : /* Restore terminal and terminal-driver state */
347 0 : swrite( STDOUT_FILENO, display.close().c_str() );
348 :
349 0 : if ( tcsetattr( STDIN_FILENO, TCSANOW, &saved_termios ) < 0 ) {
350 0 : perror( "tcsetattr" );
351 0 : exit( 1 );
352 : }
353 :
354 0 : fputs( "\n\033[37;44m[mosh is suspended.]\033[m\n", stdout );
355 :
356 0 : fflush( NULL );
357 :
358 : /* actually suspend */
359 0 : kill( 0, SIGSTOP );
360 :
361 0 : resume();
362 0 : } else if ( (the_byte == escape_pass_key) || (the_byte == escape_pass_key2) ) {
363 : /* Emulation sequence to type escape_key is escape_key +
364 : escape_pass_key (that is escape key without Ctrl) */
365 0 : net.get_current_state().push_back( Parser::UserByte( escape_key ) );
366 : } else {
367 : /* Escape key followed by anything other than . and ^ gets sent literally */
368 0 : net.get_current_state().push_back( Parser::UserByte( escape_key ) );
369 0 : net.get_current_state().push_back( Parser::UserByte( the_byte ) );
370 : }
371 :
372 0 : quit_sequence_started = false;
373 :
374 0 : if ( overlays.get_notification_engine().get_notification_string() == escape_key_help ) {
375 0 : overlays.get_notification_engine().set_notification_string( L"" );
376 : }
377 :
378 0 : continue;
379 : }
380 :
381 1183 : quit_sequence_started = (escape_key > 0) && (the_byte == escape_key) && (lf_entered || (! escape_requires_lf));
382 1183 : if ( quit_sequence_started ) {
383 0 : lf_entered = false;
384 0 : overlays.get_notification_engine().set_notification_string( escape_key_help, true, false );
385 0 : continue;
386 : }
387 :
388 1183 : lf_entered = ( (the_byte == 0x0A) || (the_byte == 0x0D) ); /* LineFeed, Ctrl-J, '\n' or CarriageReturn, Ctrl-M, '\r' */
389 :
390 1183 : if ( the_byte == 0x0C ) { /* Ctrl-L */
391 0 : repaint_requested = true;
392 : }
393 :
394 1183 : net.get_current_state().push_back( Parser::UserByte( the_byte ) );
395 : }
396 :
397 : return true;
398 : }
399 :
400 44 : bool STMClient::process_resize( void )
401 : {
402 : /* get new size */
403 44 : if ( ioctl( STDIN_FILENO, TIOCGWINSZ, &window_size ) < 0 ) {
404 0 : perror( "ioctl TIOCGWINSZ" );
405 0 : return false;
406 : }
407 :
408 : /* tell remote emulator */
409 44 : Parser::Resize res( window_size.ws_col, window_size.ws_row );
410 :
411 44 : if ( !network->shutdown_in_progress() ) {
412 44 : network->get_current_state().push_back( res );
413 : }
414 :
415 : /* note remote emulator will probably reply with its own Resize to adjust our state */
416 :
417 : /* tell prediction engine */
418 44 : overlays.get_prediction_engine().reset();
419 :
420 44 : return true;
421 44 : }
422 :
423 448 : bool STMClient::main( void )
424 : {
425 : /* initialize signal handling and structures */
426 448 : main_init();
427 :
428 : /* Drop unnecessary privileges */
429 : #ifdef HAVE_PLEDGE
430 : /* OpenBSD pledge() syscall */
431 : if ( pledge( "stdio inet tty", NULL )) {
432 : perror( "pledge() failed" );
433 : exit( 1 );
434 : }
435 : #endif
436 :
437 : /* prepare to poll for events */
438 448 : Select &sel = Select::get_instance();
439 :
440 6088 : while ( 1 ) {
441 6088 : try {
442 6088 : output_new_frame();
443 :
444 6088 : int wait_time = std::min( network->wait_time(), overlays.wait_time() );
445 :
446 : /* Handle startup "Connecting..." message */
447 109584 : if ( still_connecting() ) {
448 1424 : wait_time = std::min( 250, wait_time );
449 : }
450 :
451 : /* poll for events */
452 : /* network->fd() can in theory change over time */
453 6088 : sel.clear_fds();
454 6088 : std::vector< int > fd_list( network->fds() );
455 12176 : for ( std::vector< int >::const_iterator it = fd_list.begin();
456 12176 : it != fd_list.end();
457 12176 : it++ ) {
458 6088 : sel.add_fd( *it );
459 : }
460 6088 : sel.add_fd( STDIN_FILENO );
461 :
462 6088 : int active_fds = sel.select( wait_time );
463 6088 : if ( active_fds < 0 ) {
464 0 : perror( "select" );
465 : break;
466 : }
467 :
468 6088 : bool network_ready_to_read = false;
469 :
470 12176 : for ( std::vector< int >::const_iterator it = fd_list.begin();
471 12176 : it != fd_list.end();
472 12176 : it++ ) {
473 6088 : if ( sel.read( *it ) ) {
474 : /* packet received from the network */
475 : /* we only read one socket each run */
476 3333 : network_ready_to_read = true;
477 : }
478 : }
479 :
480 6088 : if ( network_ready_to_read ) {
481 3333 : process_network_input();
482 : }
483 :
484 6088 : if ( sel.read( STDIN_FILENO ) && !process_user_input( STDIN_FILENO ) ) { /* input from the user needs to be fed to the network */
485 0 : if ( !network->has_remote_addr() ) {
486 : break;
487 0 : } else if ( !network->shutdown_in_progress() ) {
488 0 : overlays.get_notification_engine().set_notification_string( wstring( L"Exiting..." ), true );
489 0 : network->start_shutdown();
490 : }
491 : }
492 :
493 6088 : if ( sel.signal( SIGWINCH ) && !process_resize() ) { /* resize */
494 0 : return false;
495 : }
496 :
497 6088 : if ( sel.signal( SIGCONT ) ) {
498 0 : resume();
499 : }
500 :
501 6088 : if ( sel.signal( SIGTERM )
502 6088 : || sel.signal( SIGINT )
503 6088 : || sel.signal( SIGHUP )
504 12176 : || sel.signal( SIGPIPE ) ) {
505 : /* shutdown signal */
506 0 : if ( !network->has_remote_addr() ) {
507 : break;
508 0 : } else if ( !network->shutdown_in_progress() ) {
509 0 : overlays.get_notification_engine().set_notification_string( wstring( L"Signal received, shutting down..." ), true );
510 0 : network->start_shutdown();
511 : }
512 : }
513 :
514 : /* quit if our shutdown has been acknowledged */
515 6088 : if ( network->shutdown_in_progress() && network->shutdown_acknowledged() ) {
516 0 : clean_shutdown = true;
517 0 : break;
518 : }
519 :
520 : /* quit after shutdown acknowledgement timeout */
521 6088 : if ( network->shutdown_in_progress() && network->shutdown_ack_timed_out() ) {
522 : break;
523 : }
524 :
525 : /* quit if we received and acknowledged a shutdown request */
526 6088 : if ( network->counterparty_shutdown_ack_sent() ) {
527 448 : clean_shutdown = true;
528 448 : break;
529 : }
530 :
531 : /* write diagnostic message if can't reach server */
532 5640 : if ( still_connecting()
533 486 : && (!network->shutdown_in_progress())
534 486 : && (timestamp() - network->get_latest_remote_state().timestamp > 250) ) {
535 0 : if ( timestamp() - network->get_latest_remote_state().timestamp > 15000 ) {
536 0 : if ( !network->shutdown_in_progress() ) {
537 0 : overlays.get_notification_engine().set_notification_string( wstring( L"Timed out waiting for server..." ), true );
538 0 : network->start_shutdown();
539 : }
540 : } else {
541 0 : overlays.get_notification_engine().set_notification_string( connecting_notification );
542 : }
543 5640 : } else if ( (network->get_remote_state_num() != 0)
544 5640 : && (overlays.get_notification_engine().get_notification_string()
545 5154 : == connecting_notification) ) {
546 0 : overlays.get_notification_engine().set_notification_string( L"" );
547 : }
548 :
549 5640 : network->tick();
550 :
551 5640 : string & send_error = network->get_send_error();
552 5640 : if ( !send_error.empty() ) {
553 0 : overlays.get_notification_engine().set_network_error( send_error );
554 5640 : send_error.clear();
555 : } else {
556 5640 : overlays.get_notification_engine().clear_network_error();
557 : }
558 5640 : } catch ( const Network::NetworkException &e ) {
559 0 : if ( !network->shutdown_in_progress() ) {
560 0 : overlays.get_notification_engine().set_network_error( e.what() );
561 : }
562 :
563 0 : struct timespec req;
564 0 : req.tv_sec = 0;
565 0 : req.tv_nsec = 200000000; /* 0.2 sec */
566 0 : nanosleep( &req, NULL );
567 0 : freeze_timestamp();
568 0 : } catch ( const Crypto::CryptoException &e ) {
569 0 : if ( e.fatal ) {
570 0 : throw;
571 : } else {
572 0 : wchar_t tmp[ 128 ];
573 0 : swprintf( tmp, 128, L"Crypto exception: %s", e.what() );
574 0 : overlays.get_notification_engine().set_notification_string( wstring( tmp ) );
575 : }
576 0 : }
577 : }
578 448 : return clean_shutdown;
579 : }
580 :
|