Bypass Proxy

From YobiWiki
Revision as of 21:19, 17 November 2006 by <bdi>PhilippeTeuwen</bdi> (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Comment passer outre un proxy (mal configuré)

Background

L'article Tunneling SSL Through a WWW Proxy dont une copie est visible sur Bypass Proxy reference

Introduction: Cela marche-t-il sur mon proxy?

La première chose à faire est de voir si vous pouvez passer manuellement à travers votre proxy.
Après, on verra comment "automatiser" et crypter la méthode si ça marche (qqs scripts faits pour vous attendent)
Lorsque Mozilla se connecte à un serveur https à travers un proxy, il fait exactement la même chose que ce que nous allons utiliser: il emploie une commande CONNECT cf l'article donné comme background pour une description + complète.
Essayez:

telnet <proxy-server> <port du proxy>

puis (attention, frappe à l'aveugle...)

CONNECT www.bxlug.be:80 HTTP/1.0
1 return ou +

puis si ca marche le proxy renverra une chaîne style

HTTP/1.0 200 Connection established
Proxy-Agent: firewall wwwd

et là vous accédez de facon normale au service, exemple ici:

GET / HTTP/1.0
1 return ou +

Et vous pouvez tester ainsi le service que vous désirez, par exemple:

CONNECT www.teuwen.org:22 HTTP/1.0
->
HTTP/1.0 200 Connection established
Proxy-Agent: firewall wwwd
SSH-2.0-OpenSSH_3.4p1 Debian 1:3.4p1-1

Vous pouvez aussi recevoir un truc style

HTTP/1.0 407 Proxy-Auth
Proxy-Authenticate: Basic realm=proxy
Server: CSM Proxy Server
PROXY AUTHORIZATION ERROR
[etc]

si vous devez vous identifier à votre proxy.
Dans ce cas, un truc qui fonctionne parfois est de d'abord sortir sur le Net avec votre browser IE qui s'identifiera façon NT si le proxy est M$.
Le proxy vous reconnaîtra alors par après pour qqs temps.
Sinon, vous devez inclure une string d'identification avec la commande CONNECT mais c'est déjà plus complexe.
A la main, à part l'auth basique en base64, je ne vois pas trop, les autres sont un peu trop complexes pour se faire manuellement.

CONNECT host:port HTTP/1.0
Proxy-Authorization: basic dXNlcjpwYXNzd29yZA==

Où la string dXNlcjpwYXNzd29yZA== a été obtenue par

echo -n "user:password" |base64-encode
(package mime-codecs dans la Debian, d'autres utils existent aussi)

Si le proxy est bien configuré il ne devrait pas vous laisser passer sur n'importe quel port comme je le fais mais uniquement sur les ports pour les services sous TLS/SSL: https:443 imaps:993 ...
Dans ce cas vous devrez mettre votre ssh par exemple sur un tel port.
PS: Vous pouvez employer netcat au lieu de telnet, vous verrez ce que vous taperez, ca peut aider...
Bon hack!


Utilisation de SSH à travers un proxy

Installez ce script dans par exemple /usr/local/bin/ssh-tunnel.pl

#! /usr/bin/perl
#
# ssh-tunnel.pl
#
# Usage: ssh-tunnel.pl ssl-proxy port destination_host port
# Run it alone to get a list of options
#
# This script can be used by ssh as a "ProxyCommand" to
# traverse a www-proxy/firewall that supports the http CONNECT
# command described in
# http://home.netscape.com/newsref/std/tunneling_ssl.html
#
# For basic authentication support, see
# http://www.zvon.org/tmRFC/RFC2617/Output/chapter2.html
# "To receive authorization, the client sends the userid and password,
# separated by a single colon (":") character, within a base64 encoded
# string in the credentials"
# Ex:  echo -n "user:password" |base64-encode -> dXNlcjpwYXNzd29yZA==
#
# Example, connect to host named "remote" outside of your firewall:
# $ ssh remote -o'ProxyCommand ssh-tunnel.pl www-proxy 80 remote 22'
#
# Better yet, insert the ProxyCommand definition for host "remote" in
# your $HOME/.ssh/config file:
#
#      .
#      .
#    Host remote
#      ProxyCommand /usr/local/bin/ssh-tunnel.pl www-proxy 80 %h %p
#      .
#      .
#
# Written by Urban Kaveus <urban@statt.ericsson.se>
# Posted by Urban Kaveus to newsgroup comp.security.ssh on 23 Jan 1997.

# twa>>
# Config file format should be the following (located at ~/.ssh/proxy.conf)
# [option-section]
#   debug   = 0
#   dry     = 0
#   timeout = 1
# [proxy-section]
#   12.34/16 proxy1:8080
#   56.78.91/24 proxy2:80 user password
#
#   where the first column will be used as filter on the beginning of the
#   default gateway IP address, the second is the proxy of the network
#   and the last ones (optional) are user/pwd for the proxy.
#   To enable autoselection feature, insert the ProxyCommand in ~/.ssh/config
#   as following:
#   	ProxyCommand /usr/local/bin/ssh-tunnel.pl - - %h %p
#   To enable a different port number for direct connection,
#   use a proxy port prefixed by a ':'
#   	ProxyCommand /usr/local/bin/ssh-tunnel.pl - :8123 %h %p
#
# Revisions:
# - Add automatic proxy detection based on gateway IP Address & config file.
# - Add STDERR logging of proxy connection messages.
# - Acquire gateway instead of current IP address.
#
# Version 5:
# - Check if destination is reachable before detecting proxy.
# - Add support to a different port number for direct connection.
#
# Version 6:
# - Add 'use strict' and declare variables.
# - Add subroutine 'reachable'.
# - Select proxy only if reachable. Config file with multiple proxies is
#   possible.
#
# Version 7:
# - Check only the port of service (destport or proxyport) in subroutine
#   reachable.
#
# Version 8:
# - Add debug & dry options/mode and timeout option.
# - Test destination only if a proxy match is found and record the result.
#
# Version 9:
# - Ignore signal HUP for proper exit of parent and child processes.
# - Add detection of a local destination IP address with update of
#   destination port.
#
# Version 10:
# - Improve subnet specification/selection.
# - Add options section to proxy configuration file.
#
# Version 11:
# - Add support for basic proxy authentication
# - Add option -f to force usage of the proxy
#
# twa<<
use strict;
use Socket;
use Getopt::Long;
use MIME::Base64;

# Usage
sub usage {
    print STDERR "Usage: $0 ssl-proxy port destination port\n";
	print STDERR "Options:\n";
	print STDERR " -d, --debug           ".
		"enable and increment debug level.\n";
	print STDERR " -n, --dry             ".
		"show selection steps, do not connect.\n";
	print STDERR " -t, --timeout=TIME    ".
		"set ping timeout in seconds.\n";
	print STDERR " -i, --icmp            ".
		"enable also ICMP ping (default: tcp only)\n";
	print STDERR " -u, --user=USER       ".
		"enable basic authentication against proxy: user\n";
	print STDERR " -p, --pass=PASSWORD   ".
		"enable basic authentication against proxy: password\n";
	print STDERR " -f, --forceproxy      ".
		"force going through proxy even if directly reachable\n";
    exit(1);
}

# twa>>
# Initialize variables.
my $debug =			0;
my $dry =			0;
my $icmp =			0;
my $timeout =		1;
my $proxyuser =		"";
my $proxypass =		"";
my $proxyauth =		"";
my $authlog =		"";
my $forceproxy =	0;
my $logcount =		20;
my $logfile =		$ENV{HOME}.'/.ssh/proxy.log';
my $confile =		$ENV{HOME}.'/.ssh/proxy.conf';

# Read configuration file, if any, and process option section.
if ( open (my $fh,$confile) ) {
	while(<$fh>) {
		next if m/^$/ || m/^#/;
		last if m/section/i && m/proxy/i;
		next unless m/=/;
		s/^[ \t]*//;
		eval "\$$_";
	}
	close($fh);
}

# Parse command line options, if any.
GetOptions ( "d|debug+" => \$debug,
			 "n|dry" => \$dry,
			 "i|icmp" => \$icmp,
			 "t|timeout=i" => \$timeout,
			 "u|user=s" => \$proxyuser,
			 "p|pass=s" => \$proxypass,
			 "f|forceproxy" => \$forceproxy ) || usage();

# Compute proxy authentication, if required.
$proxyauth = encode_base64($proxyuser.':'.$proxypass) if $proxyuser;
$authlog = " with auth token $proxyauth" if $proxyauth;

# Parse command line arguments
if ( $#ARGV < 3 ) {
	print STDERR "Missing arguments\n";
	usage();
}
my $sslproxy    = shift;
my $proxyport   = shift;
my $destination = shift;
my $destport    = shift;

# Get gateway IP address.
my $gateway = getgateway() || '127.0.0.1';

# Auto-selection enabled: detecting proxy value.
if ( $sslproxy =~ m/^-/ ) {
	print STDERR "$gateway: Proxy auto-selection...\n" if $debug;
	$sslproxy = 'direct-def'; # Set default in case of no match.

	if ( $proxyport =~ m/^:/ ) {
		if ( my $hbn = gethostbyname($destination) ) {
			my $ipaddr = inet_ntoa(scalar $hbn );
			if ( isPrivateNet($ipaddr) ) {
				$proxyport =~ s/^://;
				print STDERR "Local destination[$ipaddr], ".
					"modify dstport to $proxyport\n"
					if $debug > 1;
				$destport = $proxyport;
			}
		}
	}

	# Parse configuration file.
	my $try_ping = 1;
	open (my $fh,$confile) ||
		die "Cannot open file $confile: $!\n";

	# Skip section up to proxies.
	while(<$fh>) {
		next if m/^$/ || m/^#/;
		last if m/section/i && m/proxy/i;
	}
	# Process proxy section.
	while(<$fh>) {
		next if m/^$/ || m/^#/;
		my @list = split;

		# Check if subnet match.
		next unless isInNet($gateway,$list[0]);

		# Found one match, detecting if proxy is required:
		my ($proxy,$port) = split(/:/,$list[1]);
		print STDERR "Found a match: $proxy:$port\n" if $debug > 1;
		# Check once if directly accessible.
		if (! $forceproxy && $try_ping && reachable($destination,$destport)) {
			print STDERR "But $destination:$destport seems directly ".
				"accessible, let's go direct\n" if $debug > 1;
			$sslproxy = 'direct';
			last;
		} else {
			$try_ping = 0;
			# Test if proxy is alive/reachable.
			next unless reachable($proxy,$port);
			print STDERR "And we can ping the proxy, good!\n" if $debug > 1;
			$sslproxy = $proxy;
			$proxyport = $port;

			# Check if proxy authentication is required.
			if ($list[2]) {
				$proxyuser = $list[2];
				$proxypass = $list[3] if $list[3];
				$proxyauth = encode_base64($proxyuser.':'.$proxypass);
				$authlog = " with auth token $proxyauth";
			}
			# Log connections.
			tunnel_log("$gateway: PROXY connect to $destination:$destport",
					   "via $sslproxy:$proxyport".$authlog);
			last;
		}
	}
	close($fh);
}
# Auto-selection enabled: direct connection is possible.
if ( $sslproxy =~ m/^direct/ ) {
	my $direct = 'DIRECT';
	$direct = 'DEFAULT' if $sslproxy =~ m/^direct-def/;
	print STDERR "Nothing relevant, let's try direct\n"
		if $debug > 1 && $sslproxy =~ m/^direct-def/;
	$sslproxy = $destination;
	$proxyport = $destport;

	# Log connections.
	tunnel_log("$gateway: $direct connect to $destination:$destport".$authlog);
}
print STDERR "Connecting to $sslproxy:$proxyport$authlog\n" if $debug > 1;
exit(0) if $dry;
# twa<<

# Set up network communication

my ($protocol) = (getprotobyname("tcp"))[2];
my ($proxyip)  = (gethostbyname($sslproxy))[4];
my $localaddr  = pack('S n a4 x8', &AF_INET, 0, "\0\0\0\0");
my $proxyaddr  = pack('S n a4 x8', &AF_INET, $proxyport, $proxyip);

socket (PROXY, &AF_INET, &SOCK_STREAM, $protocol) or
    die("Failed to create socket");
bind (PROXY, $localaddr) or
    die("Failed to bind socket");
connect (PROXY, $proxyaddr) or
    die("Failed to connect to $sslproxy port $proxyport");

# Force flushing of socket buffers

select (PROXY);  $| = 1;
select (STDOUT); $| = 1;

#binmode PROXY;
#binmode STDOUT;
#binmode STDIN;

# twa>>
# Direct connection does not require CONNECT command.
if ( $sslproxy ne $destination ) {
# twa<<
# Send a "CONNECT" command to proxy:
	my $authstring = "";
	$authstring = "Proxy-Authorization: Basic $proxyauth\n" if $proxyauth;
	print STDERR "CONNECT $destination:$destport HTTP/1.0\n$authstring\n";
	print PROXY  "CONNECT $destination:$destport HTTP/1.0\n$authstring\n";

# Wait for HTTP status code, bail out if you don't get back a 2xx code.

	$_ = <PROXY>;
	print STDERR $_;
	my ($status) = (split())[1];

	die("Received a bad status code \"$status\" from proxy server")
		if ( int($status/100) != 2 );

# Skip through remaining part of MIME header

	while(<PROXY>) {
		print STDERR $_;
		chomp;   # Strip <LF>
		last if /^[\r]*$/;		# Empty line or a single <CR> left
	}

# twa>>
}
# twa<<

# Start copying packets in both directions.

# twa&pte>>
# Apparently ssh sends a HUP signal before terminating STDIN session.
# Catch and ignore HUP in order for the main process to exit the while
# loop and continue killing the child process.
$SIG{HUP} = 0;
# twa&pte<<

if(my $child = fork) { # Parent process

    while (sysread(STDIN,$_,4096)) {
        print PROXY;
    }
    kill(15,$child) if $child;
}

else { # Child process
    while (sysread(PROXY,$_,4096)) {
        print STDOUT;
    }
}

sub tunnel_log {
	# Log connections, keep last 20 connection.
	my @lines;
	if ( open(my $fh,$logfile) ) {
		while(<$fh>) { push @lines, $_; }
		close($fh);
	}
	while ($#lines > $logcount-2) { shift @lines; }
	open(my $fh,">$logfile") || return 0;
	while ($lines[0]) { print $fh shift @lines; }
	print $fh join(' ',@_,"\n");
	close($fh);
	return 1;
}

sub getgateway {
	my $routecmd = '/sbin/route -n';
	$routecmd = 'route print' if
		defined ($ENV{TERM}) &&
		$ENV{TERM} =~ m/cygwin/i;
	my $str = '^0.0.0.0';
	$str = '^Default Gateway' if
		$routecmd =~ m/print/;
	my $result = `$routecmd | grep -i \"$str\"`;
	$result =~ s/$str//;
	$result =~ s/^[: \t]*//;
	$result =~ s/[ \t\n\r]*$//;
	my ($gateway) = split(/[ \t]/,$result);
	return $gateway;
}

sub reachable {
	use Net::Ping;
	my $host = $_[0];
	my $port = $_[1];
	my $p = Net::Ping->new('tcp');
	$p->{port_num} = $port;
	print STDERR "Try tcp-ping($host,$port,$timeout)\n" if $debug > 2;
	my $reachable = $p->ping($host,$timeout);
	$p->close();
	return 1 if $reachable;
	return 0 unless $icmp;
	$p = Net::Ping->new('icmp');
	print STDERR "Try icmp-ping($host,$port,$timeout)\n" if $debug > 2;
	$reachable = $p->ping($host,$timeout);
	$p->close();
	return $reachable;
}

#	Expand subnet prefix: 192.168 becomes 192.168.0.0
sub expandPrefix {
	my @list = split(/\./, $_[0].".0.0.0");
	$#list = 3;
	return join('.', @list);
}

#	Check if the given IP Address is part of the given Subnet.
sub isInNet {
	my ($addr,$subnet) = @_;
	my ($prefix,$bits) = split(/\//,$subnet);
	my $mask = netmask($bits);
	my $ipaddr = inet_aton($addr);
	$prefix = expandPrefix($prefix);
	print STDERR "isInNet($addr,$subnet)\n" if $debug > 2;
	print STDERR "isInNet: prefix=$prefix, bits=$bits, ".
		"mask=".inet_ntoa($mask)."\n" if $debug > 3;
	return (inet_ntoa(($ipaddr & $mask)) eq $prefix);
}

#	Check if given IP Address is a Private Internet Address.
sub isPrivateNet {
	my $ipaddr = $_[0];
	my @privAddrs = ( "10/8", "172.16/12", "192.168/16", "169.254/16" );
	foreach ( @privAddrs ) {
		return 1 if isInNet($ipaddr,$_);
	}
	return 0;
}

#	Create netmask for a given number of bits.
#	Example: netmask(11)= 0xFFE00000
sub netmask {
	my $value = 0;
	my $bits = 32 - $_[0];
	while ($bits--) { $value <<= 1; $value |= 1; }
	return inet_aton(~$value);
}

#	======================================================================
#	End Of File: ssh-tunnel.pl

Dans ~/.ssh/config ajouter une section du style:

 Host hal
   HostName www.teuwen.org
   ProxyCommand /usr/local/bin/ssh-tunnel.pl <nom du proxy> <port du proxy> %h %p

ou pour activer la détection automatique, en bas du ficher config:

 Host *
   ProxyCommand /usr/local/bin/ssh-tunnel.pl - - %h %p

avec un fichier ~/.ssh/proxy.conf style

# SSH Tunnel configuration file.

# Section: OPTIONS
[option-section]
debug   = 0
dry     = 0
timeout = 1

# Section: PROXY
[proxy-section]

# Example
1.2.3/24   myproxy:8080

Le script fonctionne aussi bien sous linux que sous cygwin

Utilisation de SSH à travers un proxy requérant une identification

Il faut envoyer en plus la string d'identification au proxy Dans proxy.conf il suffit d'ajouter le login et pwd dans une 3ème et 4ème colonne

Utilisation d'autres services à travers un proxy requérant une identification (vieux!!)

Installez ce script dans par exemple /usr/local/bin/tunnel-auth.pl

#! /usr/bin/perl
#
# tunnel-auth.pl
#
# Usage: tunnel-auth.pl ssl-proxy port destination_host port localport AuthToken
#
# This script can be used for any program to connect to any port via localhost
# It traverses a www-proxy/firewall that supports the http CONNECT
# command described in
# http://home.netscape.com/newsref/std/tunneling_ssl.html
# & http://muffin.doit.org/doc/rfc/tunneling_ssl.html
#
# The AuthToken: cf http://www.zvon.org/tmRFC/RFC2617/Output/chapter2.html
# "To receive authorization, the client sends the userid and password, separated by a
# single colon (":") character, within a base64 encoded string in the credentials"
# Ex:  echo -n "user:password" |base64-encode -> dXNlcjpwYXNzd29yZA==
#
# Example, connect to host named "remote" outside of your firewall:
# $ tunnel-auth.pl www-proxy 80 remote dXNlcjpwYXNzd29yZA=='
# Then use localhost:2222 to connect remote:22!
#
# Comment all PRINT STDOUT if you don't want a dump.
#
# Written by Urban Kaveus <urban@statt.ericsson.se>
# Posted by Urban Kaveus to newsgroup comp.security.ssh on 23 Jan 1997.
# Addition of base64 Authentication by Doegox <phil@teuwen.org>
# Addition of local server for other services than SSH by Doegox <phil@teuwen.org>

#require 'sys/socket.ph';
use Socket;
# Parse command line arguments

if ( $#ARGV != 5 ) {
    print STDERR "Usage: $0 ssl-proxy port destination port localport AuthToken\n";
    exit(1);
}

$sslproxy    = shift;
$proxyport   = shift;
$destination = shift;
$destport    = shift;
$localport   = shift;
$authtoken   = shift;

print STDOUT "\nProxy: $sslproxy:$proxyport\nDest:  $destination:$destport\nLocalPort: $localport\n";

# Makes STDOUT "hot" so always flushed:
select((select(STDOUT), $|=1)[0]);

# Set up network communication

($protocol) = (getprotobyname("tcp"))[2];
($proxyip)  = (gethostbyname($sslproxy))[4];
$localaddr  = pack('S n a4 x8', &AF_INET, 0, "\0\0\0\0");
$proxyaddr  = pack('S n a4 x8', &AF_INET, $proxyport, $proxyip);

# Set up client socket
socket(Server, PF_INET, SOCK_STREAM, $protocol) or
    die("Failed to create socket for client connexion");
setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) || die "setsockopt: $!";
bind(Server, sockaddr_in($localport, INADDR_ANY)) or
    die("Failed to bind socket for client connexion");
listen(Server,SOMAXCONN) or
    die("Failed to listen for client connexion");
my $paddr;
$SIG{CHLD} = \&REAPER;
$paddr = accept(Client,Server);
my($localport,$iaddr) = sockaddr_in($paddr);
my $name = gethostbyaddr($iaddr,AF_INET);
# Force flushing of socket buffers
select (Client); $| = 1;
print STDOUT "Client connected to $name\n";

# Set up proxy connexion
socket (PROXY, &AF_INET, &SOCK_STREAM, $protocol) or
    die("Failed to create socket for proxy connexion");
bind (PROXY, $localaddr) or
    die("Failed to bind socket for proxy connexion");
connect (PROXY, $proxyaddr) or
    die("Failed to connect to $sslproxy port $proxyport");

# Force flushing of socket buffers
select (PROXY);  $| = 1;

# Send a "CONNECT" command to proxy:

print PROXY "CONNECT $destination:$destport HTTP/1.0\r\nProxy-Authorization: Basic  $authtoken\r\n\r\n";

# Wait for HTTP status code, bail out if you don't get back a 2xx code.

$_ = <PROXY>;
print STDOUT "Server connected to $destination:$destport\n";
print STDOUT "Reply of server:$_";

($status) = (split())[1];

die("Received a bad status code \"$status\" from proxy server")
    if ( int($status/100) != 2 );

# Skip through remaining part of MIME header

while(<PROXY>) {
    chomp;   # Strip <LF>
    last if /^[\r]*$/;		# Empty line or a single <CR> left
}

# Start copying packets in both directions.

if($child = fork) { # Parent process
    while (sysread(PROXY,$_,4096)) {
		print STDOUT "\n\n#<-#$_";
        print Client;
    }
    sleep 2;
    kill(15,$child) if $child;
}

else { # Child process
    while (sysread(Client,$_,4096)) {
    	print STDOUT "\n\n#->#$_";
		print PROXY;
    }
}

Pour l'utiliser par exemple pour Jabber:

/usr/local/bin/tunnel-auth.pl <nom du proxy> <port du proxy> 212.68.212.217 <string d'identification>

Le client peut alors se connecter sur 127.0.0.1:5222
Par défaut, un dump de la communication apparaîtra dans la console.
Si cela est dérangeant, par exemple si la communication se fait en SSL, rediriger la sortie.

/usr/local/bin/tunnel-auth.pl <nom du proxy> <port du proxy> 212.68.212.217 <string d'identification> >/dev/null

L'adresse de destination doit sans doutes être chiffrée (IP) plutôt que textuelle (Nom de domaine)
Si cela est gênant par ex pour le cas d'un serveur jabber, ajouter la correspondance dans /etc/hosts (ou C:\WINNT\system32\drivers\etc\hosts):

127.0.0.1 jabber.reseaucitoyen.be

et utiliser le nom de domaine réel dans le client plutôt que 127.0.0.1

Autre solution pour utiliser d'autres services que SSH:
On peut bien entendu se contenter des premiers scripts pour SSH et utiliser SSH pour créer un tunnel vers une machine à l'extérieur:

ssh -L5222:jabber.reseaucitoyen.be:5222 -N user@hal

hal a été configuré dans le ~/.ssh/config, cf plus haut

Robustesse

Il se peut que la communication soit cassée par le proxy de temps à autres
Pour la rendre robuste, on peut utiliser par exemple la commande suivante:

sh -c "while true; do date; ssh <host>; sleep 30; done"

Conjointement à l'utilisation de l'option "ServerAliveInterval 10" dans config cela garantit de redémarrer le tunnel endéans la minute (ServerAliveInterval*ServerAliveCountMax(=3)+sleep).
Enfin pour générer du traffic ce qui tend à`garder la connexion ouverte par le proxy: pour une communication ssh avec terminal, voir le paquet spinner et pour une communication ssh uniquement avec déport de port et sans shell on peut créer qqch comme:

#!/bin/bash

ANSI_XY_SAVE="\033[s"
ANSI_XY_RESTORE="\033[u"
STAR='-\|/'
echo -n "Dummy login "
for ((k=0;k<5;k=(k+1)%4));do
    echo -e -n "${ANSI_XY_SAVE}${STAR:$k:1} ${ANSI_XY_RESTORE}"
    sleep 5
done

à sauver dans /usr/local/bin/star.sh et l'activer:

chsh -s /usr/local/bin/star.sh <user>