3 # client for stream exchange of the FEX service
 
   5 # Author: Ulli Horlacher <framstag@rus.uni-stuttgart.de>
 
   7 # Perl Artistic Licence
 
   9 # sexsend / sexget / sexxx
 
  15 use Digest::MD5 qw(md5_hex);  # encypted ID / SID
 
  17 use constant k => 2**10;
 
  18 use constant M => 2**20;
 
  20 eval 'use Net::INET6Glue::INET_is_INET6';
 
  22 our $version = 20160104;
 
  23 our $DEBUG = $ENV{DEBUG};
 
  25 my %SSL = (SSL_version => 'TLSv1');
 
  28 if (-f ($_ = '/etc/fex/config.pl')) {
 
  29   eval { require } or warn $@;
 
  37   "usage: ... | $0 [options] [SEX-URL/]recipient [stream]\n".
 
  38   "options: -v           verbose mode\n".
 
  39   "         -g           show transfer rate\n".
 
  41   "         -t timeout   timeout in s (waiting for recipient)\n".
 
  42   "special: recipient may be \"public\" or \"anonymous\" or \".\"\n".
 
  43   "see also: sexget, sexxx\n".
 
  44   "example: tail -f /var/log/syslog | $0 fex.flupp.org/admin log\n";
 
  46 if ($0 eq 'sexget' or $0 eq 'fuckme') {
 
  48     "usage: $0 [options] [[SEX-URL/]user:ID] [stream]\n".
 
  49     "options: -v           verbose mode\n".
 
  50     "         -g           show transfer rate\n".
 
  52     "arguments: user:ID    use this user & ID\n".
 
  53     "                      (ID may be \"public\" or user:ID may be \"anonymous\")\n".
 
  54     "           stream     name of the stream\n".
 
  55     "see also: sexsend, sexxx\n".
 
  56     "example: $0 log | grep kernel\n";
 
  61     "usage: $0 [-v] [-g] [-c] [-u [SEX-URL/]user] [-s stream] [files...]\n".
 
  62     "usage: $0 [-v] [-g]      [-u [SEX-URL/]user] [-s stream] | ...\n".
 
  63     "options: -v               verbose mode\n".
 
  64     "         -g               show transfer rate\n".
 
  66     "         -c               compress files\n".
 
  67     "         -u SEX-URL/user  SEX-URL and user (default: use FEXID/FEXXX)\n".
 
  68     "         -s stream        stream name (default: xx)\n".
 
  69     "see also: sexsend, sexget\n".
 
  70     "examples: $0 -s config /etc /usr/local/etc\n".
 
  74 $fexhome = $ENV{FEXHOME} || $ENV{HOME}.'/.fex';
 
  76 $type = $timeout = $stream = $mode = '';
 
  78 $bs = $ENV{BS} || 2**16; # I/O blocksize
 
  80 # server URL, user and auth-ID
 
  81 if ($FEXID = $ENV{FEXID}) {
 
  82   $FEXID = decode_b64($FEXID) if $FEXID !~ /\s/;
 
  83   ($fexcgi,$user,$id) = split(/\s+/,$FEXID);
 
  86     chomp($fexcgi = <$idf>) or die "$0: no FEX-URL in $idf\n";
 
  87     chomp($user = <$idf>)   or die "$0: no FROM in $idf\n";
 
  88     chomp($id = <$idf>)     or die "$0: no ID in $idf\n";
 
  90     despace($fexcgi,$user,$id);
 
  91     unless ($fexcgi =~ /^[_:=\w\-\.\/\@\%]+$/) {
 
  92       die "$0: illegal FEX-URL \"$fexcgi\" in $idf\n";
 
  94     unless ($user =~ /^[_:=\w\-\.\/\@\%\+]+$/) {
 
  95       die "$0: illegal FROM \"$user\" in $idf\n";
 
 100 $opt_h = $opt_v = $opt_V = $opt_q = 0;
 
 101 $opt_u = $opt_s = $opt_c = $opt_t = '';
 
 103 $_ = "$fexhome/config.pl"; require if -f;
 
 107   # xx server URL, user and auth-ID
 
 108   if ($FEXXX = $ENV{FEXXX}) {
 
 109     $FEXXX = decode_b64($FEXXX) if $FEXXX !~ /\s/;
 
 110     ($fexcgi,$user,$id) = split(/\s+/,$FEXXX);
 
 111   } elsif (open $idf,$idf) {
 
 114         chomp($fexcgi = <$idf>) or die "$0: no xx FEX-URL in $idf\n";
 
 115         chomp($user = <$idf>)   or die "$0: no xx FROM in $idf\n";
 
 116         chomp($id = <$idf>)     or die "$0: no xx ID in $idf\n";
 
 123   getopts('hgvcu:s:') or die $usage;
 
 124   die $usage if $opt_h;
 
 125   die $usage unless -t;
 
 129     $type = '&type=GZIP';
 
 133     $fexcgi = $1 if $opt_u =~ s:(.+)/::;
 
 138     die "$0: no xx user found, use \"$0 -u SEX-URL/user\"\n";
 
 142     die "$0: no xx user found, use \"$0 -u user\"\n";
 
 145 } elsif ($0 eq 'sexget' or $0 eq 'fuckme') {
 
 148   getopts('hgvVdu:') or die $usage;
 
 149   die $usage if $opt_h;
 
 153     print "Version: $version\n";
 
 157   if (not $opt_u and @ARGV and $ARGV[0] =~ m{^anonymous|/|:}) {
 
 158     $opt_u = shift @ARGV;
 
 162     $fexcgi = $1 if $opt_u =~ s:(.+)/::;
 
 163     ($user,$id) = split(':',$opt_u);
 
 164     if ($user =~ /^anonymous/) {
 
 172     die "$0: no SEX URL found, use \"$0 -u SEX-URL/recipient\" or \"fexsend -I\"\n";
 
 176     die "$0: no recipient found, use \"$0 -u SEX-URL/recipient\" or \"fexsend -I\"\n";
 
 182   getopts('hguvqVTt:') or die $usage;
 
 183   die $usage if $opt_h;
 
 186     print "Version: $version\n";
 
 190   if ($opt_t and $opt_t =~ /^\d+$/) {
 
 191     $timeout = "&timeout=$opt_t";
 
 194   my $save_user = $user;
 
 195   $user = shift or die $usage;
 
 196   $fexcgi = $1 if $user =~ s:(.+)/::;
 
 198   if ($user =~ /^anonymous/) {
 
 199     die "$0: need SEX-URL with anonymous SEX\n" unless $fexcgi;
 
 201   } elsif ($user eq 'public') {
 
 203       die "$0: public SEX not possible without FEXID, set it with \"fexsend -I\"\n";
 
 207   } elsif ($user eq '.') {
 
 208     open $idf,$idf or die "$0: no $idf\n";
 
 214       die "$0: no SEX URL found, use \"$0 SEX-URL/recipient\" or \"fexsend -I\"\n";
 
 222 $fexcgi =~ s(^http://)()i;
 
 223 $fexcgi =~ s(/fup.*)();
 
 226 if    ($server =~ s(^https://)()i) { $port = 443 }
 
 227 elsif ($server =~ /:(\d+)/)        { $port = $1 }
 
 230 $server =~ s([:/].*)();
 
 232 ## set up tcp/ip connection
 
 233 # $iaddr = gethostbyname($server)
 
 234 #          or die "$0: cannot find ip-address for $server $!\n";
 
 235 # socket(SH,PF_INET,SOCK_STREAM,getprotobyname('tcp')) or die "$0: socket $!\n";
 
 236 # connect(SH,sockaddr_in($port,$iaddr)) or die "$0: connect $!\n";
 
 237 # warn "connecting $server:$port user=$user\n";
 
 239   if ($opt_v and %SSL) {
 
 240     foreach my $v (keys %SSL) {
 
 241       printf "%s => %s\n",$v,$SSL{$v};
 
 244   eval "use IO::Socket::SSL";
 
 245   die "$0: cannot load IO::Socket::SSL\n" if $@;
 
 246   $SH = IO::Socket::SSL->new(
 
 253   $SH = IO::Socket::INET->new(
 
 260 die "cannot connect $server:$port - $!\n" unless $SH;
 
 261 warn "TCPCONNECT to $server:$port\n" if $opt_v;
 
 266 $SIG{PIPE} = \&sigpipehandler;
 
 268 if ($0 eq 'sexget' or $0 eq 'fuckme') {
 
 269   $stream = "&stream=" . shift if @ARGV;
 
 272   } elsif ($id eq 'public') {
 
 275     $cid = query_sid($server,$port,$id);
 
 277   request("GET /sex?BS=$bs&user=$user&ID=$cid$stream HTTP/1.0");
 
 278   transfer($SH,STDOUT);
 
 279   # print while sysread $SH,$_,$bs;
 
 284   $stream = "&stream=" . ($opt_s || 'xx');
 
 287     open my $tar,'-|','tar',"cv${opt_c}f",'-',@ARGV or die "$0: cannot run tar - $!\n";
 
 288     request("POST /sex?BS=$bs&user=$user$type$stream HTTP/1.0");
 
 290     # while (read $tar,$_,$bs) { syswrite $SH,$_ }
 
 292     $cid = query_sid($server,$port,$id);
 
 293     request("GET /sex?BS=$bs&user=$user&ID=$cid$stream HTTP/1.0");
 
 294     $opt_c = 'z' if $H{'CONTENT-TYPE'} =~ /gzip/i;
 
 296       print "extracting from stream:\n";
 
 297       open $out,"|tar xv${opt_c}f -" or die "$0: cannot run tar - $!\n";
 
 300         open $out,"|gzip -d" or die "$0: cannot run gunzip - $!\n";
 
 305     print {$out} $_ while sysread $SH,$_,$bs;
 
 311 $stream = "&stream=" . shift if @ARGV;
 
 313 if ($mode eq 'anonymous') {
 
 315     print "http://$server:$port/sex?user=$user&ID=anonymous$stream\n";
 
 316     printf "http://$server:$port/sex?%s\n",
 
 317            encode_b64("user=$user&ID=anonymous$stream");
 
 319   $mode = "&mode=anonymous";
 
 320 } elsif ($mode eq 'public') {
 
 321   die "$0: need user/ID when sending to public, set it with fexsend -I\n" unless $user and $id;
 
 323     print "http://$server:$port/sex?user=$user&ID=public$stream\n";
 
 324     printf "http://$server:$port/sex?%s\n",
 
 325            encode_b64("user=$user&ID=public$stream");
 
 327   $cid = query_sid($server,$port,$id);
 
 328   $mode = "&ID=$cid&mode=public";
 
 330   # $user = checkalias($user) unless $opt_d;
 
 333 request("POST /sex?BS=$bs&user=$user$mode$type$timeout$stream HTTP/1.0");
 
 334 print STDERR "==> (streaming ...)\n" if $opt_v;
 
 343   my $destination = shift;
 
 351   while ($b = sysread $source,$_,$bs) {
 
 352     print {$destination} $_ or die "$0: link failure - $!\n";
 
 359           printf STDERR "%d MB %d kB/s        \r",
 
 360             int($B/M),int($bt/k/($t2-$tt));
 
 362           printf STDERR "%d kB %d kB/s        \r",
 
 363             int($B/k),int($bt/k/($t2-$tt));
 
 368         sleep 1; # be nice to bandwith
 
 375   die "$0: no stream data\n" unless $B;
 
 379   if ($opt_v or $opt_g) {
 
 381       printf STDERR "transfered: %d MB in %d s with %d kB/s\n",
 
 382         int($B/1048576),$tt,int($B/1024/$tt);
 
 384       printf STDERR "transfered: %d kB in %d s with %d kB/s\n",
 
 385         int($B/1024),$tt,int($B/1024/$tt);
 
 387       printf STDERR "transfered: %d B in %d s with %d kB/s\n",
 
 388         $B,$tt,int($B/1024/$tt);
 
 398   print STDERR "==> $req\n" if $opt_v;
 
 399   syswrite $SH,"$req\r\n\r\n";
 
 401     unless (defined($_ = &getline)) {
 
 402       die "$0: server has closed the connection\n";
 
 404     if (/^HTTP\/[\d\.]+ 200/) {
 
 405       print STDERR "<== $_" if $opt_v;
 
 407     } elsif (/^HTTP\/[\d\.]+ 199/) {
 
 408       print STDERR "<== $_" if $opt_v;
 
 411         print STDERR "<== $_";
 
 416         die "$0: server response: $_";
 
 420   while (defined($_ = &getline)) {
 
 422     $H{uc($1)} = $2 if /(.+):\s*(.+)/;
 
 423     print STDERR "<== $_" if $opt_v;
 
 427 # check for (mutt) alias
 
 430   if ($to !~ /@/ and open F,$ENV{HOME}.'/.mutt/aliases') {
 
 433       if (/^alias $to\s/i) {
 
 441         warn "$0: found alias, using address $to\n";
 
 459   my ($server,$port,$id) = @_;
 
 463   $req = "GET SID HTTP/1.1";
 
 464   print STDERR "==> $req\n" if $opt_v;
 
 465   syswrite $SH,"$req\r\n\r\n";
 
 467   unless (defined $_ and /\w/) {
 
 468     print STDERR "\n" if $opt_v;
 
 469     die "$0: no response from server\n";
 
 472   if (/^HTTP.* 201 (.+)/) {
 
 473     print STDERR "<== $_" if $opt_v;
 
 474     $id = 'MD5H:'.md5_hex($id.$1);
 
 475     while (defined($_ = &getline)) {
 
 478       print STDERR "<== $_" if $opt_v;
 
 481     die "$0: $server does not support session ID\n";
 
 488   $SIG{ALRM} = sub { };
 
 491   if (/^HTTP.* \d+ (.*)/) {
 
 493       die "\n$0: server error: @_\n";
 
 495       die "\n$0: server error: $1\n";
 
 498     die "\n$0: got SIGPIPE (server closed connection)\n";
 
 502 # read one text line from $SH;
 
 507   local $SIG{ALRM} = sub { die "$0: timeout while waiting for server reply\n" };
 
 510   # must use sysread to avoid perl line buffering
 
 511   while (sysread $SH,$c,1) {
 
 521 # from MIME::Base64::Perl
 
 530   return "" unless length;
 
 533   for ($i = 0; $i <= $l; $i += 60) {
 
 534     $uu .= "M" . substr($_,$i,60);
 
 538     $uu .= chr(32 + (length)*3/4) . $_;
 
 540   return unpack ("u",$uu);
 
 544 ### common functions ###
 
 548   my @d = localtime((stat shift)[9]);
 
 549   return sprintf('%d%02d%02d',$d[5]+1900,$d[4]+1,$d[3]);
 
 555   s/\%([a-f\d]{2})/chr(hex($1))/ige;
 
 561   # set SSL/TLS options
 
 562   $SSL{SSL_verify_mode} = $ENV{SSLVERIFY} if defined($ENV{SSLVERIFY});
 
 572     $SSL{$opt} = $ENV{$env} if defined($ENV{$env});
 
 575   if ($SSL{SSL_verify_mode}) {
 
 577     unless ($SSL{SSL_ca_path} or $SSL{SSL_ca_file}) {
 
 578       die "$0: \$SSLVERIFYMODE, but not valid \$SSLCAPATH or \$SSLCAFILE\n";
 
 580   } elsif (defined($SSL{SSL_verify_mode})) {
 
 581     # user has set SSLVERIFY=0 !
 
 584     $SSL{SSL_verify_mode} = 1 if $SSL{SSL_ca_path} or $SSL{SSL_ca_file};
 
 590   return if $SSL{SSL_ca_file} or $SSL{SSL_ca_path};
 
 591   foreach (qw(/etc/ssl/certs/ca-certificates.crt)) {
 
 593       $SSL{SSL_ca_file} = $_;
 
 597   foreach (qw(/etc/ssl/certs /etc/pki/tls/certs)) {
 
 599       $SSL{SSL_ca_path} = $_;
 
 607   my ($server,$port) = @_;
 
 608   my $connect = "CONNECT $server:$port HTTP/1.1";
 
 612     tcpconnect(split(':',$proxy));
 
 614       printf "--> %s\n",$connect if $opt_v;
 
 615       nvtsend($connect,"");
 
 618       printf "<-- $_"if $opt_v;
 
 619       unless (/^HTTP.1.. 200/) {
 
 620         die "$0: proxy error : $_";
 
 623       $SH = IO::Socket::SSL->start_SSL($SH,%SSL);
 
 626     tcpconnect($server,$port);
 
 628 #  if ($https and $opt_v) {
 
 629 #    printf "%s\n",$SH->get_cipher();
 
 634 # set up tcp/ip connection
 
 636   my ($server,$port) = @_;
 
 644     # eval "use IO::Socket::SSL qw(debug3)";
 
 646     $SH = IO::Socket::SSL->new(
 
 653     $SH = IO::Socket::INET->new(
 
 664     die "$0: cannot connect $server:$port - $@\n";
 
 667   print "TCPCONNECT to $server:$port\n" if $opt_v;
 
 672   eval "use IO::Socket::SSL";
 
 673   die "$0: cannot load IO::Socket::SSL\n" if $@;
 
 674   eval '$SSL{SSL_verify_mode} = 0 if Net::SSLeay::SSLeay() <= 9470143';
 
 676     foreach my $v (keys %SSL) {
 
 677       printf "%s => %s\n",$v,$SSL{$v};
 
 688   push @head,"Host: $sp";
 
 690   foreach $head (@head) {
 
 691     print "--> $head\n" if $opt_v;
 
 692     print {$SH} $head,"\r\n";
 
 694   print "-->\n" if $opt_v;
 
 700   local $SIG{PIPE} = sub { $sigpipe = "@_" };
 
 704   die "$0: internal error: no active network handle\n" unless $SH;
 
 705   die "$0: remote host has closed the link\n" unless $SH->connected;
 
 707   foreach my $line (@_) {
 
 708     print {$SH} $line,"\r\n";
 
 721   s/([^\w\@\/%^,.=+_:+-])/\\$1/g;
 
 727   print "## DEBUG: @_\n" if $DEBUG;
 
 731 # from MIME::Base64::Perl
 
 738   $res = join '',map(pack('u',$_)=~ /^.(\S*)/, ($_[0]=~/(.{1,45})/gs));
 
 739   $res =~ tr|` -_|AA-Za-z0-9+/|;
 
 740   $padding = (3-length($_[0])%3)%3;
 
 741   $res =~ s/.{$padding}$/'=' x $padding/e if $padding;