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 = 20160328;
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";
400 syswrite $SH,"User-Agent: sexsend\r\n";
403 unless (defined($_ = &getline)) {
404 die "$0: server has closed the connection\n";
406 if (/^HTTP\/[\d\.]+ 200/) {
407 print STDERR "<-- $_" if $opt_v;
409 } elsif (/^HTTP\/[\d\.]+ 199/) {
410 print STDERR "<-- $_" if $opt_v;
413 print STDERR "<-- $_";
418 die "$0: server response: $_";
422 while (defined($_ = &getline)) {
424 $H{uc($1)} = $2 if /(.+):\s*(.+)/;
425 print STDERR "<-- $_" if $opt_v;
429 # check for (mutt) alias
432 if ($to !~ /@/ and open F,$ENV{HOME}.'/.mutt/aliases') {
435 if (/^alias $to\s/i) {
443 warn "$0: found alias, using address $to\n";
461 my ($server,$port,$id) = @_;
465 $req = "GET SID HTTP/1.1";
466 print STDERR "--> $req\n" if $opt_v;
467 syswrite $SH,"$req\r\n\r\n";
469 unless (defined $_ and /\w/) {
470 print STDERR "\n" if $opt_v;
471 die "$0: no response from server\n";
474 if (/^HTTP.* 201 (.+)/) {
475 print STDERR "<-- $_" if $opt_v;
476 $id = 'MD5H:'.md5_hex($id.$1);
477 while (defined($_ = &getline)) {
480 print STDERR "<-- $_" if $opt_v;
483 die "$0: $server does not support session ID\n";
490 $SIG{ALRM} = sub { };
493 if (/^HTTP.* \d+ (.*)/) {
495 die "\n$0: server error: @_\n";
497 die "\n$0: server error: $1\n";
500 die "\n$0: got SIGPIPE (server closed connection)\n";
504 # read one text line from $SH;
509 local $SIG{ALRM} = sub { die "$0: timeout while waiting for server reply\n" };
512 # must use sysread to avoid perl line buffering
513 while (sysread $SH,$c,1) {
523 # from MIME::Base64::Perl
532 return "" unless length;
535 for ($i = 0; $i <= $l; $i += 60) {
536 $uu .= "M" . substr($_,$i,60);
540 $uu .= chr(32 + (length)*3/4) . $_;
542 return unpack ("u",$uu);
546 ### common functions ###
550 my @d = localtime((stat shift)[9]);
551 return sprintf('%d%02d%02d',$d[5]+1900,$d[4]+1,$d[3]);
557 s/\%([a-f\d]{2})/chr(hex($1))/ige;
563 # set SSL/TLS options
564 $SSL{SSL_verify_mode} = $ENV{SSLVERIFY} if defined($ENV{SSLVERIFY});
574 $SSL{$opt} = $ENV{$env} if defined($ENV{$env});
577 if ($SSL{SSL_verify_mode}) {
579 unless ($SSL{SSL_ca_path} or $SSL{SSL_ca_file}) {
580 die "$0: \$SSLVERIFYMODE, but not valid \$SSLCAPATH or \$SSLCAFILE\n";
582 } elsif (defined($SSL{SSL_verify_mode})) {
583 # user has set SSLVERIFY=0 !
586 $SSL{SSL_verify_mode} = 1 if $SSL{SSL_ca_path} or $SSL{SSL_ca_file};
592 return if $SSL{SSL_ca_file} or $SSL{SSL_ca_path};
593 foreach (qw(/etc/ssl/certs/ca-certificates.crt)) {
595 $SSL{SSL_ca_file} = $_;
599 foreach (qw(/etc/ssl/certs /etc/pki/tls/certs)) {
601 $SSL{SSL_ca_path} = $_;
609 my ($server,$port) = @_;
610 my $connect = "CONNECT $server:$port HTTP/1.1";
614 tcpconnect(split(':',$proxy));
616 printf "--> %s\n",$connect if $opt_v;
617 nvtsend($connect,"");
620 printf "<-- $_"if $opt_v;
621 unless (/^HTTP.1.. 200/) {
622 die "$0: proxy error : $_";
625 $SH = IO::Socket::SSL->start_SSL($SH,%SSL);
628 tcpconnect($server,$port);
630 # if ($https and $opt_v) {
631 # printf "%s\n",$SH->get_cipher();
636 # set up tcp/ip connection
638 my ($server,$port) = @_;
646 # eval "use IO::Socket::SSL qw(debug3)";
648 $SH = IO::Socket::SSL->new(
655 $SH = IO::Socket::INET->new(
666 die "$0: cannot connect $server:$port - $@\n";
669 print "TCPCONNECT to $server:$port\n" if $opt_v;
674 eval "use IO::Socket::SSL";
675 die "$0: cannot load IO::Socket::SSL\n" if $@;
676 eval '$SSL{SSL_verify_mode} = 0 if Net::SSLeay::SSLeay() <= 9470143';
678 foreach my $v (keys %SSL) {
679 printf "%s => %s\n",$v,$SSL{$v};
690 push @head,"Host: $sp";
692 foreach $head (@head) {
693 print "--> $head\n" if $opt_v;
694 print {$SH} $head,"\r\n";
696 print "-->\n" if $opt_v;
702 local $SIG{PIPE} = sub { $sigpipe = "@_" };
706 die "$0: internal error: no active network handle\n" unless $SH;
707 die "$0: remote host has closed the link\n" unless $SH->connected;
709 foreach my $line (@_) {
710 print {$SH} $line,"\r\n";
723 s/([^\w\@\/%^,.=+_:+-])/\\$1/g;
729 print "## DEBUG: @_\n" if $DEBUG;
733 # from MIME::Base64::Perl
740 $res = join '',map(pack('u',$_)=~ /^.(\S*)/, ($_[0]=~/(.{1,45})/gs));
741 $res =~ tr|` -_|AA-Za-z0-9+/|;
742 $padding = (3-length($_[0])%3)%3;
743 $res =~ s/.{$padding}$/'=' x $padding/e if $padding;