Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

executable file 328 lines (269 sloc) 9.595 kb
#!/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 <http://www.gnu.org/licenses/>.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the
# OpenSSL library under certain conditions as described in each
# individual source file, and distribute linked combinations including
# the two.
#
# You must obey the GNU General Public License in all respects for all
# of the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the
# file(s), but you are not obligated to do so. If you do not wish to do
# so, delete this exception statement from your version. If you delete
# this exception statement from all source files in the program, then
# also delete it here.
my $MOSH_VERSION = '1.2.4a';
use warnings;
use strict;
use Getopt::Long;
use IO::Socket;
$|=1;
my $client = 'mosh-client';
my $server = 'mosh-server';
my $predict = undef;
my $bind_ip = undef;
my $family = 'inet';
my $port_request = undef;
my $ssh = 'ssh';
my $term_init = 1;
my $help = undef;
my $version = undef;
my @cmdline = @ARGV;
my $usage =
qq{Usage: $0 [options] [--] [user@]host [command...]
--client=PATH mosh client on local machine
(default: "mosh-client")
--server=COMMAND 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
--predict=experimental aggressively echo even when incorrect
-4 --family=inet use IPv4 only [default]
-6 --family=inet6 use IPv6 only
-p PORT[:PORT2]
--port=PORT[:PORT2] server-side UDP port or range
--bind-server={ssh|any|IP} ask the server to reply from an IP address
(default: "ssh")
--ssh=COMMAND ssh command to run when setting up session
(example: "ssh -p 2222")
(default: "ssh")
--no-init do not send terminal initialization string
--help this message
--version version and copyright information
Please report bugs to mosh-devel\@mit.edu.
Mosh home page: http://mosh.mit.edu\n};
my $version_message = qq{mosh $MOSH_VERSION
Copyright 2012 Keith Winstein <mosh-devel\@mit.edu>
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.\n};
sub predict_check {
my ( $predict, $env_set ) = @_;
if ( not exists { adaptive => 0, always => 0,
never => 0, experimental => 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=s' => \$port_request,
'a' => sub { $predict = 'always' },
'n' => sub { $predict = 'never' },
'family=s' => \$family,
'4' => sub { $family = 'inet' },
'6' => sub { $family = 'inet6' },
'p=s' => \$port_request,
'ssh=s' => \$ssh,
'init!' => \$term_init,
'help' => \$help,
'version' => \$version,
'fake-proxy!' => \my $fake_proxy,
'bind-server=s' => \$bind_ip) or die $usage;
die $usage if ( defined $help );
die $version_message if ( defined $version );
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{^(\d+)(:(\d+))?$} ) {
my ( $low, $clause, $high ) = ( $1, $2, $3 );
# good port or port-range
if ( $low <= 0 or $low > 65535 ) {
die "$0: Server-side (low) port ($low) must be within valid range [1..65535].\n";
}
if ( defined $high ) {
if ( $high <= 0 or $high > 65535 ) {
die "$0: Server-side high port ($high) must be within valid range [1..65535].\n";
}
if ( $low > $high ) {
die "$0: Server-side port range ($port_request): low port greater than high port.\n";
}
}
} else {
die "$0: Server-side port or range ($port_request) is not valid.\n";
}
}
delete $ENV{ 'MOSH_PREDICTION_DISPLAY' };
my @bind_arguments;
if ( not defined $bind_ip or $bind_ip =~ m{^ssh$}i ) {
push @bind_arguments, '-s';
} elsif ( $bind_ip =~ m{^any$}i ) {
# do nothing
} elsif ( $bind_ip =~ m{^[0-9\.]+$} ) {
push @bind_arguments, ('-i', "$bind_ip");
} else {
print STDERR qq{$0: Unknown server binding option: $bind_ip\n};
die $usage;
}
if ( defined $fake_proxy ) {
use Errno qw(EINTR);
BEGIN { eval { require IO::Socket::IP; IO::Socket::IP->import('-register'); }; }
use POSIX qw(_exit);
my ( $host, $port ) = @ARGV;
# Resolve hostname and connect
my $afstr = 'AF_' . uc( $family );
my $af = eval { IO::Socket->$afstr } or die "$0: Invalid family $family\n";
my $sock = IO::Socket->new( Domain => $af,
Family => $af,
PeerHost => $host,
PeerPort => $port,
Proto => "tcp" )
or die "$0: Could not connect to $host: $@\n";
print STDERR "MOSH IP ", $sock->peerhost, "\n";
# Act like netcat
binmode($sock);
binmode(STDIN);
binmode(STDOUT);
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 = shift;
my @command = @ARGV;
# Count colors
open COLORCOUNT, '-|', $client, ('-c') or die "Can't count colors: $!\n";
my $colors = "";
{
local $/ = undef;
$colors = <COLORCOUNT>;
}
close COLORCOUNT or die;
chomp $colors;
if ( (not defined $colors)
or $colors !~ m{^[0-9]+$}
or $colors < 0 ) {
$colors = 0;
}
my $pid = open(my $pipe, "-|");
die "$0: fork: $!\n" unless ( defined $pid );
if ( $pid == 0 ) { # child
open(STDERR, ">&STDOUT") or die;
my @server = ( 'new' );
push @server, ( '-c', $colors );
push @server, @bind_arguments;
if ( defined $port_request ) {
push @server, ( '-p', $port_request );
}
for ( &locale_vars ) {
push @server, ( '-l', $_ );
}
if ( scalar @command > 0 ) {
push @server, '--', @command;
}
my $quoted_self = shell_quote( $0, "--family=$family" );
exec "$ssh " . shell_quote( '-S', 'none', '-o', "ProxyCommand=$quoted_self --fake-proxy -- %h %p", '-n', '-tt', $userhost, '--', "$server " . shell_quote( @server ) );
die "Cannot exec ssh: $!\n";
} else { # parent
my ( $ip, $port, $key );
my $bad_udp_port_warning = 0;
LINE: while ( <$pipe> ) {
chomp;
if ( m{^MOSH IP } ) {
if ( defined $ip ) {
die "$0 error: detected attempt to redefine MOSH IP.\n";
}
( $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 {
if ( defined $port_request and $port_request =~ m{:} and m{Bad UDP port} ) {
$bad_udp_port_warning = 1;
}
print "$_\n";
}
}
waitpid $pid, 0;
close $pipe;
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 ) {
if ( $bad_udp_port_warning ) {
die "$0: Server does not support UDP port range option.\n";
}
die "$0: Did not find mosh server startup message.\n";
}
# Now start real mosh client
$ENV{ 'MOSH_KEY' } = $key;
$ENV{ 'MOSH_PREDICTION_DISPLAY' } = $predict;
$ENV{ 'MOSH_NO_TERM_INIT' } = '1' if !$term_init;
exec {$client} ("$client @cmdline |", $ip, $port);
}
sub shell_quote { join ' ', map {(my $a = $_) =~ s/'/'\\''/g; "'$a'"} @_ }
sub locale_vars {
my @names = qw[LANG LANGUAGE LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION LC_ALL];
my @assignments;
for ( @names ) {
if ( defined $ENV{ $_ } ) {
push @assignments, $_ . q{=} . $ENV{ $_ };
}
}
return @assignments;
}
Jump to Line
Something went wrong with that request. Please try again.