#!perl -w # todo: # - better error checking # - figure out when the hidden service becomes operational # now it seems to take quite some time, between getting the hostname # and being able to actually send data to it. use strict; my $verbose= 0; my $password; package TOR; use strict; use IO::Socket; use Net::Cmd; our @ISA = qw(Net::Cmd IO::Socket::INET); sub new { my ($class)= @_; my $torsock= $class->SUPER::new(PeerAddr=>"127.0.0.1", Timeout=>10, PeerPort=>9051, Proto=>'tcp') or die "localhost:9051: $@\n"; $torsock->autoflush(1); $torsock->debug(1) if ($::verbose); return $torsock; } sub authenticate { my ($tor)= @_; $tor->command($password ? "AUTHENTICATE \"$password\"" : "AUTHENTICATE"); return ($tor->response()==CMD_OK); } sub getinfo { my ($tor, $type)= @_; $tor->command("GETINFO", $type); if ($tor->response()==CMD_OK) { my $reply= $tor->message(); if ($reply =~ /$type=(.*)/) { return $1; } } } sub signal { my ($tor, $type)= @_; $tor->command("SIGNAL", $type); my $rc= $tor->response(); if ($rc!=CMD_OK) { printf("error: %d\n", $rc); } printf("signal: %s\n", $tor->message) if ($::verbose); return 1; } package main; use IO::File; use IO::Socket::INET; use Cwd; use threads; use Getopt::Long; $|=1; GetOptions( "v"=>\$verbose, "p=s"=>\$password, ); my $tor= TOR->new(); $tor->authenticate(); my $tordir= find_tor_dir(); printf("tordir=%s\n", $tordir) if ($verbose); sub path_separator { return "\\" if $^O eq "MSWin32"; return "/"; } sub find_tor_dir { # for windows: # todo: find this for a unix system return "$ENV{APPDATA}\\Tor" if ($^O eq "MSWin32"); return "$ENV{HOME}/.tor"; } my $hs= find_hidden_service("torxfer"); if (!$hs) { printf("creating new hiddenservice\n") if ($verbose); create_hidden_service("torxfer", 9873, "127.0.0.1", 9873); $hs= find_hidden_service("torxfer"); } else { printf("found existing hiddenservice\n") if ($verbose); } printf("\n\nlistening on %s:%d\n\n", $hs->{hostname}, $hs->{ports}[0]{externalport}); my $rsyncdconf= "$tordir/torrsyncd.conf"; creatersyncdconf($rsyncdconf, "."); printf("rsyncconf created\n") if ($verbose); async { # note: rsync daemon is killed when thread is terminated printf("rsync listening on %s:%d\n", $hs->{ports}[0]{dest_ip}, $hs->{ports}[0]{dest_port}) if ($verbose); system sprintf("rsync -vv --no-detach --daemon --address=%s --port=%d --config=\"%s\"", $hs->{ports}[0]{dest_ip}, $hs->{ports}[0]{dest_port}, cygpath($rsyncdconf)); }->detach(); printf("rsync listening\n") if ($verbose); sleep(2); tail("rsync.log"); sub tail { open FH, "<".shift or die $!; seek(FH,0,2); # my $checkfile; while(1) { seek(FH,0,1); while () { print $_; # if ($_ =~ /recv_files\((.*?)\)/) { # $checkfile= $1; # } } sleep(1); # if ($checkfile) { # printf("%9d %s\r", -s $_, $_) for (glob(".$checkfile.*")); # } # todo: parse log output to find out what file is currently being transferred # problem monitoring the transferred size, is that the daemon pre-allocates # the entire file, so you can't tell what is being transferred by looking at the filesize } } sub create_hidden_service { my ($dirname, $externalport, $dstip, $dstport)= @_; my $torrc_fn= find_torrc() or die "could not find torrc\n"; my $fh= IO::File->new($torrc_fn, "a") or die "$torrc_fn: append: $!\n"; $fh->printf("HiddenServiceDir %s%s%s/\n", $tordir, path_separator(), $dirname); $fh->printf("HiddenServicePort %d %s:%d\n", $externalport, $dstip, $dstport); $fh->close(); $tor->signal("RELOAD"); printf("hiddenservice created\n") if ($verbose); } sub find_hidden_service { my ($dirname)= @_; printf("find_hidden_service \n") if ($verbose); my $torrc_fn= find_torrc() or die "could not find torrc\n"; printf("torrc = %s\n", $torrc_fn) if ($verbose); my $rc= parse_torrc($torrc_fn) or die "error parsing torrc\n"; printf("torrc parsed\n") if ($verbose); for (my $i=0 ; $i<@$rc ; $i++) { if (lc($rc->[$i]{key}) eq "hiddenservicedir" && $rc->[$i]{value} =~ /[\\\/]$dirname[\\\/]/i) { my @ports; my $dir= $rc->[$i]{value}; $i++; printf("reading hidden svc params\n") if ($verbose); while ($i<@$rc && lc($rc->[$i]{key}) eq "hiddenserviceport") { printf("found hsport: %s\n", $rc->[$i]{value}) if ($verbose); if ($rc->[$i]{value} =~ /(\d+)\s+(\S+):(\d+)/) { push @ports, { externalport=>$1, dest_ip=>$2, dest_port=>$3, }; } $i++; } # wait for hostname printf("waiting for $dir/hostname\n") if ($verbose); my $t0= time(); while (time()-$t0 < 300 && ! -e "$dir/hostname") { sleep(1); } printf("hostname file found\n") if ($verbose); return { ports=>\@ports, dir=>$dir, hostname=>readhostname("$dir/hostname"), }; } } printf("ERROR: no hiddenservice found\n"); return undef; } sub parse_torrc { my ($fn)= @_; my $fh= IO::File->new($fn, "r") or die "$fn: $!\n"; my @rc; while (<$fh>) { if (/^(\w+)\s+(.*?)\s*$/) { push @rc, { key=>$1, value=>$2 }; } } return \@rc; } sub find_torrc { return $tor->getinfo("config-file"); } sub readhostname { my ($fn)= @_; my $fh= IO::File->new($fn, "r") or die "$fn: $!\n"; my $hostname= <$fh>; $fh->close; $hostname =~ s/\s+$//; return $hostname; } sub creatersyncdconf { my ($fn, $dir)= @_; my $cygdir= cygpath($dir); my $rsyncchroot = ($^O eq "MSWin32" || $ENV{USER} eq "root") ? "true" : "false"; my $fh= IO::File->new($fn, "w") or die "$fn: $!\n"; $fh->print(<<__EOF__ ); log file=$cygdir/rsync.log max verbosity=9 [torxfer] path = $cygdir comment = tor transfer area read only = false write only = false use chroot = $rsyncchroot __EOF__ $fh->close(); printf("rsyncd.conf created\n") if ($verbose); } sub cygpath { my $path; if ($_[0] =~ /^\w:/) { # is absolute path with drive letter $path=$_[0]; } elsif ($_[0] =~ /^[\/\\]/) { # prepend drive letter if ($^O eq "MSWin32") { $path=substr(cwd,0,2).$_[0]; } else { $path= $_[0]; } } elsif ($_[0] eq ".") { # current directory $path=cwd; } else { # relative path $path=cwd . '/' . $_[0] } $path =~ s/\\/\//g; return $path; # return `cygpath -a -u "$_[0]"`; }