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 : #include "version.h"
35 :
36 : #include <err.h>
37 : #include <errno.h>
38 : #include <locale.h>
39 : #include <string.h>
40 : #include <strings.h>
41 : #include <sstream>
42 : #include <termios.h>
43 : #include <unistd.h>
44 : #include <fcntl.h>
45 : #include <stdio.h>
46 : #include <stdlib.h>
47 : #include <sys/ioctl.h>
48 : #include <sys/types.h>
49 : #include <pwd.h>
50 : #include <typeinfo>
51 : #include <signal.h>
52 : #ifdef HAVE_UTEMPTER
53 : #include <utempter.h>
54 : #endif
55 : #ifdef HAVE_SYSLOG
56 : #include <syslog.h>
57 : #endif
58 : #include <sys/socket.h>
59 : #include <netdb.h>
60 : #include <time.h>
61 : #include <sys/stat.h>
62 : #include <inttypes.h>
63 :
64 : #ifdef HAVE_UTMPX_H
65 : #include <utmpx.h>
66 : #endif
67 :
68 : #ifdef HAVE_PATHS_H
69 : #include <paths.h>
70 : #endif
71 :
72 : #if HAVE_PTY_H
73 : #include <pty.h>
74 : #elif HAVE_UTIL_H
75 : #include <util.h>
76 : #endif
77 :
78 : #if FORKPTY_IN_LIBUTIL
79 : #include <libutil.h>
80 : #endif
81 :
82 : #include "completeterminal.h"
83 : #include "swrite.h"
84 : #include "user.h"
85 : #include "fatal_assert.h"
86 : #include "locale_utils.h"
87 : #include "pty_compat.h"
88 : #include "select.h"
89 : #include "timestamp.h"
90 : #include "fatal_assert.h"
91 :
92 : #ifndef _PATH_BSHELL
93 : #define _PATH_BSHELL "/bin/sh"
94 : #endif
95 :
96 : #include "networktransport-impl.h"
97 :
98 : typedef Network::Transport< Terminal::Complete, Network::UserStream > ServerConnection;
99 :
100 : static void serve( int host_fd,
101 : Terminal::Complete &terminal,
102 : ServerConnection &network,
103 : long network_timeout,
104 : long network_signaled_timeout );
105 :
106 : static int run_server( const char *desired_ip, const char *desired_port,
107 : const string &command_path, char *command_argv[],
108 : const int colors, unsigned int verbose, bool with_motd );
109 :
110 :
111 0 : static void print_version( FILE *file )
112 : {
113 0 : fputs( "mosh-server (" PACKAGE_STRING ") [build " BUILD_VERSION "]\n"
114 : "Copyright 2012 Keith Winstein <mosh-devel@mit.edu>\n"
115 : "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
116 : "This is free software: you are free to change and redistribute it.\n"
117 : "There is NO WARRANTY, to the extent permitted by law.\n", file );
118 0 : }
119 :
120 0 : static void print_usage( FILE *stream, const char *argv0 )
121 : {
122 3158 : fprintf( stream, "Usage: %s new [-s] [-v] [-i LOCALADDR] [-p PORT[:PORT2]] [-c COLORS] [-l NAME=VALUE] [-- COMMAND...]\n", argv0 );
123 0 : }
124 :
125 : static bool print_motd( const char *filename );
126 : static void chdir_homedir( void );
127 : static bool motd_hushed( void );
128 : static void warn_unattached( const string & ignore_entry );
129 :
130 : /* Simple spinloop */
131 0 : static void spin( void )
132 : {
133 0 : static unsigned int spincount = 0;
134 0 : spincount++;
135 :
136 0 : if ( spincount > 10 ) {
137 0 : struct timespec req;
138 0 : req.tv_sec = 0;
139 0 : req.tv_nsec = 100000000; /* 0.1 sec */
140 0 : nanosleep( &req, NULL );
141 0 : freeze_timestamp();
142 : }
143 0 : }
144 :
145 0 : static string get_SSH_IP( void )
146 : {
147 0 : const char *SSH_CONNECTION = getenv( "SSH_CONNECTION" );
148 0 : if ( !SSH_CONNECTION ) { /* Older sshds don't set this */
149 0 : fputs( "Warning: SSH_CONNECTION not found; binding to any interface.\n", stderr );
150 0 : return string( "" );
151 : }
152 0 : std::istringstream ss( SSH_CONNECTION );
153 0 : string dummy, local_interface_IP;
154 0 : ss >> dummy >> dummy >> local_interface_IP;
155 0 : if ( !ss ) {
156 0 : fputs( "Warning: Could not parse SSH_CONNECTION; binding to any interface.\n", stderr );
157 0 : return string( "" );
158 : }
159 :
160 : /* Strip IPv6 prefix. */
161 0 : const char IPv6_prefix[] = "::ffff:";
162 :
163 0 : if ( ( local_interface_IP.length() > strlen( IPv6_prefix ) )
164 0 : && ( 0 == strncasecmp( local_interface_IP.c_str(), IPv6_prefix, strlen( IPv6_prefix ) ) ) ) {
165 0 : return local_interface_IP.substr( strlen( IPv6_prefix ) );
166 : }
167 :
168 0 : return local_interface_IP;
169 0 : }
170 :
171 452 : int main( int argc, char *argv[] )
172 : {
173 : /* For security, make sure we don't dump core */
174 452 : Crypto::disable_dumping_core();
175 :
176 : /* Detect edge case */
177 452 : fatal_assert( argc > 0 );
178 :
179 452 : const char *desired_ip = NULL;
180 452 : string desired_ip_str;
181 452 : const char *desired_port = NULL;
182 452 : string command_path;
183 452 : char **command_argv = NULL;
184 452 : int colors = 0;
185 452 : unsigned int verbose = 0; /* don't close stdin/stdout/stderr */
186 : /* Will cause mosh-server not to correctly detach on old versions of sshd. */
187 452 : list<string> locale_vars;
188 :
189 : /* strip off command */
190 4966 : for ( int i = 1; i < argc; i++ ) {
191 4966 : if ( 0 == strcmp( argv[ i ], "--help" ) || 0 == strcmp( argv[ i ], "-h" ) ) {
192 0 : print_usage( stdout, argv[ 0 ] );
193 0 : exit( 0 );
194 : }
195 4966 : if ( 0 == strcmp( argv[ i ], "--version" ) ) {
196 0 : print_version( stdout );
197 0 : exit( 0 );
198 : }
199 4966 : if ( 0 == strcmp( argv[ i ], "--" ) ) { /* -- is mandatory */
200 452 : if ( i != argc - 1 ) {
201 452 : command_argv = argv + i + 1;
202 : }
203 : argc = i; /* rest of options before -- */
204 : break;
205 : }
206 : }
207 :
208 : /* Parse new command-line syntax */
209 452 : if ( (argc >= 2)
210 452 : && (strcmp( argv[ 1 ], "new" ) == 0) ) {
211 : /* new option syntax */
212 : int opt;
213 3158 : while ( (opt = getopt( argc - 1, argv + 1, "@:i:p:c:svl:" )) != -1 ) {
214 2706 : switch ( opt ) {
215 : /*
216 : * This undocumented option does nothing but eat its argument.
217 : * Useful in scripting where you prepend something to a
218 : * mosh-server argv, and might end up with something like
219 : * "mosh-server new -v new -c 256", now you can say
220 : * "mosh-server new -v -@ new -c 256" to discard the second
221 : * "new".
222 : */
223 : case '@':
224 : break;
225 452 : case 'i':
226 452 : desired_ip = optarg;
227 452 : break;
228 0 : case 'p':
229 0 : desired_port = optarg;
230 0 : break;
231 0 : case 's':
232 0 : desired_ip = NULL;
233 0 : desired_ip_str = get_SSH_IP();
234 0 : if ( !desired_ip_str.empty() ) {
235 0 : desired_ip = desired_ip_str.c_str();
236 0 : fatal_assert( desired_ip );
237 : }
238 : break;
239 452 : case 'c':
240 452 : try {
241 452 : colors = myatoi( optarg );
242 0 : } catch ( const CryptoException & ) {
243 0 : fprintf( stderr, "%s: Bad number of colors (%s)\n", argv[ 0 ], optarg );
244 0 : print_usage( stderr, argv[ 0 ] );
245 0 : exit( 1 );
246 0 : }
247 452 : break;
248 900 : case 'v':
249 900 : verbose++;
250 900 : break;
251 452 : case 'l':
252 904 : locale_vars.push_back( string( optarg ) );
253 452 : break;
254 0 : default:
255 : /* don't die on unknown options */
256 3158 : print_usage( stderr, argv[ 0 ] );
257 : break;
258 : }
259 : }
260 0 : } else if ( argc == 1 ) {
261 : /* legacy argument parsing for older client wrapper script */
262 : /* do nothing */
263 0 : } else if ( argc == 2 ) {
264 0 : desired_ip = argv[ 1 ];
265 0 : } else if ( argc == 3 ) {
266 0 : desired_ip = argv[ 1 ];
267 0 : desired_port = argv[ 2 ];
268 : } else {
269 0 : print_usage( stderr, argv[ 0 ] );
270 0 : exit( 1 );
271 : }
272 :
273 : /* Sanity-check arguments */
274 452 : int dpl, dph;
275 452 : if ( desired_port && ! Connection::parse_portrange( desired_port, dpl, dph ) ) {
276 0 : fprintf( stderr, "%s: Bad UDP port range (%s)\n", argv[ 0 ], desired_port );
277 0 : print_usage( stderr, argv[ 0 ] );
278 0 : exit( 1 );
279 : }
280 :
281 452 : bool with_motd = false;
282 :
283 : #ifdef HAVE_SYSLOG
284 : openlog(argv[0], LOG_PID | LOG_NDELAY, LOG_AUTH);
285 : #endif
286 :
287 : /* Get shell */
288 452 : char *my_argv[ 2 ];
289 904 : string shell_name;
290 452 : if ( !command_argv ) {
291 : /* get shell name */
292 0 : const char *shell = getenv( "SHELL" );
293 0 : if ( shell == NULL ) {
294 0 : struct passwd *pw = getpwuid( getuid() );
295 0 : if ( pw == NULL ) {
296 0 : perror( "getpwuid" );
297 0 : exit( 1 );
298 : }
299 0 : shell = pw->pw_shell;
300 : }
301 :
302 0 : string shell_path( shell );
303 0 : if ( shell_path.empty() ) { /* empty shell means Bourne shell */
304 0 : shell_path = _PATH_BSHELL;
305 : }
306 :
307 0 : command_path = shell_path;
308 :
309 0 : size_t shell_slash( shell_path.rfind('/') );
310 0 : if ( shell_slash == string::npos ) {
311 0 : shell_name = shell_path;
312 : } else {
313 0 : shell_name = shell_path.substr(shell_slash + 1);
314 : }
315 :
316 : /* prepend '-' to make login shell */
317 0 : shell_name = '-' + shell_name;
318 :
319 0 : my_argv[ 0 ] = const_cast<char *>( shell_name.c_str() );
320 0 : my_argv[ 1 ] = NULL;
321 0 : command_argv = my_argv;
322 :
323 0 : with_motd = true;
324 0 : }
325 :
326 452 : if ( command_path.empty() ) {
327 452 : command_path = command_argv[0];
328 : }
329 :
330 : /* Adopt implementation locale */
331 452 : set_native_locale();
332 452 : if ( !is_utf8_locale() ) {
333 : /* save details for diagnostic */
334 0 : LocaleVar native_ctype = get_ctype();
335 0 : string native_charset( locale_charset() );
336 :
337 : /* apply locale-related environment variables from client */
338 0 : clear_locale_variables();
339 0 : for ( list<string>::const_iterator i = locale_vars.begin();
340 0 : i != locale_vars.end();
341 0 : i++ ) {
342 0 : char *env_string = strdup( i->c_str() );
343 0 : fatal_assert( env_string );
344 0 : if ( 0 != putenv( env_string ) ) {
345 0 : perror( "putenv" );
346 : }
347 : }
348 :
349 : /* check again */
350 0 : set_native_locale();
351 0 : if ( !is_utf8_locale() ) {
352 0 : LocaleVar client_ctype = get_ctype();
353 0 : string client_charset( locale_charset() );
354 :
355 0 : fprintf( stderr, "mosh-server needs a UTF-8 native locale to run.\n\n"
356 : "Unfortunately, the local environment (%s) specifies\n"
357 : "the character set \"%s\",\n\n"
358 : "The client-supplied environment (%s) specifies\n"
359 : "the character set \"%s\".\n\n",
360 0 : native_ctype.str().c_str(), native_charset.c_str(), client_ctype.str().c_str(), client_charset.c_str() );
361 0 : int unused __attribute((unused)) = system( "locale" );
362 0 : exit( 1 );
363 0 : }
364 0 : }
365 :
366 452 : try {
367 452 : return run_server( desired_ip, desired_port, command_path, command_argv, colors, verbose, with_motd );
368 0 : } catch ( const Network::NetworkException &e ) {
369 0 : fprintf( stderr, "Network exception: %s\n",
370 0 : e.what() );
371 0 : return 1;
372 0 : } catch ( const Crypto::CryptoException &e ) {
373 0 : fprintf( stderr, "Crypto exception: %s\n",
374 0 : e.what() );
375 0 : return 1;
376 0 : }
377 902 : }
378 :
379 452 : static int run_server( const char *desired_ip, const char *desired_port,
380 : const string &command_path, char *command_argv[],
381 : const int colors, unsigned int verbose, bool with_motd ) {
382 : /* get network idle timeout */
383 452 : long network_timeout = 0;
384 452 : char *timeout_envar = getenv( "MOSH_SERVER_NETWORK_TMOUT" );
385 452 : if ( timeout_envar && *timeout_envar ) {
386 2 : errno = 0;
387 2 : char *endptr;
388 2 : network_timeout = strtol( timeout_envar, &endptr, 10 );
389 2 : if ( *endptr != '\0' || ( network_timeout == 0 && errno == EINVAL ) ) {
390 0 : fputs( "MOSH_SERVER_NETWORK_TMOUT not a valid integer, ignoring\n", stderr );
391 2 : } else if ( network_timeout < 0 ) {
392 0 : fputs( "MOSH_SERVER_NETWORK_TMOUT is negative, ignoring\n", stderr );
393 : network_timeout = 0;
394 : }
395 : }
396 : /* get network signaled idle timeout */
397 452 : long network_signaled_timeout = 0;
398 452 : char *signal_envar = getenv( "MOSH_SERVER_SIGNAL_TMOUT" );
399 452 : if ( signal_envar && *signal_envar ) {
400 2 : errno = 0;
401 2 : char *endptr;
402 2 : network_signaled_timeout = strtol( signal_envar, &endptr, 10 );
403 2 : if ( *endptr != '\0' || ( network_signaled_timeout == 0 && errno == EINVAL ) ) {
404 0 : fputs( "MOSH_SERVER_SIGNAL_TMOUT not a valid integer, ignoring\n", stderr );
405 2 : } else if ( network_signaled_timeout < 0 ) {
406 0 : fputs( "MOSH_SERVER_SIGNAL_TMOUT is negative, ignoring\n", stderr );
407 : network_signaled_timeout = 0;
408 : }
409 : }
410 : /* get initial window size */
411 452 : struct winsize window_size;
412 452 : if ( ioctl( STDIN_FILENO, TIOCGWINSZ, &window_size ) < 0 ||
413 452 : window_size.ws_col == 0 ||
414 452 : window_size.ws_row == 0 ) {
415 : /* Fill in sensible defaults. */
416 : /* They will be overwritten by client on first connection. */
417 0 : memset( &window_size, 0, sizeof( window_size ) );
418 0 : window_size.ws_col = 80;
419 0 : window_size.ws_row = 24;
420 : }
421 :
422 : /* open parser and terminal */
423 452 : Terminal::Complete terminal( window_size.ws_col, window_size.ws_row );
424 :
425 : /* open network */
426 452 : Network::UserStream blank;
427 452 : typedef shared::shared_ptr<ServerConnection> NetworkPointer;
428 452 : NetworkPointer network( new ServerConnection( terminal, blank, desired_ip, desired_port ) );
429 :
430 452 : network->set_verbose( verbose );
431 452 : Select::set_verbose( verbose );
432 :
433 : /*
434 : * If mosh-server is run on a pty, then typeahead may echo and break mosh.pl's
435 : * detection of the MOSH CONNECT message. Print it on a new line to bodge
436 : * around that.
437 : */
438 452 : if ( isatty( STDIN_FILENO ) ) {
439 452 : puts( "\r\n" );
440 : }
441 904 : printf( "MOSH CONNECT %s %s\n", network->port().c_str(), network->get_key().c_str() );
442 :
443 : /* don't let signals kill us */
444 452 : struct sigaction sa;
445 452 : sa.sa_handler = SIG_IGN;
446 452 : sa.sa_flags = 0;
447 452 : fatal_assert( 0 == sigfillset( &sa.sa_mask ) );
448 452 : fatal_assert( 0 == sigaction( SIGHUP, &sa, NULL ) );
449 452 : fatal_assert( 0 == sigaction( SIGPIPE, &sa, NULL ) );
450 :
451 :
452 : /* detach from terminal */
453 452 : fflush( NULL );
454 452 : pid_t the_pid = fork();
455 1356 : if ( the_pid < 0 ) {
456 0 : perror( "fork" );
457 1356 : } else if ( the_pid > 0 ) {
458 452 : fputs( "\nmosh-server (" PACKAGE_STRING ") [build " BUILD_VERSION "]\n"
459 : "Copyright 2012 Keith Winstein <mosh-devel@mit.edu>\n"
460 : "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
461 : "This is free software: you are free to change and redistribute it.\n"
462 : "There is NO WARRANTY, to the extent permitted by law.\n\n", stderr );
463 :
464 452 : fprintf( stderr, "[mosh-server detached, pid = %d]\n", static_cast<int>(the_pid) );
465 : #ifndef HAVE_IUTF8
466 : fputs( "\nWarning: termios IUTF8 flag not defined.\n"
467 : "Character-erase of multibyte character sequence\n"
468 : "probably does not work properly on this platform.\n", stderr );
469 : #endif /* HAVE_IUTF8 */
470 :
471 452 : fflush( NULL );
472 452 : if ( isatty( STDOUT_FILENO ) ) {
473 0 : tcdrain( STDOUT_FILENO );
474 : }
475 452 : if ( isatty( STDERR_FILENO ) ) {
476 0 : tcdrain( STDERR_FILENO );
477 : }
478 452 : exit( 0 );
479 : }
480 :
481 904 : int master;
482 :
483 : /* close file descriptors */
484 904 : if ( verbose == 0 ) {
485 : /* Necessary to properly detach on old versions of sshd (e.g. RHEL/CentOS 5.0). */
486 4 : int nullfd;
487 :
488 4 : nullfd = open( "/dev/null", O_RDWR );
489 4 : if ( nullfd == -1 ) {
490 0 : perror( "open" );
491 0 : exit( 1 );
492 : }
493 :
494 8 : if ( dup2 ( nullfd, STDIN_FILENO ) < 0 ||
495 8 : dup2 ( nullfd, STDOUT_FILENO ) < 0 ||
496 4 : dup2 ( nullfd, STDERR_FILENO ) < 0 ) {
497 0 : perror( "dup2" );
498 0 : exit( 1 );
499 : }
500 :
501 4 : if ( close( nullfd ) < 0 ) {
502 0 : perror( "close" );
503 0 : exit( 1 );
504 : }
505 : }
506 :
507 904 : char utmp_entry[ 64 ] = { 0 };
508 904 : snprintf( utmp_entry, 64, "mosh [%ld]", static_cast<long int>( getpid() ) );
509 :
510 : /* Fork child process */
511 904 : pid_t child = forkpty( &master, NULL, NULL, &window_size );
512 :
513 904 : if ( child == -1 ) {
514 0 : perror( "forkpty" );
515 0 : exit( 1 );
516 : }
517 :
518 904 : if ( child == 0 ) {
519 : /* child */
520 :
521 : /* reenable signals */
522 452 : struct sigaction sa;
523 452 : sa.sa_handler = SIG_DFL;
524 452 : sa.sa_flags = 0;
525 452 : fatal_assert( 0 == sigfillset( &sa.sa_mask ) );
526 452 : fatal_assert( 0 == sigaction( SIGHUP, &sa, NULL ) );
527 452 : fatal_assert( 0 == sigaction( SIGPIPE, &sa, NULL ) );
528 :
529 : #ifdef HAVE_SYSLOG
530 : closelog();
531 : #endif
532 :
533 : /* close server-related file descriptors */
534 452 : network.reset();
535 :
536 : /* set IUTF8 if available */
537 : #ifdef HAVE_IUTF8
538 452 : struct termios child_termios;
539 452 : if ( tcgetattr( STDIN_FILENO, &child_termios ) < 0 ) {
540 0 : perror( "tcgetattr" );
541 0 : exit( 1 );
542 : }
543 :
544 452 : child_termios.c_iflag |= IUTF8;
545 :
546 452 : if ( tcsetattr( STDIN_FILENO, TCSANOW, &child_termios ) < 0 ) {
547 0 : perror( "tcsetattr" );
548 0 : exit( 1 );
549 : }
550 : #endif /* HAVE_IUTF8 */
551 :
552 : /* set TERM */
553 452 : const char default_term[] = "xterm";
554 452 : const char color_term[] = "xterm-256color";
555 :
556 904 : if ( setenv( "TERM", (colors == 256) ? color_term : default_term, true ) < 0 ) {
557 0 : perror( "setenv" );
558 0 : exit( 1 );
559 : }
560 :
561 : /* ask ncurses to send UTF-8 instead of ISO 2022 for line-drawing chars */
562 452 : if ( setenv( "NCURSES_NO_UTF8_ACS", "1", true ) < 0 ) {
563 0 : perror( "setenv" );
564 0 : exit( 1 );
565 : }
566 :
567 : /* clear STY environment variable so GNU screen regards us as top level */
568 452 : if ( unsetenv( "STY" ) < 0 ) {
569 0 : perror( "unsetenv" );
570 0 : exit( 1 );
571 : }
572 :
573 452 : chdir_homedir();
574 :
575 452 : if ( with_motd && (!motd_hushed()) ) {
576 : // On illumos motd is printed by /etc/profile.
577 : #ifndef __sun
578 : // For Ubuntu, try and print one of {,/var}/run/motd.dynamic.
579 : // This file is only updated when pam_motd is run, but when
580 : // mosh-server is run in the usual way with ssh via the script,
581 : // this always happens.
582 : // XXX Hackish knowledge of Ubuntu PAM configuration.
583 : // But this seems less awful than build-time detection with autoconf.
584 0 : if (!print_motd("/run/motd.dynamic")) {
585 0 : print_motd("/var/run/motd.dynamic");
586 : }
587 : // Always print traditional /etc/motd.
588 0 : print_motd("/etc/motd");
589 : #endif
590 0 : warn_unattached( utmp_entry );
591 : }
592 :
593 : /* Wait for parent to release us. */
594 452 : char linebuf[81];
595 452 : if (fgets(linebuf, sizeof linebuf, stdin) == NULL) {
596 0 : err( 1, "parent signal" );
597 : }
598 :
599 452 : Crypto::reenable_dumping_core();
600 :
601 0 : if ( execvp( command_path.c_str(), command_argv ) < 0 ) {
602 0 : warn( "execvp: %s", command_path.c_str() );
603 0 : sleep( 3 );
604 0 : exit( 1 );
605 : }
606 : } else {
607 : /* parent */
608 :
609 : /* Drop unnecessary privileges */
610 : #ifdef HAVE_PLEDGE
611 : /* OpenBSD pledge() syscall */
612 : if ( pledge( "stdio inet tty", NULL )) {
613 : perror( "pledge() failed" );
614 : exit( 1 );
615 : }
616 : #endif
617 :
618 : #ifdef HAVE_UTEMPTER
619 : /* make utmp entry */
620 : utempter_add_record( master, utmp_entry );
621 : #endif
622 :
623 452 : try {
624 452 : serve( master, terminal, *network, network_timeout, network_signaled_timeout );
625 0 : } catch ( const Network::NetworkException &e ) {
626 0 : fprintf( stderr, "Network exception: %s\n",
627 0 : e.what() );
628 0 : } catch ( const Crypto::CryptoException &e ) {
629 0 : fprintf( stderr, "Crypto exception: %s\n",
630 0 : e.what() );
631 0 : }
632 :
633 : #ifdef HAVE_UTEMPTER
634 : utempter_remove_record( master );
635 : #endif
636 :
637 452 : if ( close( master ) < 0 ) {
638 0 : perror( "close" );
639 0 : exit( 1 );
640 : }
641 : }
642 :
643 452 : fputs( "\n[mosh-server is exiting.]\n", stdout );
644 :
645 904 : return 0;
646 452 : }
647 :
648 452 : static void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network, long network_timeout, long network_signaled_timeout )
649 : {
650 : /* scale timeouts */
651 452 : const uint64_t network_timeout_ms = static_cast<uint64_t>( network_timeout ) * 1000;
652 452 : const uint64_t network_signaled_timeout_ms = static_cast<uint64_t>( network_signaled_timeout ) * 1000;
653 : /* prepare to poll for events */
654 452 : Select &sel = Select::get_instance();
655 452 : sel.add_signal( SIGTERM );
656 452 : sel.add_signal( SIGINT );
657 452 : sel.add_signal( SIGUSR1 );
658 :
659 452 : uint64_t last_remote_num = network.get_remote_state_num();
660 :
661 : #ifdef HAVE_UTEMPTER
662 : bool connected_utmp = false;
663 : #endif
664 : #if defined(HAVE_SYSLOG) || defined(HAVE_UTEMPTER)
665 : bool force_connection_change_evt = false;
666 : Addr saved_addr;
667 : socklen_t saved_addr_len = 0;
668 : #endif
669 :
670 : #ifdef HAVE_SYSLOG
671 : struct passwd *pw = getpwuid( getuid() );
672 : if (pw == NULL) {
673 : throw NetworkException( std::string( "serve: getpwuid: " ) + strerror( errno ), 0 );
674 : }
675 : syslog(LOG_INFO, "user %s session begin", pw->pw_name);
676 : #endif
677 :
678 452 : bool child_released = false;
679 :
680 27044 : while ( true ) {
681 27044 : try {
682 27044 : static const uint64_t timeout_if_no_client = 60000;
683 27044 : int timeout = INT_MAX;
684 27044 : uint64_t now = Network::timestamp();
685 :
686 27044 : timeout = std::min( timeout, network.wait_time() );
687 27044 : timeout = std::min( timeout, terminal.wait_time( now ) );
688 27044 : if ( (!network.get_remote_state_num())
689 27044 : || network.shutdown_in_progress() ) {
690 1927 : timeout = std::min( timeout, 5000 );
691 : }
692 : /*
693 : * The server goes completely asleep if it has no remote peer.
694 : * We may want to wake up sooner.
695 : */
696 27044 : if ( network_timeout_ms ) {
697 18 : int64_t network_sleep = network_timeout_ms -
698 18 : ( now - network.get_latest_remote_state().timestamp );
699 18 : if ( network_sleep < 0 ) {
700 : network_sleep = 0;
701 : } else if ( network_sleep > INT_MAX ) {
702 : /* 24 days might be too soon. That's OK. */
703 : network_sleep = INT_MAX;
704 : }
705 22 : timeout = std::min( timeout, static_cast<int>(network_sleep) );
706 : }
707 :
708 : /* poll for events */
709 27044 : sel.clear_fds();
710 27044 : std::vector< int > fd_list( network.fds() );
711 27044 : assert( fd_list.size() == 1 ); /* servers don't hop */
712 27044 : int network_fd = fd_list.back();
713 27044 : sel.add_fd( network_fd );
714 27044 : if ( !network.shutdown_in_progress() ) {
715 26021 : sel.add_fd( host_fd );
716 : }
717 :
718 27044 : int active_fds = sel.select( timeout );
719 27044 : if ( active_fds < 0 ) {
720 0 : perror( "select" );
721 : break;
722 : }
723 :
724 27044 : now = Network::timestamp();
725 27044 : uint64_t time_since_remote_state = now - network.get_latest_remote_state().timestamp;
726 27044 : string terminal_to_host;
727 :
728 27044 : if ( sel.read( network_fd ) ) {
729 : /* packet received from the network */
730 1952 : network.recv();
731 :
732 : /* is new user input available for the terminal? */
733 1952 : if ( network.get_remote_state_num() != last_remote_num ) {
734 1946 : last_remote_num = network.get_remote_state_num();
735 :
736 :
737 1946 : Network::UserStream us;
738 1946 : us.apply_string( network.get_remote_diff() );
739 : /* apply userstream to terminal */
740 3623 : for ( size_t i = 0; i < us.size(); i++ ) {
741 1677 : const Parser::Action &action = us.get_action( i );
742 1677 : if ( typeid( action ) == typeid( Parser::Resize ) ) {
743 : /* apply only the last consecutive Resize action */
744 496 : if ( i < us.size() - 1 ) {
745 186 : const Parser::Action &next = us.get_action( i + 1 );
746 186 : if ( typeid( next ) == typeid( Parser::Resize ) ) {
747 0 : continue;
748 : }
749 : }
750 : /* tell child process of resize */
751 496 : const Parser::Resize &res = static_cast<const Parser::Resize &>( action );
752 496 : struct winsize window_size;
753 496 : if ( ioctl( host_fd, TIOCGWINSZ, &window_size ) < 0 ) {
754 0 : perror( "ioctl TIOCGWINSZ" );
755 0 : network.start_shutdown();
756 : }
757 496 : window_size.ws_col = res.width;
758 496 : window_size.ws_row = res.height;
759 496 : if ( ioctl( host_fd, TIOCSWINSZ, &window_size ) < 0 ) {
760 0 : perror( "ioctl TIOCSWINSZ" );
761 496 : network.start_shutdown();
762 : }
763 : }
764 3354 : terminal_to_host += terminal.act( action );
765 : }
766 :
767 1946 : if ( !us.empty() ) {
768 : /* register input frame number for future echo ack */
769 958 : terminal.register_input_frame( last_remote_num, now );
770 : }
771 :
772 : /* update client with new state of terminal */
773 1946 : if ( !network.shutdown_in_progress() ) {
774 1331 : network.set_current_state( terminal );
775 : }
776 : #if defined(HAVE_SYSLOG) || defined(HAVE_UTEMPTER)
777 : #ifdef HAVE_UTEMPTER
778 : if (!connected_utmp) {
779 : force_connection_change_evt = true;
780 : } else {
781 : force_connection_change_evt = false;
782 : }
783 : #else
784 : force_connection_change_evt = false;
785 : #endif
786 :
787 : /**
788 : * - HAVE_UTEMPTER - update utmp entry if we have become "connected"
789 : * - HAVE_SYSLOG - log connection information to syslog
790 : **/
791 : if ( (force_connection_change_evt)
792 : || saved_addr_len != network.get_remote_addr_len()
793 : || memcmp( &saved_addr, &network.get_remote_addr(),
794 : saved_addr_len ) != 0 ) {
795 :
796 : saved_addr = network.get_remote_addr();
797 : saved_addr_len = network.get_remote_addr_len();
798 :
799 : char host[ NI_MAXHOST ];
800 : int errcode = getnameinfo( &saved_addr.sa, saved_addr_len,
801 : host, sizeof( host ), NULL, 0,
802 : NI_NUMERICHOST );
803 : if ( errcode != 0 ) {
804 : throw NetworkException( std::string( "serve: getnameinfo: " ) + gai_strerror( errcode ), 0 );
805 : }
806 :
807 : #ifdef HAVE_UTEMPTER
808 : utempter_remove_record( host_fd );
809 : char tmp[ 64 + NI_MAXHOST ];
810 : snprintf( tmp, 64 + NI_MAXHOST, "%s via mosh [%d]", host, getpid() );
811 : utempter_add_record( host_fd, tmp );
812 :
813 : connected_utmp = true;
814 : #endif
815 :
816 : #ifdef HAVE_SYSLOG
817 : syslog(LOG_INFO, "user %s connected from host: %s", pw->pw_name, host);
818 : #endif
819 : }
820 : #endif
821 :
822 : /* Tell child to start login session. */
823 1946 : if ( !child_released ) {
824 452 : if ( swrite( host_fd, "\n", 1 ) < 0) {
825 0 : err( 1, "child release" );
826 : }
827 : child_released = true;
828 : }
829 1946 : }
830 : }
831 :
832 27044 : if ( (!network.shutdown_in_progress()) && sel.read( host_fd ) ) {
833 : /* input from the host needs to be fed to the terminal */
834 21904 : const int buf_size = 16384;
835 21904 : char buf[ buf_size ];
836 :
837 : /* fill buffer if possible */
838 21904 : ssize_t bytes_read = read( host_fd, buf, buf_size );
839 :
840 : /* If the pty slave is closed, reading from the master can fail with
841 : EIO (see #264). So we treat errors on read() like EOF. */
842 21904 : if ( bytes_read <= 0 ) {
843 448 : network.start_shutdown();
844 : } else {
845 64368 : terminal_to_host += terminal.act( string( buf, bytes_read ) );
846 :
847 : /* update client with new state of terminal */
848 21904 : network.set_current_state( terminal );
849 : }
850 : }
851 :
852 : /* write user input and terminal writeback to the host */
853 27044 : if ( swrite( host_fd, terminal_to_host.c_str(), terminal_to_host.length() ) < 0 ) {
854 0 : network.start_shutdown();
855 : }
856 :
857 27044 : bool idle_shutdown = false;
858 27044 : if ( network_timeout_ms &&
859 27044 : network_timeout_ms <= time_since_remote_state ) {
860 4 : idle_shutdown = true;
861 27048 : fprintf( stderr, "Network idle for %llu seconds.\n",
862 4 : static_cast<unsigned long long>( time_since_remote_state / 1000 ) );
863 : }
864 27044 : if ( sel.signal( SIGUSR1 )
865 27044 : && ( !network_signaled_timeout_ms || network_signaled_timeout_ms <= time_since_remote_state ) ) {
866 2 : idle_shutdown = true;
867 27046 : fprintf( stderr, "Network idle for %llu seconds when SIGUSR1 received\n",
868 2 : static_cast<unsigned long long>( time_since_remote_state / 1000 ) );
869 : }
870 :
871 54088 : if ( sel.any_signal() || idle_shutdown ) {
872 : /* shutdown signal */
873 6 : if ( network.has_remote_addr() && (!network.shutdown_in_progress()) ) {
874 4 : network.start_shutdown();
875 : } else {
876 : break;
877 : }
878 : }
879 :
880 : /* quit if our shutdown has been acknowledged */
881 27042 : if ( network.shutdown_in_progress() && network.shutdown_acknowledged() ) {
882 : break;
883 : }
884 :
885 : /* quit after shutdown acknowledgement timeout */
886 27619 : if ( network.shutdown_in_progress() && network.shutdown_ack_timed_out() ) {
887 : break;
888 : }
889 :
890 : /* quit if we received and acknowledged a shutdown request */
891 26592 : if ( network.counterparty_shutdown_ack_sent() ) {
892 : break;
893 : }
894 :
895 : #ifdef HAVE_UTEMPTER
896 : /* update utmp if has been more than 30 seconds since heard from client */
897 : if ( connected_utmp
898 : && time_since_remote_state > 30000 ) {
899 : utempter_remove_record( host_fd );
900 :
901 : char tmp[ 64 ];
902 : snprintf( tmp, 64, "mosh [%d]", getpid() );
903 : utempter_add_record( host_fd, tmp );
904 :
905 : connected_utmp = false;
906 : }
907 : #endif
908 :
909 26592 : if ( terminal.set_echo_ack( now ) && !network.shutdown_in_progress() ) {
910 : /* update client with new echo ack */
911 366 : network.set_current_state( terminal );
912 : }
913 :
914 26592 : if ( !network.get_remote_state_num()
915 26592 : && time_since_remote_state >= timeout_if_no_client ) {
916 452 : fprintf( stderr, "No connection within %llu seconds.\n",
917 : static_cast<unsigned long long>( timeout_if_no_client / 1000 ) );
918 : break;
919 : }
920 :
921 26592 : network.tick();
922 53636 : } catch ( const Network::NetworkException &e ) {
923 0 : fprintf( stderr, "%s\n", e.what() );
924 0 : spin();
925 0 : } catch ( const Crypto::CryptoException &e ) {
926 0 : if ( e.fatal ) {
927 0 : throw;
928 : } else {
929 0 : fprintf( stderr, "Crypto exception: %s\n", e.what() );
930 : }
931 0 : }
932 : }
933 : #ifdef HAVE_SYSLOG
934 : syslog(LOG_INFO, "user %s session end", pw->pw_name);
935 : #endif
936 452 : }
937 :
938 : /* Print the motd from a given file, if available */
939 0 : static bool print_motd( const char *filename )
940 : {
941 0 : FILE *motd = fopen( filename, "r" );
942 0 : if ( !motd ) {
943 : return false;
944 : }
945 :
946 : const int BUFSIZE = 256;
947 :
948 0 : char buffer[ BUFSIZE ];
949 0 : while ( 1 ) {
950 0 : size_t bytes_read = fread( buffer, 1, BUFSIZE, motd );
951 0 : if ( bytes_read == 0 ) {
952 : break; /* don't report error */
953 : }
954 0 : size_t bytes_written = fwrite( buffer, 1, bytes_read, stdout );
955 0 : if ( bytes_written == 0 ) {
956 : break;
957 : }
958 : }
959 :
960 0 : fclose( motd );
961 : return true;
962 : }
963 :
964 452 : static void chdir_homedir( void )
965 : {
966 452 : const char *home = getenv( "HOME" );
967 452 : if ( home == NULL ) {
968 0 : struct passwd *pw = getpwuid( getuid() );
969 0 : if ( pw == NULL ) {
970 0 : perror( "getpwuid" );
971 0 : return; /* non-fatal */
972 : }
973 0 : home = pw->pw_dir;
974 : }
975 :
976 452 : if ( chdir( home ) < 0 ) {
977 0 : perror( "chdir" );
978 : }
979 :
980 452 : if ( setenv( "PWD", home, 1 ) < 0 ) {
981 0 : perror( "setenv" );
982 : }
983 : }
984 :
985 0 : static bool motd_hushed( void )
986 : {
987 : /* must be in home directory already */
988 0 : struct stat buf;
989 0 : return 0 == lstat( ".hushlogin", &buf );
990 : }
991 :
992 : #ifdef HAVE_UTMPX_H
993 0 : static bool device_exists( const char *ut_line )
994 : {
995 0 : string device_name = string( "/dev/" ) + string( ut_line );
996 0 : struct stat buf;
997 0 : return 0 == lstat( device_name.c_str(), &buf );
998 0 : }
999 : #endif
1000 :
1001 0 : static void warn_unattached( const string & ignore_entry )
1002 : {
1003 : #ifdef HAVE_UTMPX_H
1004 : /* get username */
1005 0 : const struct passwd *pw = getpwuid( getuid() );
1006 0 : if ( pw == NULL ) {
1007 0 : perror( "getpwuid" );
1008 : /* non-fatal */
1009 0 : return;
1010 : }
1011 :
1012 0 : const string username( pw->pw_name );
1013 :
1014 : /* look for unattached sessions */
1015 0 : vector< string > unattached_mosh_servers;
1016 :
1017 0 : while ( struct utmpx *entry = getutxent() ) {
1018 0 : if ( (entry->ut_type == USER_PROCESS)
1019 0 : && (username == string( entry->ut_user )) ) {
1020 : /* does line show unattached mosh session */
1021 0 : string text( entry->ut_host );
1022 0 : if ( (text.size() >= 5)
1023 0 : && (text.substr( 0, 5 ) == "mosh ")
1024 0 : && (text[ text.size() - 1 ] == ']')
1025 0 : && (text != ignore_entry)
1026 0 : && device_exists( entry->ut_line ) ) {
1027 0 : unattached_mosh_servers.push_back( text );
1028 : }
1029 0 : }
1030 : }
1031 :
1032 : /* print out warning if necessary */
1033 0 : if ( unattached_mosh_servers.empty() ) {
1034 0 : return;
1035 0 : } else if ( unattached_mosh_servers.size() == 1 ) {
1036 0 : printf( "\033[37;44mMosh: You have a detached Mosh session on this server (%s).\033[m\n\n",
1037 0 : unattached_mosh_servers.front().c_str() );
1038 : } else {
1039 0 : string pid_string;
1040 :
1041 0 : for ( vector< string >::const_iterator it = unattached_mosh_servers.begin();
1042 0 : it != unattached_mosh_servers.end();
1043 0 : it++ ) {
1044 0 : pid_string += " - " + *it + "\n";
1045 : }
1046 :
1047 0 : printf( "\033[37;44mMosh: You have %d detached Mosh sessions on this server, with PIDs:\n%s\033[m\n",
1048 0 : (int)unattached_mosh_servers.size(), pid_string.c_str() );
1049 0 : }
1050 : #endif /* HAVE_UTMPX_H */
1051 0 : }
|