#!/usr/bin/env perl # Mosh: the mobile shell # Copyright 2012 Keith Winstein # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . use warnings; use strict; use Socket; use IO::Pty; use Getopt::Long; $|=1; my $client = 'mosh-client'; my $server = 'mosh-server'; my $predict = undef; my $port_request = undef; my $usage = qq{Usage: $0 [options] [user@]host --client=PATH mosh client on local machine (default: "mosh-client") --server=PATH mosh server on remote machine (default: "mosh-server") --predict=adaptive local echo for slower links [default] -a --predict=always use local echo even on fast links -n --predict=never never use local echo -p NUM --port=NUM server-side UDP port Please report bugs to mosh-devel\@mit.edu. Mosh home page: http://mosh.mit.edu\n}; sub predict_check { my ( $predict, $env_set ) = @_; if ( not exists { adaptive => 0, always => 0, never => 0 }->{ $predict } ) { my $explanation = $env_set ? " (MOSH_PREDICTION_DISPLAY in environment)" : ""; print STDERR qq{$0: Unknown mode \"$predict\"$explanation.\n\n}; die $usage; } } GetOptions( 'client=s' => \$client, 'server=s' => \$server, 'predict=s' => \$predict, 'port=i' => \$port_request, 'a' => sub { $predict = 'always' }, 'n' => sub { $predict = 'never' }, 'p=i' => \$port_request, 'fake-proxy!' => \my $fake_proxy ) or die $usage; if ( defined $predict ) { predict_check( $predict, 0 ); } elsif ( defined $ENV{ 'MOSH_PREDICTION_DISPLAY' } ) { $predict = $ENV{ 'MOSH_PREDICTION_DISPLAY' }; predict_check( $predict, 1 ); } else { $predict = 'adaptive'; predict_check( $predict, 0 ); } if ( defined $port_request ) { if ( $port_request =~ m{^[0-9]+$} and $port_request >= 0 and $port_request <= 65535 ) { # good port } else { die "$0: Server-side port ($port_request) must be within valid range [0..65535].\n"; } } delete $ENV{ 'MOSH_PREDICTION_DISPLAY' }; if ( defined $fake_proxy ) { use Errno qw(EINTR); use IO::Socket::INET; use POSIX qw(_exit); my ( $host, $port ) = @ARGV; # Resolve hostname my $packed_ip = gethostbyname $host; if ( not defined $packed_ip ) { die "$0: Could not resolve hostname $host\n"; } my $ip = inet_ntoa $packed_ip; print STDERR "MOSH IP $ip\n"; # Act like netcat my $sock = IO::Socket::INET->new( PeerAddr => $ip, PeerPort => $port, Proto => "tcp" ) or die "$0: connect to host $ip port $port: $!\n"; sub cat { my ( $from, $to ) = @_; while ( my $n = $from->sysread( my $buf, 4096 ) ) { next if ( $n == -1 && $! == EINTR ); $n >= 0 or last; $to->write( $buf ) or last; } } defined( my $pid = fork ) or die "$0: fork: $!\n"; if ( $pid == 0 ) { cat $sock, \*STDOUT; $sock->shutdown( 0 ); _exit 0; } $SIG{ 'HUP' } = 'IGNORE'; cat \*STDIN, $sock; $sock->shutdown( 1 ); waitpid $pid, 0; exit; } if ( scalar @ARGV != 1 ) { die $usage; } my $userhost = $ARGV[ 0 ]; # Run SSH and read password my $pty = new IO::Pty; my $pty_slave = $pty->slave; $pty_slave->clone_winsize_from( \*STDIN ); my $pid = fork; die "$0: fork: $!\n" unless ( defined $pid ); if ( $pid == 0 ) { # child close $pty; open STDOUT, ">&", $pty_slave->fileno() or die; open STDERR, ">&", $pty_slave->fileno() or die; close $pty_slave; my $s; if ( defined $port_request ) { $s = q{sh -c 'MOSH_PORT=$1; shift; MOSH_IP=`set -- $SSH_CONNECTION; echo $3`; exec "$@" $MOSH_IP $MOSH_PORT' -- } . $port_request . ' ' . $server; } else { # retain compatibility with older server when no port requested $s = q{sh -c 'exec "$@" "`set -- $SSH_CONNECTION; echo $3`"' -- } . $server; } exec 'ssh', '-S', 'none', '-o', "ProxyCommand=$0 --fake-proxy -- %h %p", '-t', $userhost, '--', $s; die "Cannot exec ssh: $!\n"; } else { # server my ( $ip, $port, $key ); $pty->close_slave(); LINE: while ( <$pty> ) { chomp; if ( m{^MOSH IP } ) { ( $ip ) = m{^MOSH IP (\S+)\s*$} or die "Bad MOSH IP string: $_\n"; } elsif ( m{^MOSH CONNECT } ) { if ( ( $port, $key ) = m{^MOSH CONNECT (\d+?) ([A-Za-z0-9/+]{22})\s*$} ) { last LINE; } else { die "Bad MOSH CONNECT string: $_\n"; } } else { print "$_\n"; } } waitpid $pid, 0; if ( not defined $ip ) { die "$0: Did not find remote IP address (is SSH ProxyCommand disabled?).\n"; } if ( not defined $key or not defined $port ) { die "$0: Did not find mosh server startup message.\n"; } # Now start real mosh client $ENV{ 'MOSH_KEY' } = $key; $ENV{ 'MOSH_PREDICTION_DISPLAY' } = $predict; exec {$client} ($client, $ip, $port); }