#!/usr/bin/perl -w # $myversion = 'ssh-with-2fa V7.3 22 Apr 24'; # # Current comments and instructions are in # http://www.maths.usyd.edu.au/u/psz/ssh-howto.html # # Helper program for ssh connection through Maths firewall: with 2FA # (skey or TOTP) authentication when coming in (and was through proxy # when going out). # # There is no NEED for this script, but you may still want to use it # to do things "right" e.g. choose sensible options, use short names # (e.g. @enna instead of @maths.usyd.edu.au), and avoid typing skeys # (or passwords) too often. # # Boring perl code (and developer comments) only below. # You probably should not change anything below # (but please fix, and/or report, any bugs or problems). ########## # Version history # ssh-with-2fa V7.3 13 Mar 24 With VcXsrv (or Xming) # ssh-with-2fa V7.2 4 Jan 24 Incoming ssk to enna (not dora), POP to rome unused # ssh-with-2fa V7.1 21 Jan 23 Handle ssh from dora to rome (or other internal server) # ssh-with-2fa V7.0 11 Nov 22 Better multi-user detection # ssh-with-2fa V6.9 20 Sep 22 Handle Intune PCs with weird hostname # ssh-with-2fa V6.8 13 May 21 Using nxagent instead of xnest # ssh-with-2fa V6.7 6 May 21 Using plink with xpra on Windows # ssh-with-2fa V6.6 19 Jan 21 With xpra support # ssh-with-2fa V6.5 15 Jan 21 With xsess (LDM Xsession) support # ssh-with-2fa V6.4 27 Sep 19 Quirks for Windows10 ssh, better Xming options # ssh-with-2fa V6.3 31 Jan 19 Better check for Xming location # ssh-with-2fa V6.2 21 Aug 18 Use Windows10 ssh if present # ssh-with-2fa V6.1 12 Aug 18 Check if Xming is running # ssh-with-2fa V6.0 5 Aug 18 Accept rsync or unison as ssh (remote) command # ssh-with-2fa V5.7 4 Aug 18 With quiet option (automatic for rsync) # ssh-with-2fa V5.6 6 Nov 17 Renamed. Fix netstat on Mac, tidy up code # ssh-with-skey V5.5 16 Dec 16 With XauthLocation for Mac Sierra # ssh-with-skey V5.4 30 Nov 15 No more telnet or cconnect # ssh-with-skey V5.3 23 Nov 15 Make use of existing 12022 forwarding # ssh-with-skey V5.2 22 Nov 15 Using dynamic forwarding # ssh-with-skey V5.1 20 Dec 13 Have transparent proxy (tproxy) on siv # ssh-with-skey V5.0 20 Jul 13 Warn about XQuartz on Macs # ssh-with-skey V4.8 26 Nov 12 With ForwardX11Timeout=596h on Macs # ssh-with-skey V4.7 12 Jun 12 With port forwarding for IMAP # ssh-with-skey V4.6 19 Oct 10 With port forwarding for POP and SMTP # ssh-with-skey V4.5 4 Jun 10 Use -C for compression # ssh-with-skey V4.4 23 Feb 10 Better ifconfig IP address detection # ssh-with-skey V4.3 11 Nov 08 Better IP address detection # ssh-with-skey V4.2 3 May 08 Better multi-user detection # ssh-with-skey V4.1 31 Mar 07 Better argument handling for sshout # ssh-with-skey V4.0 29 Mar 07 Do sshout (scpout sftpout), cconnect also # ssh-with-skey V3.1 19 Mar 07 Comment out unused code, drop deprecated # ssh-with-skey V3.0 14 Mar 07 No more skey stuff, all done within sskd # ssh-with-skey V2.1 18 Jan 07 Handle Mac (darwin) also # ssh-with-skey V2.0 25 Sep 06 Do outgoing also; do random local port # ssh-with-skey V1.6 16 Aug 06 With check for Maths machines # ssh-with-skey V1.5 6 May 06 Better show errors, option for local port # ssh-with-skey V1.4 5 May 06 Option -k for "no Tk", better defaults # ssh-with-skey V1.3 4 May 06 Allow command-line skey chat # ssh-with-skey V1.2 3 May 06 Cygwin compatibility won't work # ssh-with-skey V1.1 25 Apr 06 Neater defaults, better comments # ssh-with-skey V1.0 21 Apr 06 Original coding (from pirated parts) ########## ( $cmd = $0 ) =~ s!.*/!!; $sshname = 'ssh'; foreach (qw(scp sftp sshfs)) { $sshname = $_, last if $cmd =~ m/$_/; } $cmdname = $sshname; if ($sshname eq 'ssh') { if ($cmd =~ m/pra.*(sess|ldm)/i) { $cmdname = 'xprasess'; $pra = 1; $ses = 1; } elsif ($cmd =~ m/xpra/i) { $cmdname = 'xpraterm'; $pra = 1; } elsif ($cmd =~ m/xsess|ldm/i) { $cmdname = 'xsess'; $ses = 1; } } $usage = " USAGE: Command line options depend on command name used: $0 --> $cmdname Actions depend also on location of client: internal or external to Maths.\n ssh [--quiet] [--debug] [ssh_options] [user\@]host[:port] [remcmd] scp [--quiet] [--debug] [scp_options] file1 file2 sftp [--quiet] [--debug] [sftp_options] [user\@]host[:port] xsess [--quiet] [--debug] [-full-X-Y-size] [ssh_options] [user\@]host[:port] xpraterm [--quiet] [--debug] [ssh_options] [user\@]host[:port] xprasess [--quiet] [--debug] [-full-X-Y-size] [ssh_options] [user\@]host[:port] --quiet quiet (no chatty messages) --debug debug (verbose messages) cmd_options any (except user, host or port) options that ssh/putty, scp/pscp, or sftp may take; not checked or sanitized in any way; some options may clash with our usage. user username for authentication. host server host to connect to. For incoming, use any of host names enna, dora, rome, or ssh (or with maths.usyd.edu.au). port server port to connect to, default 22. remcmd remote command for ssh, must be rsync or unison. file1 or file2 must be in 'quasi scp syntax' [user\@]host[:port]:path so user, host and port will be taken from last one matched; any number of file arguments are permitted and will be modified, the last two only are checked. -full-X-Y-size nxagent size to be full-screen minus specified pixels.\n Notes: sftp is supported in interactive mode only, use scp for others. xsess emulates a Y-term terminal login, runs nxagent and LDM Xsession. xpraterm runs an xterm, xprasess does like xsess, via xpra. sshfs handling is not implemented yet.\n "; sub debug { $debug and print join(' ',"$0 $$ at", scalar localtime, ":", @_), "\n" } while (@ARGV and defined $ARGV[0] and $ARGV[0]) { $debug = 1, shift, next if $ARGV[0] eq '--debug' or $ARGV[0] eq '--verbose'; $quiet = 1, shift, next if $ARGV[0] eq '--quiet'; #$noTk = 1, shift, next if $ARGV[0] eq '--noTk'; # Ignored, for backwards compatibility only last; } debug "Version: $myversion"; debug "Command and args:", $0, @ARGV; debug "Doing $cmdname ($sshname)"; @ORIGARGV = @ARGV; # Half-baked argument parsing: accept rsync or unison as remcmd @CMDARGV = (); if ($cmdname eq 'ssh') { @TMPARGV = (); while (@ARGV) { if (defined $ARGV[0] and $ARGV[0]) { if ($ARGV[0] =~ m/^(rsync|unison)($| )/) { @CMDARGV = @ARGV; $quiet = 1; # Should be quiet if called from rsync (or unison?) last; } } push @TMPARGV, shift; } @ARGV = @TMPARGV; } sub go_out { my ( $msg ) = @_; debug "Doing $cmdname out (reason=$msg)"; $msg = "\n$msg\n$usage\n" if $msg; $cmdname =~ m/^(ssh|scp|sftp)$/ or die( $msg || "Cannot do out for $cmdname\n" ); -e "/usr/bin/$sshname" or die( $msg || "There is no /usr/bin/$sshname\n" ); # With transparent proxy (tproxy), we can just do the "real thing", # no need for any ProxyCommand. debug "Running $sshname", @ORIGARGV; # Windows10 (or its ssh.exe?) is weird: if we use exec then ssh will run # "in the background", dissociated from keyboard input. But this is never # for Windows, so can use exec exec "/usr/bin/$sshname", @ORIGARGV; die "Exec $sshname failed ($!)\n"; } # Doing sshout is trivial, what is difficult is to decide when we need that: # do if asked, or if behind tsocks or proxychains $cmdname =~ m/^(ssh|scp|sftp)$/ and $cmd =~ m/out$/ and go_out('asked to do'); $cmdname =~ m/^(ssh|scp|sftp)$/ and ( $ENV{'LD_PRELOAD'} && $ENV{'LD_PRELOAD'} =~ m/lib(tsocks|proxychains)\.so/ ) and go_out('behind tsocks or proxychains'); die "$usage\n" unless @ARGV; ##### Find my own IP address, see whether we need skeys # (in fact needed to determine what command-line options to take) # Three ways of finding own IP address. Do easiest, fastest first... # In code below we use constructs like `cmd 2>&1`, so perl would not say # Can't exec "cmd": No such file or directory # 'cmd' is not recognized as an internal or external command, ... # and do not use `cmd 2>&-` because Windows does not recognize that. # In fact use (`cmd 2>&-` || '') to avoid "uninitialized value". undef $cli_addr; # $cli_addr or eval { if ($^O eq 'MSWin32') { @ifconfig = ('ipconfig /all'); } else { @ifconfig = ('/sbin/ifconfig -a', 'ifconfig -a', 'ip a', 'hostname -I'); } foreach $try (@ifconfig) { debug "Running $try"; $x = `$try 2>&1`; next unless $x; while ($x =~ m/\b(\d+\.\d+\.\d+\.\d+)\b/g) { $cli_addr = $1; debug "$try shows $cli_addr"; # Skip things that look like localhost or netmask... if (not $cli_addr or $cli_addr =~ m/^(127\.0|255)/ ) { undef $cli_addr; } last if $cli_addr; } last if $cli_addr; } }; # not $cli_addr and ($^O eq 'linux' or $^O eq 'darwin') and die "\n Cannot find my own IP address: are you connected to a network??!! Or maybe ... you do not have ifconfig installed on your machine (and things will not work well without it...).\n For Ubuntu Linux, please install the net-tools package: use command sudo apt-get install net-tools\n (I do not know how you get ifconfig on other Linux distros.) \n\n"; # $cli_addr or eval { # Avoid "Can't locate ... compilation aborted" with eval within eval debug "Using Sys::Hostname"; eval "use Sys::Hostname;"; $@ and die "No Sys::Hostname on this machine\n"; debug "Looking up hostname"; $chost = hostname(); debug "hostname() returned $chost"; # Bug with some Macs (e.g. jmg visitor of wm), slow to look up "own" name # (but fast for any others), maybe: # http://www.macosxhints.com/article.php?story=20021212074234953 # http://the.taoofmac.com/space/com/Apple/OSX/DNS%20and%20.local $cli_addr = join(".", unpack("C4", scalar gethostbyname($chost) || '')); debug "Hostname $chost shows IP address $cli_addr"; # Sometimes (often?) hostname shows nothing, or shows localhost # Skip things that look like localhost or netmask... if (not $cli_addr or $cli_addr =~ m/^(127\.0|255)/ ) { undef $cli_addr; } }, ($@ and chomp($@), debug $@); # $cli_addr or eval { debug "Using Sys::HostIP"; eval "use Sys::HostIP;"; $@ and die "No Sys::HostIP on this machine\n"; debug "Looking up HostIP"; $cli_addr = Sys::HostIP->ip; debug "HostIP shows IP address $cli_addr"; # Skip things that look like localhost or netmask... if (not $cli_addr or $cli_addr =~ m/^(127\.0|255)/ ) { undef $cli_addr; } }, ($@ and chomp($@), debug $@); $cli_addr or $cli_addr = ''; if ($cli_addr =~ m/^129\.78\.(69|94|95|233)\.\d+$/) { $cli_loc = 'internal'; # Find our name now. See elsewhere some comments about weird results we may get use Socket; $cli_name = gethostbyaddr( inet_aton( $cli_addr ), AF_INET ); debug "gethostbyaddr shows name $cli_name"; } elsif ($cli_addr =~ m/^129\.78\.68\.\d+$/) { $cli_loc = 'externalMaths'; } else { # elsif ($cli_addr) { $cli_loc = 'external'; } debug "Client location $cli_loc for IP address $cli_addr"; ##### Find options, settings @TMPARGV = @ARGV; if ($cmdname eq 'scp') { foreach (1..2) { $try = pop @TMPARGV || ''; ( $user, $shost, $sport ) = $try =~ m/^(?:([\w-]+)\@)?(\w[\w\.-]+)(?::(\d+))?:/; last if $shost; } $shost or go_out("No host specification found in last two arguments"); } else { $try = pop @TMPARGV || ''; ( $user, $shost, $sport ) = $try =~ m/^(?:([\w-]+)\@)?(\w[\w\.-]+)(?::(\d+))?$/; $shost and pop; # Only pop on success: do not remove a -X option $ses and defined $ARGV[0] and $ARGV[0] and $ARGV[0] =~ m/^-full-(\d+)-(\d+)-size$/ and $nxsize = shift; #($ses or $pra) and ( @ARGV or $CMDARGV ) and die "Un-acceptable $cmdname option(s)\n$usage\n"; } $shost or go_out("No hostname"); if ($shost =~ m/^(pisa|asti|bari|como|dora|enna|fano|rome|ssh|maths|p639.pc)?\.?(maths\.usyd\.edu\.au)?$/) { $x = $shost; $shost = 'enna' if not ($cli_name and $cli_name =~ m/^(dora|enna|fano)(\.maths\.usyd\.edu\.au)?$/); $shost = 'enna' if $ses and not ($shost =~ m/^(dora|enna|fano|p639.pc)(\.maths\.usyd\.edu\.au)?$/); $shost = 'ssh.maths.usyd.edu.au' if $cli_loc =~ m/external/; $shost eq $x or debug "Server host was $x, going to $shost instead"; $sport = 22; $svr_addr = '(no need to know)'; $svr_loc = 'internal'; } else { $ses and die "$cmdname must go to enna\n"; $shost or go_out("No hostname"); $shost =~ m/^\d*$/ and go_out("Bad hostname $shost"); $svr_loc = 'unknown'; $svr_addr = join(".", unpack("C4", scalar gethostbyname($shost) || '')); if ($svr_addr =~ m/^129\.78\.(69|94|95|233)\.\d+$/) { $svr_loc = 'internal'; } elsif ($svr_addr =~ m/^127\.0/) { $svr_loc = $cli_loc; } elsif ($svr_addr) { $svr_loc = 'external'; } else { go_out("No IP for host $shost"); } } debug "Server location $svr_loc for host $shost and IP address $svr_addr"; $sport ||= 22; if ($^O eq 'MSWin32') { # Windows: guess this is ActivePerl unless ($sshcmd) { if ($pra) { # xpra on Windows10 does NOT work with OpenSSH, # see https://github.com/Xpra-org/xpra/issues/3113 if (-f "C:/Program Files/Xpra/plink.exe") { # Have xpra's own plink, xpra can find it without need for PATH # Would like options "plink -ssh -agent ..." but those two seem default $sshcmd = 'plink'; debug "Using xpra's own C:/Program Files/Xpra/plink.exe"; } } else { # Check for ssh things present in Windows10, # but beware that xpra does not work with OpenSSH ##### Oddity: Sanjana's Win11 laptop does not find ssh.exe here (though the file is in fact present)??!! ##### Maybe the ssh client needs to be "enabled" on Win11, see: ##### https://geekrewind.com/how-to-install-openssh-client-in-windows-11/ $x = "C:/WINDOWS/System32/OpenSSH/$sshname.exe"; if (-f $x) { $sshcmd = $x; # Work-around for Windows10 oddity, see # https://github.com/PowerShell/Win32-OpenSSH/issues/1088 mkdir '/dev'; open F,'>/dev/tty' and print F "Windows10 ssh oddity, see:\r\nhttps://github.com/PowerShell/Win32-OpenSSH/issues/1088\r\n" and close F; } elsif ( $sshname =~ m/^(ssh|scp|sftp)$/ and (`ver 2>&1` || '') =~ m/Windows.*Version\s*1\d\b/ ) { $quiet or print "\nNo OpenSSH on this PC, Win10 or later?\nYou may want to enable:\nSettings > Apps > Optional Features > OpenSSH Client > Install\nNo matter, will look for putty, instead...\n\n"; } } } unless ($sshcmd) { # Look for putty and friends: they have funny names $exe = $sshname; if ($sshname eq 'ssh') { $exe = 'putty'; if ($pra or $ses) { # For xpra we probably had its own plink, already debug "Need plink instead of putty"; $exe = 'plink'; } } elsif ($sshname eq 'scp') { $exe = 'pscp'; } elsif ($sshname eq 'sftp') { $exe = 'psftp'; } elsif ($sshname eq 'sshfs') { die "Sorry no sshfs for Windows - see https://code.google.com/p/win-sshfs/ issues\n"; } # Look for exe: may have it installed in bizarre places... # Take care not to find ourselves, though unlikely we would be anything.exe foreach $d ( split(/;/,$ENV{PATH}), '.', 'Downloads', glob('*Doc*/Downloads'), 'C:', glob('C:/*'), glob('C:/Program*Files*/*'), glob('C:/WINDOWS*/system*') ) { $x = "$d/$exe.exe"; $sshcmd = $x, last if $x and $x ne $0 and -f $x; } $sshcmd or die "Cannot find $exe anywhere, tried in:\n PATH=$ENV{PATH}\n '.' Downloads *Doc*/Downloads\n C: C:/* C:/Program*Files*/* C:/WINDOWS*/system*\nPlease install $exe somewhere it can be found.\n"; $sshcmd =~ s:\\:/:g; $sshcmd =~ s://+:/:g; if ($pra and ( $d, $x ) = $sshcmd =~ m:^(.*)/([^/]*)$: and $d and $x) { # (Unlikely this would be needed, surely have xpra's own plink ...) # Avoid fancy quoting: add directory to the PATH, for when directory has # funny characters e.g. "Program Files" or "Program Files (x86)" $ENV{PATH} =~ m/(^|;)\Q$d\E(;|$)/ or $ENV{PATH} = "$d;$ENV{PATH}"; $sshcmd = $x; debug "Added $d to PATH, for the sake of $x"; } } } elsif ($^O eq 'cygwin') { #print "Perl/Tk does not work with Cygwin, using command-line interface ...\n"; #$noTk = 1; # Cygwin on Windows: we assume ssh in cygwin unless ($sshcmd) { # Look for ssh.exe (make sure we have it) # Take care not to find ourselves, though unlikely we would be anything.exe foreach $d ( "/bin", "/usr/bin", split(/:/,$ENV{PATH}) ) { $x = "$d/$sshname.exe"; $sshcmd = $x, last if $x and $x ne $0 and -f $x and -x _; } $sshcmd or die "Cannot find $sshname.exe in /bin /usr/bin or PATH=$ENV{PATH}\nPlease install OpenSSH package.\n"; } } elsif ($^O eq 'linux' or $^O eq 'darwin') { # Linux (or MacOSX: is that similar enough?): we assume ssh unless ($sshcmd) { # Look for ssh (make sure we have it) # Take care not to find ourselves ... foreach $d ( "/usr/bin", split(/:/,$ENV{PATH}) ) { next if $d =~ m!^/usr/sms/bin/?$!; $x = "$d/$sshname"; $sshcmd = $x, last if $x and $x ne $0 and -f $x and -x _; } $sshcmd or die "Cannot find $sshname in /usr/bin or PATH=$ENV{PATH}\nPlease install $sshname somewhere it can be found.\n"; } } else { die "Un-recognized operating system.\nCould handle Windows, Linux, MacOSX or Cygwin,\nbut \$^O shows $^O.\n"; } ##### Type of proxy $proxy = ''; if ($cli_loc eq 'internal' and $svr_loc eq 'internal') { # Fully internal, can go direct # Is this a laptop, or a Windows PC in MCS domain, connecting to enna? if ($shost eq 'enna' and $sport == 22) { #####zzzzz Should not we do this simply by $cli_addr IP address?! #####zzzzz But then, how would we know when to have FQDN enna.maths? # We looked up $cli_name already. Weird results we may get: # MCS machines have non-Maths DNS and default domain, show *.mcs.usyd.edu.au # Intune PCs have Maths DNS and default domain, but wrong hostname Wxxx (with Dell service tag), show Wxxx.maths.usyd.edu.au (?!) # We have laptops with "wrong" names: p630x.pc p714x.pc p714y.pc if ($cli_name =~ m/^((([a-z][a-z]\w+|p\d+\w+)\.pc|W\w+)\.maths\.usyd\.edu\.au|\w+\.mcs\.usyd\.edu\.au)$/) { $shost = 'enna.maths.usyd.edu.au'; # Not plain enna, for the sake of MCS PCs (with non-Maths DNS and default domain) # On laptops, we may want IMAP POP SMTP and CUPS port forwarding $proxy = 'laptop-to-enna' if $sshname eq 'ssh'; } } } elsif ($cli_loc =~ m/external/ and $svr_loc eq 'external') { # Fully external, can go direct } elsif ($cli_loc eq 'externalMaths' and $svr_loc eq 'internal') { # Do not require skeys } elsif ($cli_loc eq 'external' and $svr_loc eq 'internal') { # PSz 14 Mar 07 sskd used PAM module, no need for proxy at all, # but leave it marked so we can add port forwarding if useful. $proxy = 'skey'; } elsif ($cli_loc eq 'internal' and $svr_loc eq 'external') { $proxy = 'tproxy'; # Used to be cproxy } else { die "Logic error: cannot figure out proxy type\nwhen going from $cli_loc to $svr_loc.\n"; } debug "Proxy type is " . ($proxy || "(empty)"); ##### Build command line: original options, and ours sub sw_vers_since # Helper for Mac: we want '10.10' ge '10.7' { my ($o) = @_; my $n = `sw_vers -productVersion 2>&1` || ''; $n =~ s/(\d+)/sprintf "%04d",$1/eg; $o =~ s/(\d+)/sprintf "%04d",$1/eg; return $n ge $o; } if ($sshname eq 'ssh') { unless ($quiet) { if ($^O eq 'linux' or $^O eq 'darwin') { $x = getppid(); $x = `ps -p $x 2>&1` || ''; ## Should be quiet if called from rsync (or unison?), or ## expect that does not seem to handle long "Using command ..." lines. #$x =~ m/rsync|unison|expect/ and $quiet = 1; # Should be quiet if called from anything but a shell $x =~ m/\d -?\w{0,4}sh\r?\n?$/ or $quiet = 1; } } unless ( $pra or $ENV{DISPLAY} ) { # Seems no X-windows available if ( $^O eq 'MSWin32' ) { # Do not seem to get a DISPLAY, not even with VcXsrv or Xming running... unless ( (`tasklist 2>&1` || '') =~ m/Xming|vcxsrv/ ) { foreach $d ( split(/;/,$ENV{PATH}), '.', 'Downloads', glob('*Doc*/Downloads'), 'C:', glob('C:/*'), glob('C:/Program*Files*/*'), glob('C:/WINDOWS*/system*') ) { $x = "$d/vcxsrv.exe"; $xming = $x, last if $x and -f $x; $x = "$d/VcXsrv/vcxsrv.exe"; $xming = $x, last if $x and -f $x; $x = "$d/Xming.exe"; $xming = $x, last if $x and -f $x; $x = "$d/Xming/Xming.exe"; $xming = $x, last if $x and -f $x; } if ( $xming ) { $xming =~ s:\\:/:g; $xming =~ s://+:/:g; $quiet or print "Starting $xming (iconized) ...\n"; system("start \"\" /MIN \"$xming\" -clipboard -multiwindow"); sleep 2; # Give it time to start (maybe no need, login might take longer) } else { $quiet or print "\nNo X-windows, should install VcXsrv from\nhttps://github.com/marchaesen/vcxsrv/releases\n\n"; #$quiet or print "\nNo X-windows, should install Xming and fonts from\nhttp://sourceforge.net/projects/xming/files/\n\n"; $ses and not $pra and die "$cmdname needs X-windows\n"; } } if ($sshcmd =~ m/WINDOWS.*OpenSSH/ ) { # Work-around for Windows10 oddities, see # https://unix.stackexchange.com/questions/207365/x-flag-x11-forwarding-does-not-appear-to-work-in-windows/ # Setting things in the hope we have VcXsrv or Xming, harmless(?) if not print "Doing set DISPLAY=localhost:0\n"; $ENV{DISPLAY} = 'localhost:0'; unshift @ARGV,'-Y' unless grep m/^-[XY]$/, @ARGV; } } elsif ( $^O eq 'darwin' and sw_vers_since('10.8') ) { $quiet or print "\nNo X-windows, should install XQuartz from http://www.xquartz.org\n\n"; $ses and not $pra and die "$cmdname needs X-windows\n"; } else { $quiet or print "\nNo X-windows??\n\n"; $ses and not $pra and die "$cmdname needs X-windows\n"; } } $sshopt = ''; $sshopt .= " " . join(' ', @ARGV) if @ARGV; $sshopt .= " -X" unless $pra or $sshopt =~ m/ -[XY]( |$)/; ### Do we need -Y for cygwin? $sshopt .= " -C" unless $pra or $sshopt =~ m/ -C( |$)/; $sshopt .= " -oForwardX11Timeout=596h" if $^O eq 'darwin' and sw_vers_since('10.7'); # Mac bug/feature, see RT#5533 RT#5655 # The ForwardX11Timeout option is only available from MacOSX 10.7, # causes ssh to fail on 10.6.* or earlier, compare: # https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/10.6/man5/ssh_config.5.html # https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/10.7/man5/ssh_config.5.html # https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/ssh_config.5.html # The ForwardX11Timeout option is needed still at MacOSX 10.13.4 . $sshopt .= " -oXauthLocation=/opt/X11/bin/xauth" if $^O eq 'darwin' and -f '/opt/X11/bin/xauth'; # Mac bug, see e.g. http://stackoverflow.com/questions/39622173/cant-run-ssh-x-on-macos-sierra # The XauthLocation option needed since MacOSX 10.12, # may not be needed at MacOSX 10.13.4, # but is needed again at MacOSX 10.13.6 and later. # Does not seem necessary, not even on hotel (wireless?) networks #$sshopt .= " -oServerAliveInterval=10" if $proxy eq 'skey' and $sshcmd !~ m/putty|plink/; if ( $proxy eq 'skey' or $proxy eq 'laptop-to-enna' ) { # For skey: do port forwarding for scp so as not to need skeys each time, # and for IMAP POP SMTP CUPS. # For laptop: do port forwarding for IMAP POP SMTP CUPS, # others for symmetry. $x = 0; if ($^O eq 'linux') { # Use FIRST_UID or LAST_SYSTEM_UID? ( $y ) = ( (`grep FIRST_UID= /etc/adduser.conf 2>&1` || '') =~ m/=(\d+)/ ); $y ||= 100; ( $z ) = ( (`grep LAST_UID= /etc/adduser.conf 2>&1` || '') =~ m/=(\d+)/ ); $z ||= 65533; # Primitive count of passwd file lines while ( @x = getpwent() ) { $x++ if $x[2] >= $y and $x[2] <= $z; } } debug "No port forwarding on multi-user machine" if $x > 5; # No POP to rome anymore (was unused anyway) #$sshopt .= " -D 13080 -L 12022:localhost:22 -L 12143:enna:143 -L 12110:rome:110 -L 12025:rome:25 -L 12631:siv:631" unless $x > 5; $sshopt .= " -D 13080 -L 12022:localhost:22 -L 12143:enna:143 -L 12025:rome:25 -L 12631:siv:631" unless $x > 5; } unless ($pra) { $sshopt .= " -l $user" if $user; if ($sshcmd =~ m/putty|plink/) { $sshopt .= " -P PORT-GOES-HERE"; } else { $sshopt .= " -p PORT-GOES-HERE"; } $sshopt .= " HOST-GOES-HERE"; } if ($ses) { # Use "nxagent -run Xsession" not $nxsize and $^O eq 'MSWin32' and $nxsize = '-full-0-72-size'; not $nxsize and $^O eq 'darwin' and $nxsize = '-full-0-106-size'; not $nxsize and $^O eq 'linux' and $nxsize = '-full-72-80-size'; # For most Ubuntu layouts not $pra and $^O eq 'darwin' and print "\n\n BEWARE\n XQuartz (and your session) may quit and die ...\n (bug in XQuartz ??!)\n\n"; @CMDARGV = split(' ',"/usr/sms/bin/nxagent $nxsize -run /usr/sms/share/ldm/Xsession"); # Might like to use "ssh -M ..." etc as LDM does. But would not work: # - Windows ssh does NOT support the -M (ControlMaster) option, fails # with message "getsockname failed: Not a socket", see # https://github.com/PowerShell/Win32-OpenSSH/issues/405 # https://github.com/PowerShell/Win32-OpenSSH/issues/1328 # noting possible workaround with "ssh -O proxy" in # https://github.com/PowerShell/Win32-OpenSSH/issues/405#issuecomment-1481385347 # - On Mac have too many window managers: # - gnome - quits instantly (another window manager? no acceleration?) # - fluxbox - complains about another window manager, and quits # - xfce - works almost (but cannot type in terminal window?) # - SMSsession - session manager window cannot be made visible # - On Linux also have too many window managers, anyway probably # un-wanted as "panel"s would be on top of (overwriting) each other. # This would be for "ssh -M ...": ## We really would like to have: ##$ldmsock = ( $ENV{HOME} || $ENV{USERPROFILE} ) . '/.ldmsock'; ##$ldmsock =~ s:\\:/:g; ## but HOME or USERPROFILE may have blanks in them... #if ($^O eq 'MSWin32') { # chdir $ENV{USERPROFILE}; # $debug and $x = `cd`, chomp $x; # debug "Changed directory to USERPROFILE = $x"; #} else { # chdir; # to HOME # $debug and $x = `pwd`, chomp $x; # debug "Changed directory to HOME = $x"; #} #$ldmsock = '.ldmsock'; ##unlink $ldmsock; #-e $ldmsock and die "Object $ldmsock exists, please remove\n"; #$sshopt .= " -T -M -S $ldmsock -o ControlMaster=yes -o ControlPersist=20 -o ConnectTimeout=5"; #@CMDARGV = qw(echo LDM-is-OK); } } elsif ($sshname eq 'scp') { $sshopt = " -P PORT-GOES-HERE"; $sshopt .= " " . join(' ', @ARGV); $sshopt =~ s/ (?:([\w-]+)\@)?(\w[\w\.-]+)(?::(\d+))?:/ HOST-GOES-HERE:/g; $sshopt =~ s/HOST-GOES-HERE/$user\@HOST-GOES-HERE/g if $user; } elsif ($sshname eq 'sftp') { $sshopt = " -P PORT-GOES-HERE"; $sshopt .= " " . join(' ', @ARGV) if @ARGV; $sshopt .= " HOST-GOES-HERE"; $sshopt =~ s/HOST-GOES-HERE/$user\@HOST-GOES-HERE/g if $user; } elsif ($sshname eq 'sshfs') { die "Sorry, sshfs handling not implemented yet\n"; } else { die "Logic error: un-recognized command type $sshname.\n"; } sub have_12022 { # Check whether we have port forwarding of 12022 already # Check for local port 12022 listening # Efficiency: check cached result defined $have_12022 and return $have_12022; $have_12022 = 0; debug "checking netstat for 12022 ..."; $netstat = `netstat -an 2>&1` || ''; unless ( $netstat and $netstat =~ m/(tcp|udp)/i ) { # Some Linux machines may not have netstat installed... debug "netstat shows nothing" . ( $netstat ? ": $netstat" : '' ); undef $netstat; } if ( $netstat ) { $netstat =~ m/^\s*(tcp|TCP)4?\s.*[:\.]12022\s+(0\.0\.0\.0|\*)[:\.](\*|0)\s+LISTEN/m or return 0; debug "netstat shows 12022 LISTEN"; } # Check for a running skey-ed ssh if ($^O eq 'linux' or $^O eq 'darwin') { $U = $ENV{USER} || getpwuid($<); length($U) > 8 and substr($U,7)='\S+'; # Many ps show abcdefg+ instead of abcdefghijk debug "checking ps for $U ssh ..."; (`ps aux 2>&1` || '') =~ m/^$U .*ssh.* -L 12022:localhost:22 /m or return 0; debug "ps shows 12022 is ours"; } elsif ( $^O eq 'MSWin32' ) { # Wrong: tasklist does not show command arguments #`tasklist` =~ m/ -L 12022:localhost:22 / or return 0; # Somewhat hopeless: -L would not show if we used putty interactively # (but would for "our" putty or plink, or Windows10 ssh) (`wmic path win32_process get ExecutablePath,CommandLine 2>&1` || '') =~ m/ -L 12022:localhost:22 /m and return 1; # Seems can only go by netstat... if we have it! $netstat or return 0; } $have_12022 = 1; return 1; } $ses and have_12022() and die "Cannot $cmdname with another session in progress\n"; if ($proxy eq 'skey' and $sshname =~ m/^(ssh|scp|sftp)$/ and have_12022()) { $quiet or print "Saving skeys, using existing port forwarding\n"; $shost = 'localhost'; $sport = 12022; if ($sshcmd !~ m/putty|plink|pscp|psftp/) { $sshopt =~ s/^/ -oNoHostAuthenticationForLocalhost=yes/; } else { # There is no equivalent option for putty and friends $quiet or print "\nMay get complaint about mismatched RSA key, we connect to 'localhost'.\n\n"; } # Remove port forwardings that we may have added (bummer, removes all others too...) $sshopt =~ s/ -D \S+//; 1 while $sshopt =~ s/ -L \S+//; } else { # Check that all forwardings can be done, no port in use. # Surely not us doing previously: would have used localhost:12022 above... if ($sshname eq 'ssh' and $sshopt =~ m/\s-[DL]\s/) { $netstat = `netstat -an 2>&1` || ''; if ($netstat) { foreach ($sshopt =~ m/\s-[DL]\s+(\d+)/g) { if ($netstat =~ m/^\s*(tcp|TCP)\s.*:$_\s+.*:/m) { $quiet or print "Port $_ in use, removing forward\n"; $sshopt =~ s/\s-[DL]\s+$_(:\S*)?\s/ /; } } } } # No proxies anymore: skeys do not need, have tproxy not cproxy. # Do the "real thing" directly. } $sshopt =~ s/^ *//; $sshopt =~ s/HOST-GOES-HERE/$shost/g; $sshopt =~ s/PORT-GOES-HERE/$sport/g; if ($pra) { # Find xpra binary if ($^O eq 'MSWin32') { foreach $d ( 'C:/Program Files/Xpra', split(/;/,$ENV{PATH}), '.', 'Downloads', glob('*Doc*/Downloads'), 'C:', glob('C:/*'), glob('C:/Program*Files*/*'), glob('C:/WINDOWS*/system*') ) { $x = "$d/Xpra.exe"; $xprcmd = $x, last if $x and $x ne $0 and -f $x; } } elsif ($^O eq 'linux' or $^O eq 'darwin') { foreach $d ( "/bin", "/usr/bin", "/usr/local/bin", split(/:/,$ENV{PATH}) ) { $x = "$d/xpra"; $xprcmd = $x, last if $x and $x ne $0 and -f $x and -x _; } } $xprcmd or die "Cannot find xpra commmand\nYou may need to install from https://www.xpra.org/\n"; $xprcmd =~ s:\\:/:g; $xprcmd =~ s://+:/:g; # Set configs similar to /usr/sms/bin/xpra-with-conf # (wonder whether configs are needed on client or server end). # Environment # See https://xpra.org/trac/wiki/Usage/EnvironmentOptions $ENV{XPRA_PING_TIMEOUT} = 300; $ENV{XPRA_OPENGL_DOUBLE_BUFFERED} = 1; # See https://xpra.org/trac/ticket/2090 $ENV{XPRA_FORCE_BATCH} = 1; $ENV{XPRA_BATCH_MAX_DELAY} = 20; # See https://xpra.org/trac/ticket/877 https://xpra.org/trac/ticket/891 $ENV{XPRA_CLIPBOARD_LIMIT} = 100; # See # https://xpra.org/trac/wiki/Configuration # for info on file location. Location seems too variable on Windows, # so not doing there (hopefully does not matter?). $ENV{HOME} and -d $ENV{HOME} and mkdir "$ENV{HOME}/.xpra", 0700; if ($ENV{HOME} and -d "$ENV{HOME}/.xpra") { if (open F, "< $ENV{HOME}/.xpra/xpra.conf") { $now = join('', ); close F; $add = ''; } else { $add = "# xpra user configuration file, see\n# https://xpra.org/manual.html\n"; } $now ||= ''; foreach ( qw(mdns webcam speaker pulseaudio printing) ) { $now =~ m/^\s*$_\s*=/m and next; $exp or $exp = 1, $add .= "\n# Added by $0 on " . localtime() . "\n"; $add .= "$_ = no\n"; } $add and open F, ">> $ENV{HOME}/.xpra/xpra.conf" and print(F $add), close F; } # Construct command line @XPRARGV = ('start'); $x = 'ssh://'; $user and $x .= "$user\@"; $x .= $shost; $sport and $sport != 22 and $x .= ":$sport"; push @XPRARGV, $x; push @XPRARGV, "--ssh=$sshcmd $sshopt" if $sshopt; push @XPRARGV, "--remote-xpra=/usr/sms/bin/xpra-with-conf"; # xpra options # ... --no-speaker --no-printing ... # seem to work well, but the options # ... --mdns=no --webcam=no ... # seem ineffective (harmless?). push @XPRARGV, qw( --no-speaker --no-printing --mdns=no --webcam=no ); push @XPRARGV, '--exit-with-children=yes'; # We may already have @CMDARGV set for $ses @CMDARGV and unshift @CMDARGV, '/usr/sms/bin/xpra-is-up'; @CMDARGV or @CMDARGV = qw( xterm -ls -fn 9x15 ); push @XPRARGV, '--start-child='.join(' ',@CMDARGV); $showcmd = join(' ', $xprcmd, @XPRARGV); $proxy eq 'tproxy' or $quiet or print "Using command:\n\n$showcmd\n\n"; debug "Running $showcmd"; system $xprcmd, @XPRARGV; exit; } $showcmd = join(' ', $sshcmd, $sshopt, @CMDARGV); $proxy eq 'tproxy' or $quiet or print "Using command:\n\n$showcmd\n\n"; debug "Running $showcmd"; # Windows10 (or its ssh.exe?) is weird: if we use exec then ssh will run # "in the background", dissociated from keyboard input. # (Also for xsess, if it used "ssh -M", we would want system, not exec.) #exec $sshcmd, split(' ',$sshopt), @CMDARGV; #die "Exec of sshcmd failed ($!)\n"; system $sshcmd, split(' ',$sshopt), @CMDARGV; # This would be for "ssh -M ...": #if ($ses) { # -e $ldmsock or die "Failed login? No $ldmsock object\n"; # debug "Running $sshcmd -X -t -S $ldmsock $shost /usr/sms/share/ldm/Xsession"; # system "$sshcmd -X -t -S $ldmsock $shost /usr/sms/share/ldm/Xsession"; # # Do not bother with # #$resp = `$sshcmd -X -S $ldmsock $shost /usr/sms/share/ldm/ldm-checks 2>&1`; # debug "Running $sshcmd -S $ldmsock -O exit $shost"; # system "$sshcmd -S $ldmsock -O exit $shost"; # unlink $ldmsock; #} #!#