3 # CLI client for the F*EX service (send, list, delete)
7 # Author: Ulli Horlacher <framstag@rus.uni-stuttgart.de>
9 # Perl Artistic Licence
12 use strict qw'vars subs';
21 use Fcntl qw':flock :mode';
22 use Digest::MD5 qw'md5_hex'; # encrypted ID / SID
23 use Time::HiRes qw'time';
24 # use Smart::Comments;
25 use constant k => 2**10;
26 use constant M => 2**20;
28 eval 'use Net::INET6Glue::INET_is_INET6';
30 &update if "@ARGV" eq 'UPDATE';
34 our ($SH,$fexhome,$idf,$tmpdir,$windoof,$useragent,$editor,$nomail);
35 our ($anonymous,$public);
36 our ($tpid,$frecipient);
37 our ($FEXID,$FEXXX,$HOME);
40 our $version = 20150120;
44 my %SSL = (SSL_version => 'TLSv1');
47 if ($Config{osname} =~ /^mswin/i) {
48 $windoof = $Config{osname};
49 $HOME = $ENV{USERPROFILE};
50 $fexhome = $ENV{FEXHOME} || $HOME.'\fex';
51 $tmpdir = $ENV{FEXTMP} || $ENV{TEMP} || "$fexhome\\tmp";
52 $idf = "$fexhome\\id";
53 $editor = $ENV{EDITOR} || 'notepad.exe';
54 $useragent = sprintf("fexsend-$version (%s %s)",
55 $Config{osname},$Config{archname});
56 $SSL{SSL_verify_mode} = 0;
59 $HOME = (getpwuid($<))[7]||$ENV{HOME};
60 $fexhome = $HOME.'/.fex';
61 $tmpdir = $ENV{FEXTMP} || "$fexhome/tmp";
63 $editor = $ENV{EDITOR} || 'vi';
64 $_ = `(lsb_release -d||uname -a)2>/dev/null`||'';
67 $useragent = "fexsend-$version ($_)";
71 if (-f ($_ = '/etc/fex/config.pl')) {
72 eval { require } or warn $@;
80 my $atype = ''; # archive type
81 my $fexcgi; # F*EX CGI URL
82 my @files; # files to send
83 my %AB = (); # server based address book
84 my ($server,$port,$sid);
86 my $proxy_prefix = '';
88 my $timeout = 30; # server timeout
89 my $fexlist = "$tmpdir/fexlist";
94 $usage = "usage: send file(s): xx [:slot] file...\n".
95 " or: send STDIN: xx [:slot] -\n".
96 " or: send pipe: ... | xx [:slot] \n".
97 " or: get file(s) or STDIN: xx [:slot] \n".
98 " or: get file(s) no-questions: xx [:slot] --\n".
99 "examples: dmesg | xx\n".
102 " xx :conf /etc /boot\n";
105 usage: $0 [options] file(s) [@] recipient(s)
106 or: $0 [special options]
107 or: $0 -f \# recipient(s)
108 or: $0 -x \# [-C -k -D -K -S]
109 options: -v verbose mode
110 -d delete file on fex server
112 -g encrypt file with gpg
113 -m limit limit throughput (kB/s)
114 -i tag use ID data [tag] from ID file
115 -C comment add comment to notification e-mail
116 -k max keep file max days on fex server
117 -D delay auto-delete after download
118 -K no auto-delete after download
119 -M MIME-file (to be displayed in recipient\'s webbrowser)
120 -o overwrite mode, do not resume
121 -a archive put files in archive (.zip .7z .tar .tgz)
122 -s stream read data from pipe and upload it with stream name
123 special options: -I initialize ID file or show ID
124 -I tag add alternate ID data (secondary logins) to ID file
125 -l list sent files numbered (# needed for -f -x -d -N)
126 -f \# forward already uploaded file to another recipient
127 -x \# modify options -C -k -D -K for already uploaded file
128 -d \# delete file on fex server
129 -N \# resend notification e-mail
131 -A edit server address book (aliases)
132 -S show server/user settings and auth-ID
133 -H show hints, examples and more options
135 (\# is a file number, see output from $0 -l)
136 examples: $0 visualization.mpg framstag\@rus.uni-stuttgart.de
137 $0 -a images.zip *.jpg webmaster\@flupp.org,metoo
138 lshw | $0 -s hardware.list admin\@flupp.org
140 # or: $0 -R FEX-URL e-mail
141 # -R FEX mail self-register your e-mail address at FEX server
144 $0 hints and more options:
146 usage: $0 [options] file recipient(s)
148 Recipient can be a comma separated address list. Example:
149 $0 big.file framstag\@rus.uni-stuttgart.de,webmaster\@flupp.org
151 Recipient can be an alias from your server address book
152 (use "$0 -A" to edit it). Example:
155 Recipient can be a SKEY URL, which you have received from a regular F*EX user.
156 When using this URL you are a subuser of this full user and the file will be
157 sent to him. Example:
158 $0 big.file http://fex.rus.uni-stuttgart.de/fup?skey=4285f8cdd881626524fba686d5f0a83a
160 Recipient can be a GKEY URL, which you have received from a regular F*EX user.
161 Using this URL you are a member of his group and the file will be sent to all
162 members of this group. Example:
163 $0 big.file http://fex.rus.uni-stuttgart.de/fup?gkey=50d26547b1e8c1110beb8748fc1d9444
165 When you use "FEX-URL/anonymous" as recipient and your F*EX administrator has
166 allowed anonymous upload for your IP address then no auth-ID is needed.
168 "." as recipient means fex to yourself and show immediately the download URL
169 (no notification e-mail will be sent). Example:
172 "//" as recipient means fex to yourself and create extra short download URL.
176 If you want a Bcc of the notification e-mail then add '!bcc!' to the comment:
177 fexsend -C '!bcc! for me and you' ...
179 Additional special options:
181 -. sends a short instead of a detailed notification e-mail
182 -/ does not upload the file, but tells the server to link it
183 -= uses an alias name as file name
184 -# excludes files (# is list separator) from archive -a
185 -n sends no notification e-mail, but shows the download URL immediately
187 -r ADDRESS sets e-mail Reply-To ADDRESS
188 -F activates female mode
189 -U show authorized URL
190 -+ is an undocumented feature - test it :-)
192 To manage your subuser and groups or forward or redirect files, use a
193 webbrowser with the URL from "$0 -U", e.g.: firefox \$($0 -U)
195 If you want to copy-forward an already uploaded file to another recipient,
196 then you first have to query the file number with:
198 and then copy-forward it with:
199 $0 -b # other\@address
200 Where # is the file number.
202 If you want to modify the keep time, comment or auto-delete behaviour of an
203 already uploaded file then you first have to query the file number with:
205 and then for example set the keep time to 30 days with:
207 Where # is the file number.
209 With option -a you can send several files or whole directories within a single
210 archive file. The archive types tar and tgz are build on-the-fly (streaming)
211 whereas archive types zip and 7z need a temporary archive file on local disk.
213 With option -s you can send any data coming from a pipe (STDIN) as a file
214 without wasting local disc space.
216 With option -X you can specify any parameter, e.g.: -X autodelete=yes
218 For HTTPS you can set the environment variables:
219 SSLVERIFY=1 # activate server identity verification
220 SSLVERSION=TLSv1 # this is the default
221 SSLCAPATH=/etc/ssl/certs # path to trusted (root) certificates
222 SSLCAFILE=/etc/ssl/cert.pem # file with trusted (root) certificates
223 SSLCIPHERLIST=HIGH:!3DES # see http://www.openssl.org/docs/apps/ciphers.html
225 Partner program xx is an internet clipboard. See: xx -h
227 Partner program fexget is for downloading. See: fexget -h
229 For temporary usage of a HTTP proxy use:
230 $0 -P your_proxy:port:chunksize_in_MB file recipient
232 $0 -P wwwproxy.uni-stuttgart.de.de:8080:1024 4GB.tar .
234 For temporary usage of an alternative F*EX server or user use:
235 FEXID="FEXSERVER USER AUTHID" $0 file recipient
237 FEXID="fex.flupp.org gaga\@flupp.org blubb" $0 big.file framstag\@rus.uni-stuttgart.de
239 You can define aliases (and optional fexsend options) in \$HOME/.fex/config.pl:
241 'alias1' => 'user1\@domain1.org',
242 'alias2' => 'user2\@domain2.org',
243 'both' => 'user1\@domain1.org,user2\@domain2.org',
244 'extra' => 'extra\@special.net:-i other -K -k 30',
247 fexsend also respects aliases in $HOME/.mutt/aliases
248 The alias priority is (descending):
249 \$HOME/.fex/config.pl
251 fexserver address book
253 In \$HOME/.fex/config.pl you can also set the SSL* environment variables and the
254 \$opt_* variables, e.g.:
256 \$ENV{SSLVERSION} = 'TLSv1';
276 if ($windoof and not @ARGV and not $ENV{PROMPT}) {
277 # restart with cmd.exe to have mouse cut+paste
278 exec qw'cmd /k',$0,'-W';
282 unless (-d $fexhome) {
283 mkdir $fexhome,0700 or die "$0: cannot create FEXHOME $fexhome - $!\n";
286 unless (-d $tmpdir) {
287 mkdir $tmpdir,0700 or die "$0: cannot create tmpdir $tmpdir - $!\n";
290 my @_ARGV = @ARGV; # save arguments
292 our ($opt_q,$opt_h,$opt_H,$opt_v,$opt_m,$opt_c,$opt_k,$opt_d,$opt_l,$opt_I,
293 $opt_K,$opt_D,$opt_u,$opt_f,$opt_a,$opt_C,$opt_R,$opt_M,$opt_L,$opt_Q,
294 $opt_A,$opt_i,$opt_z,$opt_Z,$opt_b,$opt_P,$opt_x,$opt_X,$opt_V,$opt_U,
295 $opt_s,$opt_o,$opt_g,$opt_F,$opt_n,$opt_r,$opt_S,$opt_N);
298 $opt_q = 1 if @ARGV and $ARGV[-1] eq '--' and pop @ARGV or not -t STDOUT;
299 $opt_h = $opt_v = $opt_m = $opt_I = 0;
301 $_ = "$fexhome/config.pl"; require if -f;
302 getopts('hvIm:') or die $usage;
304 $opt_h = $opt_v = $opt_m = $opt_c = $opt_k = $opt_d = $opt_l = $opt_I = 0;
305 $opt_H = $opt_K = $opt_D = $opt_R = $opt_M = $opt_L = $opt_Q = $opt_A = 0;
306 $opt_x = $opt_o = $opt_g = $opt_V = $opt_U = $opt_F = $opt_n = $opt_q = 0;
308 ${'opt_@'} = ${'opt_!'} = ${'opt_+'} = ${'opt_.'} = ${'opt_/'} = 0;
309 ${'opt_='} = ${'opt_#'} = '';
310 $opt_u = $opt_f = $opt_a = $opt_C = $opt_i = $opt_b = $opt_P = $opt_X = '';
311 $opt_s = $opt_r = '';
312 $_ = "$fexhome/config.pl"; require if -f;
313 getopts('hHvcdognVDKlILUARWMFzZqQS@!+./r:m:k:u:f:a:s:C:i:b:P:x:X:N:=:#:')
322 print "Version: $version\n";
325 if ($opt_K and $opt_D) {
326 die "$0: you cannot use both options -D and -K\n";
329 if ($opt_a and $opt_c) {
330 die "$0: you cannot use both options -a and -c\n";
333 if ($opt_a and $opt_s) {
334 die "$0: you cannot use both options -a and -s\n";
337 if ($opt_g and $opt_c) {
342 if ($opt_f and $opt_f !~ /^\d+$/) {
343 die "$0: option -f needs a number, see $0 -l\n";
346 if ($opt_I and $opt_R) {
347 die "$0: you cannot use both options -I and -R\n";
350 # $opt_C is COMMENT command in F*EX protocol
353 ($opt_l or $opt_L) ? 'LIST':
354 ($opt_Q) ? 'CHECKQUOTA':
355 ($opt_S) ? 'LISTSETTINGS':
356 ($opt_Z) ? 'RECEIVEDLOG':
357 ($opt_z) ? 'SENDLOG':
358 (${'opt_!'}) ? 'FOPLOG':
370 female_mode("show help?") if $opt_F;
382 die $usage if $opt_m and $opt_m !~ /^\d+/;
385 if ($opt_P =~ /^([\w.-]+:\d+)(:(\d+))?/) {
387 $chunksize = $3 || 0;
389 die "$0: proxy must be: SERVER:PORT\n";
393 if ($FEXID = $ENV{FEXID}) {
394 $FEXID = decode_b64($FEXID) if $FEXID !~ /\s/;
395 ($fexcgi,$from,$id) = split(/\s+/,$FEXID);
397 if ($windoof and not -f $idf) { &init_id }
398 if (open $idf,$idf) {
405 # convert old idxx file
406 if ($idf and open $idf,$idf.'xx') {
409 if (open $idf,'>>',$idf) {
410 print {$idf} "\n[xx]\n",
420 if ($FEXXX = $ENV{FEXXX}) {
421 $FEXXX = decode_b64($FEXXX) if $FEXXX !~ /\s/;
422 ($fexcgi,$from,$id) = split(/\s+/,$FEXXX);
423 } elsif (open $idf,$idf) {
426 $proxy = $proxy_prefix = '';
438 $proxy = $proxy_prefix = '';
439 open $idf,$idf or die "$0: cannot open $idf - $!\n";
447 die "$0: no [$opt_i] in $idf\n" unless $_;
452 if ($xx) { &show_id }
457 if (@ARGV > 1 and $ARGV[-1] =~ /(^|\/)anonymous/) {
458 $fexcgi = $1 if $ARGV[-1] =~ s:(.+)/::;
459 die "usage: $0 [options] file FEXSERVER/anonymous\n" unless $fexcgi;
460 $anonymous = $from = 'anonymous';
461 $sid = $id = 'ANONYMOUS';
462 } elsif (@ARGV > 1 and $id eq 'PUBLIC') {
463 $public = $sid = $id;
464 } elsif (@ARGV > 1 and $ARGV[-1] =~ m{^(https?://[\w.-]+(:\d+)?/fup\?[sg]key=\w+)}) {
466 $skey = $1 if $fexcgi =~ /skey=(\w+)/;
467 $gkey = $1 if $fexcgi =~ /gkey=(\w+)/;
470 $fexcgi = $opt_u if $opt_u;
472 if (not -e $idf and not ($fexcgi and $from and $id)) {
473 die "$0: no ID file $idf found, use \"fexsend -I\" to create it\n";
477 die "$0: no FEX URL found, use \"$0 -u URL\" or \"$0 -I\"\n";
480 unless ($from and $id) {
481 die "$0: no sender found, use \"$0 -f FROM:ID\" or \"$0 -I\"\n";
484 if ($fexcgi !~ /^http/) {
485 if ($fexcgi =~ /:443/) { $fexcgi = "https://$fexcgi" }
486 else { $fexcgi = "http://$fexcgi" }
494 $port = 443 if $server =~ s{https://}{};
495 $port = $1 if $server =~ s/:(\d+)//;
497 if (0 and $port == 443) {
498 $opt_s and die "$0: cannot use -s with https due to stunnel bug\n";
499 $opt_g and die "$0: cannot use -g with https due to stunnel bug\n";
502 $server =~ s{http://}{};
505 # $chunksize = 4*k unless $chunksize;
509 if ($port == 80) { $proxy_prefix = "http://$server" }
510 elsif ($port != 443) { $proxy_prefix = "http://$server:$port" }
513 # xx: special file exchange between own accounts
515 my $transferfile = "$tmpdir/STDFEX";
518 $transferfile = "$tmpdir/xx:xxx";
519 } elsif (@ARGV and $ARGV[0] =~ /^:([\w.=+-]+)$/) {
520 $transferfile = "$tmpdir/xx:$1";
523 open my $lock,'>>',$transferfile
524 or die "$0: cannot write $transferfile - $!\n";
525 flock($lock,LOCK_EX|LOCK_NB)
526 or die "$0: $transferfile is locked by another process\n";
527 truncate $transferfile,0;
528 if (not @ARGV and -t) {
529 &get_xx($transferfile);
531 &send_xx($transferfile);
538 &inquire if $windoof and not @ARGV and not
539 ($opt_l or $opt_L or $opt_Q or $opt_A or $opt_U or $opt_I or
540 $opt_f or $opt_x or $opt_N);
543 $opt_C = "!SHORTMAIL! $opt_C";
546 if ($opt_n or $opt_C =~ /NOMAIL|!#!/) {
550 unless ($skey or $gkey or $anonymous) {
552 $opt_f||$opt_x||$opt_Q||$opt_l||$opt_L||$opt_U||$opt_z||$opt_Z||$opt_A
553 ||$opt_d||${'opt_!'}||${'opt_@'})
554 ) { warn "Server/User: $fexcgi/$from\n" }
557 if ($opt_V and not @ARGV) { exit }
558 if ($opt_f) { &forward }
559 elsif ($opt_x) { &modify }
560 elsif ($opt_N) { &renotify }
561 elsif ($opt_Q) { &query_quotas }
562 elsif ($opt_S) { &query_settings }
563 elsif ($opt_l or $opt_L) { &list }
564 elsif ($opt_U) { &show_URL }
565 elsif ($opt_z or $opt_Z or ${'opt_!'}) { &get_log }
566 elsif ($opt_A) { edit_address_book($from) }
567 elsif (${'opt_@'}) { &show_address_book }
568 elsif ($opt_d and $anonymous) { &purge }
569 elsif ($opt_d and $ARGV[-1] =~ /^\d+$/) { &delete }
575 # initialize ID file or show ID
585 $fexcgi = $from = $id = '';
587 unless (-d $fexhome) {
588 mkdir $fexhome,0700 or die "$0: cannot create FEXHOME $fexhome - $!\n";
592 if (not $tag and open $idf,$idf) {
595 last if /^\[$opt_i\]/;
603 chomp($fexcgi,$from,$id);
604 $FEXID = encode_b64("$fexcgi $from $id");
606 print "# hint: to edit the ID file $idf use \"$0 -I .\" #\n";
607 print "export FEXID=$FEXID\n";
608 print "history -d \$((HISTCMD-1));history -d \$((HISTCMD-1))\n";
610 print "FEXID=$FEXID\n";
614 die "$0: no ID data found\n";
618 if ($tag and $tag eq '.') { exec $ENV{EDITOR}||'vi',$idf }
620 if ($tag) { print "F*EX server URL for [$tag]: " }
621 else { print "F*EX server URL: " }
623 $fexcgi =~ s/[\s\n]//g;
624 die "you MUST provide a FEX-URL!\n" unless $fexcgi;
625 if ($fexcgi =~ /\?/) {
626 $from = $1 if $fexcgi =~ /\bfrom=(.+?)(&|$)/i;
627 $id = $1 if $fexcgi =~ /\bid=(.+?)(&|$)/i;
628 $skey = $1 if $fexcgi =~ /\bskey=(.+?)(&|$)/i;
629 $gkey = $1 if $fexcgi =~ /\bgkey=(.+?)(&|$)/i;
632 unless ($fexcgi =~ /^[_:=\w\-\.\/\@\%]+$/) {
633 die "\"$fexcgi\" is not a legal FEX-URL!\n";
635 $fexcgi =~ s:/fup/*$::;
636 print "proxy address (hostname:port or empty if none): ";
638 $proxy =~ s/[\s\n]//g;
639 if ($proxy =~ /^[\w.-]+:\d+$/) {
641 } elsif ($proxy =~ /\S/) {
642 die "wrong proxy address format\n";
647 print "proxy POST limit in MB (use 2048 if unknown): ";
657 $from = 'GROUPMEMBER';
661 print "Your e-mail address as registered at $fexcgi: ";
663 $from =~ s/[\s\n]//g;
664 die "you MUST provide your e-mail address!\n" unless $from;
666 unless ($from =~ /^[_:=\w\-\.\/\@\%\+]+$/) {
667 die "\"$from\" is not a legal e-mail address!\n";
670 print "Your auth-ID for $from at $fexcgi: ";
673 die "you MUST provide your ID!\n" unless $id;
676 if (open $idf,'>>',$idf) {
677 print {$idf} "\n[$tag]\n" if $tag and -s $idf;
678 print {$idf} "$fexcgi$proxy\n",
682 print "data written to $idf\n";
684 die "$0: cannot write to $idf - $!\n";
690 my ($fexcgi,$from,$id);
691 if (open $idf,$idf) {
703 die "$0: too few data in $idf" unless defined $id;
707 $FEXXX = encode_b64("$fexcgi $from $id");
709 print "export FEXXX=$FEXXX\n";
710 print "history -d \$((HISTCMD-1));history -d \$((HISTCMD-1))\n";
712 print "FEXXX=$FEXXX\n";
715 die "$0: cannot read $idf - $!\n";
721 my $fs = shift @ARGV or die $usage;
722 my $mail = shift @ARGV or die $usage;
724 my ($server,$user,$id);
726 die "$0: $idf does already exist\n" if -e $idf;
728 if ($fs =~ /^https/) {
729 die "$0: cannot handle https at this time\n";
732 $fs =~ s{^http://}{};
734 if ($fs =~ s/:(\d+)//) { $port = $1 }
737 tcpconnect($fs,$port);
738 sendheader("$fs:$port","GET $proxy_prefix/fur?user=$mail&verify=no HTTP/1.1");
743 printf "<-- $_"if $opt_v;
749 printf "<-- $_"if $opt_v;
750 if (m{http://(.*)/fup\?from=(.+)&ID=(.+)}) {
755 if (open F,">$idf") {
761 print "user data written to $idf\n";
762 print "you can now fex!\n";
765 die "$0: cannot write to $idf - $!\n";
770 die "$0: no account data received from F*EX server\n";
776 my $transferfile = shift;
780 $SIG{PIPE} = $SIG{INT} = sub {
781 unlink $transferfile;
785 if ($0 eq 'xxx') { @tar = qw'tar -cv' }
786 else { @tar = qw'tar -cvz' }
789 if ("@ARGV" eq '-') {
790 # store STDIN to transfer file
791 shelldo("cat >> $transferfile");
793 print "making tar transfer file $transferfile :\n";
794 # single file? then add this directly
795 if (scalar @ARGV == 1) {
797 # strip path if not ending with /
798 if ($ARGV[0] =~ m:(.+)/(.+): and $2 !~ m:/$:) {
799 ($dir,$file) = ($1,$2);
800 chdir $dir or die "$0: $dir - $!\n";
805 shelldo(@tar,qw'--dereference -f',$transferfile,$file);
807 shelldo(@tar,'-f',$transferfile,$file);
810 shelldo(@tar,'-f',$transferfile,@ARGV);
813 unlink $transferfile;
815 die "$0: interrupted making tar transfer file\n";
817 die "$0: error while making tar transfer file\n";
822 # write input from pipe to transfer file
823 shelldo("cat >> $transferfile");
826 die "$0: no transfer file\n" unless -s $transferfile;
828 serverconnect($server,$port);
829 query_sid($server,$port);
835 file => $transferfile,
837 autodelete => $transferfile =~ /STDFEX/ ? 'NO' : 'DELAY',
840 # open P,'|w3m -T text/html -dump' or die "$0: w3m - $!\n";
843 if ($transferfile =~ /:/ and $0 ne 'xxx') {
844 if ("@r" =~ /\s(X-)?Location: (http.*)\s/) {
845 print "wget -O- $2 | tar xvzf -\n";
849 unlink $transferfile;
857 female_mode("query quotas?") if $opt_F;
865 die "$0: no response from fex server $server\n" unless @r;
867 unless (/^HTTP.* 2/) {
869 die "$0: server response: $_\n";
871 if (($_) = grep(/^X-Sender-Quota/,@r) and /(\d+)\s+(\d+)/) {
872 print "sender quota (used): $1 ($2) MB\n";
874 print "sender quota: unlimited\n";
876 if (($_) = grep(/^X-Recipient-Quota/,@r) and /(\d+)\s+(\d+)/) {
877 print "recipient quota (used): $1 ($2) MB\n";
879 print "recipient quota: unlimited\n";
888 female_mode("query settings?") if $opt_F;
891 print "ID data from \$FEXID\n";
893 print "ID data from $idf\n";
895 die "$0: found no ID\n";
897 print "server: $fexcgi\n";
898 print "user: $from\n";
899 print "auth-ID: $id\n";
909 die "$0: no response from fex server $server\n" unless @r;
911 unless (/^HTTP.* 2/) {
913 die "$0: server response: $_\n";
915 if (($_) = grep(/^X-Autodelete/,@r) and /:\s+(\w+)/) {
916 print "autodelete: $1\n";
918 if (($_) = grep(/^X-Default-Keep/,@r) and /(\d+)/) {
919 print "default keep: $1 days\n";
921 if (($_) = grep(/^X-Default-Locale/,@r) and /:\s+(\w+)/) {
922 print "default locale: $1\n";
924 if (($_) = grep(/^X-MIME/,@r) and /:\s+(\w+)/) {
925 print "display file with browser: $1\n";
927 if (($_) = grep(/^X-Sender-Quota/,@r) and /(\d+)\s+(\d+)/) {
928 print "sender quota (used): $1 ($2) MB\n";
930 print "sender quota: unlimited\n";
932 if (($_) = grep(/^X-Recipient-Quota/,@r) and /(\d+)\s+(\d+)/) {
933 print "recipient quota (used): $1 ($2) MB\n";
935 print "recipient quota: unlimited\n";
946 female_mode("list spooled files?") if $opt_F;
948 if ($opt_l and $n = shift @ARGV and $n =~ /^\d+$/) {
949 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
951 if (/^\s*(\d+)\) (\w+) (.+)/ and $1 eq $n) {
952 serverconnect($server,$port) unless $SH;
955 "GET $proxy_prefix/fop/$2/$2?LIST HTTP/1.1",
956 "User-Agent: $useragent",
960 print "<-- $_" if $opt_v;
962 print "<-- $_" if $opt_v;
970 } elsif (s:HTTP/[\d\. ]+::) {
971 die "$0: server response: $_";
973 die "$0: no response from fex server $server\n";
978 die "$0: file \#$n not found in fexlist\n";
982 to => $opt_l ? '*' : $from,
986 die "$0: no response from fex server $server\n" unless @r;
988 unless (/^HTTP.* 200/) {
990 die "$0: server response: $_\n";
995 open $fexlist,">$fexlist" or die "$0: cannot write $fexlist - $!\n";
997 next unless /<pre>/ or $data;
1000 if (/<a href=".*dkey=(\w+).*?">/) { $dkey = $1 }
1002 # $_ = encode_utf8($_);
1006 print {$fexlist} "\n$1\n";
1007 } elsif (m/(\d+) MB (.+)/) {
1009 printf "%4s) %8d MB %s\n","#$n",$1,$2;
1010 printf {$fexlist} "%3d) %s %s\n",$n,$dkey,$2;
1016 # list received files
1019 next unless /<pre>/ or $data;
1023 if (/(from .* :)/) {
1026 if (m{(\d+) (MB.*)<a href="(https?://.*/fop/\w+/.+)">(.+)</a>( ".*")?}) {
1027 printf "%8d %s%s%s\n",$1,$2,$3,($5||'');
1035 printf "%s/fup/%s\n",$fexcgi,encode_b64("from=$from&id=$id");
1049 die "$0: no response from fex server $server\n" unless @r;
1051 unless (/^HTTP.* 200/) {
1053 die "$0: server response: $_\n";
1056 foreach (@r) { print "$_\n" }
1060 sub show_address_book {
1065 %AB = query_address_book($server,$port,$from);
1066 foreach $alias (sort keys %AB) {
1067 next if $alias eq 'ADDRESS_BOOK';
1068 $_ = sprintf "%s = %s (%s) # %s\n",
1071 $AB{$alias}->{options},
1072 $AB{$alias}->{comment};
1081 die "$0: not yet implemented\n";
1089 $opt_d = shift @ARGV;
1090 die "$usage: $0 -d #\n" if $opt_d !~ /^\d+$/;
1092 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
1093 while (<$fexlist>) {
1094 if (/^to (.+\@.+) :/) {
1096 } elsif (/^\s*(\d+)\) (\w+) (.+)/ and $1 eq $opt_d) {
1097 serverconnect($server,$port) unless $SH;
1100 "GET $proxy_prefix/fop/$2/$2?DELETE HTTP/1.1",
1101 "User-Agent: $useragent",
1105 print "<-- $_" if $opt_v;
1106 if (/^HTTP.* 200/) {
1109 last if /^\n/; # ignore HTML output
1110 print "<-- $_" if $opt_v;
1111 if (/^X-File:.*\/(.+)/) {
1112 printf "%s deleted\n",decode_utf8(urldecode($1));
1116 } elsif (s:HTTP/[\d\. ]+::) {
1117 die "$0: server response: $_";
1119 die "$0: no response from fex server $server\n";
1125 sleep 1; # do not overrun server
1136 my ($data,$aname,$alias);
1138 my $ma = $HOME.'/.mutt/aliases';
1144 if ($from =~ /^SUBUSER|GROUPMEMBER$/) {
1147 # look for single @ in arguments
1148 for (my $i=1; $i<$#ARGV; $i++) {
1149 if ($ARGV[$i] eq '@') {
1150 $ARGV[$i] = join(',',@ARGV[$i+1 .. $#ARGV]);
1155 $to = pop @ARGV or die $usage;
1158 $nomail = $opt_C ||= 'NOMAIL';
1162 $nomail = $opt_C ||= 'NOMAIL';
1164 if ($opt_g and $to =~ /,/) {
1165 die "$0: encryption is supported to only one recipient\n";
1167 if ($to =~ m{^https?://.*/fup\?skey=(\w+)}) {
1172 if ($to =~ m{^https?://.*/fup\?gkey=(\w+)}) {
1173 $from = 'GROUPMEMBER';
1178 @to = split(',',lc($to));
1180 die $usage unless @ARGV or $opt_a or $opt_s;
1181 die $usage if $opt_s and @ARGV;
1183 # early serverconnect necessary for X-Features info
1184 serverconnect($server,$port);
1188 sendheader("$server:$port","OPTIONS FEX HTTP/1.1");
1191 die "$0: no response from fex server $server\n" unless $_;
1192 print "<-- $_" if $opt_v;
1193 if (/^HTTP.* 201/) {
1196 print "<-- $_" if $opt_v;
1198 $aok = $_ if /X-Features:.*ANONYMOUS/;
1200 die "$0: no anonymous support on server $server\n" unless $aok;
1202 die "$0: bad response from server $server : $_\n";
1207 query_sid($server,$port);
1209 if ($from eq 'SUBUSER') {
1211 # die "skey=$skey\nid=$id\nsid=$sid\n";
1214 if ($from eq 'GROUPMEMBER') {
1220 $opt_C ||= 'NOMAIL';
1221 } elsif ($to =~ m:^(//.*):) {
1223 if ($features =~ /XKEY/) {
1227 die "$0: server does not support XKEY\n";
1229 } elsif (grep /^[^@]*$/,@to and not $skey and not $gkey) {
1230 %AB = query_address_book($server,$port,$from);
1232 serverconnect($server,$port);
1233 query_sid($server,$port);
1236 # alias in local config?
1238 if ($alias{$to} =~ /(.+?):(.+)/) {
1243 # special extra upload
1244 system $0,split(/\s/,$opt),@argv,$ato;
1250 # alias in server address book?
1252 # do not substitute alias with expanded addresses because then
1253 # keep and autodelete options from address book will get lost
1256 # look for mutt aliases
1257 elsif ($to !~ /@/ and $to ne $from and open $ma,$ma) {
1260 if (/^alias \Q$to\E\s/i) {
1268 warn "$0: ignoring mutt multi-alias $to = $alias\n";
1273 warn "$0: found mutt alias $to = $alias\n";
1284 $to = join(',',grep /./,@to) or exit;
1285 warn "Server/User: $fexcgi/$from\n" unless $opt_q;
1288 not $skey and not $gkey
1289 and $features =~ /CHECKRECIPIENT/
1290 and $opt_C !~ /^(DELETE|LIST|RECEIVEDLOG|SENDLOG|FOPLOG)$/
1292 checkrecipient($from,$to);
1294 serverconnect($server,$port);
1295 query_sid($server,$port);
1300 if (@ARGV > 1 and not ($opt_a or $opt_s or $opt_d)) {
1301 print "Archive name (name.tar, name.tgz or name.zip) or [ENTER] to send file for file:\n";
1310 $opt_s =~ s/[^\w_.+-]/_/g;
1315 $opt_a =~ s/[^\w_.+-]/_/g;
1316 if ($opt_a =~ /(.+)\.(zip|tar|tgz|7z)$/) {
1320 die "$0: archive name must be one of ".
1321 "$opt_a.tar $opt_a.tgz $opt_a.zip\n";
1323 # no file argument left?
1325 # use file name as archive name
1330 foreach my $file (@ARGV) {
1331 die "$0: cannot read $file\n" unless -l $file or -r $file;
1333 $opt_a .= ".$atype" if $opt_a !~ /\.$atype$/;
1334 $transferfile = "$tmpdir/$opt_a";
1335 unlink $transferfile;
1336 print "Making fex archive ($opt_a):\n";
1337 if ($atype eq 'zip') {
1339 # if ($opt_c) { system(qw'7z a -tzip',$transferfile,@ARGV) }
1340 # else { system(qw'7z a -tzip -mm=copy',$transferfile,@ARGV) }
1341 system(qw'7z a -tzip',$transferfile,@ARGV);
1342 @files = ($transferfile);
1344 # zip archives must be < 2 GB, so split as necessary
1345 @files = zipsplit($transferfile,@ARGV);
1346 if (scalar(@files) == 1) {
1347 $transferfile = $files[0];
1348 $transferfile =~ s/_1.zip$/.zip/;
1349 rename $files[0],$transferfile;
1350 @files = ($transferfile);
1353 @transferfiles = @files;
1354 } elsif ($atype eq '7z') {
1355 # http://www.7-zip.org/
1356 my @X = (); # exclude list
1358 foreach my $x (split('#',${'opt_#'})) {
1362 if ($opt_c) { system(qw'7z a',@X,$transferfile,@ARGV) }
1363 else { system(qw'7z a -t7z -mx0',@X,$transferfile,@ARGV) }
1364 @transferfiles = @files = ($transferfile);
1365 } elsif ($atype eq 'tar') {
1367 system(qw'7z a -ttar',$transferfile,@ARGV);
1368 @transferfiles = @files = ($transferfile);
1370 ## tar is now handled by formdatapost()
1371 # system(qw'tar cvf',$transferfile,@ARGV);
1374 } elsif ($atype eq 'tgz') {
1376 die "$0: archive type tgz not available, use tar, zip or 7z\n";
1378 ## tgz is now handled by formdatapost()
1379 # system(qw'tar cvzf',$transferfile,@ARGV);
1383 die "$0: unknown archive format \"$atype\"\n";
1386 if (@transferfiles) {
1388 # error in making transfer archive?
1390 unlink @transferfiles;
1391 die "$0: $! - aborting upload\n";
1394 # maybe timeout, so make new connect
1395 if (time-$t0 >= $timeout) {
1396 serverconnect($server,$port);
1397 query_sid($server,$port) unless $anonymous;
1417 die "$0: $file is not a regular file, try option -a\n"
1419 die "$0: $file does not exist\n";
1422 die "$0: cannot read $file\n" unless -r $file;
1429 foreach my $file (@files) {
1430 my @s = stat($file);
1431 unless (@s and ($s[2] & S_IROTH) and -r $file) {
1432 die "$0: $file is not world readable\n";
1437 foreach my $file (@files) {
1438 sleep 1; # do not overrun server!
1439 unless (-s $file or $opt_d or $opt_a or $opt_s) {
1440 die "$0: cannot send empty file $file\n";
1442 female_mode("send file $file?") if $opt_F;
1451 autodelete => $opt_D,
1454 if (not @r or not grep /\w/,@r) {
1455 die "$0: no response from server\n";
1457 if (($r) = grep /^ERROR:/,@r) {
1458 if ($anonymous and $r =~ /purge it/) {
1459 die "$0: file is already on server for $to - use another anonymous recipent\n";
1463 die "$0: server error: $r\n";
1466 if (($r) = grep /<h3>\Q$file/,@r) {
1470 if ($opt_a !~ /^afex_\d+\.tar$/ and $file !~ /afex_\d+\.tar$/) {
1471 # print grep({s/^(X-Recipient:.*\((.+)\))/Parameters: $2\n/i} @r);
1473 my ($recipient,$location);
1475 if (/^(X-)?(Recipient.*)/i) {
1477 if (/notification=no/i) { $nonot = 1 }
1480 if (/^(X-)?(Location.*)/i) {
1482 if ($from eq $to or $from =~ /^\Q$to\E@/i
1483 or $nomail or $anonymous or $nonot) {
1484 print "$recipient\n";
1485 print "$location\n";
1492 # delete transfer tmp file
1493 unlink $transferfile if $transferfile;
1499 my ($to,$n,$dkey,$file,$req);
1503 # look for single @ in arguments
1504 for (my $i=1; $i<$#ARGV; $i++) {
1505 if ($ARGV[$i] eq '@') {
1506 $ARGV[$i] = join(',',@ARGV[$i+1 .. $#ARGV]);
1512 # if ($windoof and not @ARGV) { &inquire }
1513 $to = pop @ARGV or die $usage;
1514 $to = $from if $to eq '.';
1516 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
1517 while (<$fexlist>) {
1518 if (/^\s*(\d+)\) (\w+) \[\d+ d\] (.+)/ and $1 eq $opt_f) {
1522 if ($file =~ s/ "(.*)"$//) {
1523 $opt_C ||= $1 if $1 ne 'NOMAIL';
1531 die "$0: file #$opt_f not found in fexlist\n";
1534 female_mode("forward file #$opt_f?") if $opt_F;
1536 serverconnect($server,$port);
1537 query_sid($server,$port);
1539 $req = "GET $proxy_prefix/fup?"
1540 ."from=$from&ID=$sid&to=$to&dkey=$dkey&command=FORWARD";
1541 $req .= "&comment=$opt_C" if $opt_C;
1542 $req .= "&keep=$opt_k" if $opt_k;
1543 $req .= "&autodelete=$opt_D" if $opt_D;
1544 $req .= "&$opt_X" if $opt_X;
1545 $req .= " HTTP/1.1";
1546 sendheader("$server:$port",$req);
1551 $status = 0 if /\Q"$file"/;
1561 die "$0: server failed, rerun command with option -v\n";
1569 my ($to,$n,$dkey,$file,$req,$recipient);
1572 die $usage if @ARGV;
1574 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
1575 while (<$fexlist>) {
1576 if (/^\s*(\d+)\) (\w+) \[\d+ d\] (.+)/ and $1 eq $opt_N) {
1585 die "$0: file #$opt_N not found in fexlist\n";
1588 female_mode("resend notification for file #$opt_N?") if $opt_F;
1590 serverconnect($server,$port);
1591 query_sid($server,$port);
1593 $req = "GET $proxy_prefix/fup?"
1594 ."from=$from&ID=$sid&dkey=$dkey&command=RENOTIFY"
1596 sendheader("$server:$port",$req);
1600 print "<-- $_" if $opt_v;
1602 if (/^X-Notify: (.+)\/(.+)\/(.+)/) {
1609 print "notification e-mail for $file has been resent to $recipient\n";
1612 die "$0: server failed\n";
1614 die "$0: server failed, rerun command with option -v\n";
1624 my ($n,$dkey,$file,$req);
1627 die $usage if @ARGV;
1628 die $usage unless $opt_C or $opt_k or $opt_D;
1630 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
1631 while (<$fexlist>) {
1632 if (/^\s*(\d+)\) (\w+) \[\d+ d\] (.+)/ and $1 eq $opt_x) {
1636 $file =~ s/ "(.*)"$//;
1643 die "$0: file #$opt_x not found in fexlist\n";
1646 female_mode("modify file #$opt_x?") if $opt_F;
1648 serverconnect($server,$port);
1649 query_sid($server,$port);
1651 $req = "GET $proxy_prefix/fup?"
1652 ."from=$from&ID=$sid&dkey=$dkey&command=MODIFY";
1653 $req .= "&comment=$opt_C" if $opt_C;
1654 $req .= "&keep=$opt_k" if $opt_k;
1655 $req .= "&autodelete=$opt_D" if $opt_D;
1656 $req .= " HTTP/1.1";
1657 sendheader("$server:$port",$req);
1672 my $transferfile = shift;
1676 # get transfer file from FEX server
1678 serverconnect($server,$port);
1679 query_sid($server,$port);
1682 xxget($from,$sid,$transferfile);
1685 unless (-s $transferfile) {
1686 unlink $transferfile;
1690 # no further processing if delivering to pipe
1691 exec 'cat',$transferfile unless -t STDOUT;
1693 if ($ft = `file $transferfile 2>/dev/null`) {
1694 if ($ft =~ /compressed/) {
1695 rename $transferfile,"$transferfile.gz";
1696 shelldo(ws("gunzip $transferfile.gz"));
1698 $ft = `file $transferfile`;
1700 # file command failed, so we look ourself into the file...
1701 elsif (open $transferfile,$transferfile) {
1702 read $transferfile,$_,4;
1703 close $transferfile;
1705 if (/\x1F\x8B\x08\x00/) {
1706 rename $transferfile,"$transferfile.gz";
1707 shelldo(ws("gunzip $transferfile.gz"));
1709 $ft = 'tar archive';
1712 if ($ft =~ /tar archive/) {
1713 rename $transferfile,"$transferfile.tar";
1714 $transferfile .= '.tar';
1718 print "Files in transfer-container:\n\n";
1719 shelldo(ws("tar tvf $transferfile"));
1720 print "\nExtract these files? [Yn] ";
1724 print "keeping $transferfile\n";
1726 system("tar xvf $transferfile && rm $transferfile");
1727 die "$0: error while untaring, see $transferfile\n" if -f $transferfile;
1730 exec 'cat',$transferfile;
1738 my ($boundary,$filename,$filesize,$length,$buf,$file,$fpsize,$resume,$seek);
1740 my (@hh,@hb,@r,@pv,$to);
1742 my ($t0,$t1,$t2,$tt,$tc);
1743 my $bs = 2**16; # blocksize for reading and sending file
1744 my $fileid = int(time);
1746 my $connection = '';
1748 my ($tar,$aname,$atype,$tarlist,$tarerror,$location,$transferfile);
1751 if (defined($file = $P{file})) {
1753 $to = $AB{$P{to}} || $P{to}; # for gpg
1755 # special file: stream from STDIN
1757 $filename = encode_utf8($file);
1765 $if =~ s/([^_\w\.\-])/\\$1/g;
1766 $transferfile = $tmpdir . '/' . basename($file) . '.gz';
1767 $of = $transferfile;
1768 $of =~ s/([^_\w\.\-])/\\$1/g;
1769 shelldo("gzip <$if>$of");
1770 $filesize = -s $transferfile;
1771 die "$0: cannot gzip $file\n" unless $filesize;
1772 $file = $transferfile;
1775 # special file: tar-on-the-fly
1776 if (not $windoof and $opt_a and $file =~ /(.+)\.(tar|tgz)$/) {
1779 $tarlist = "$tmpdir/$aname.list";
1780 $tarerror = "$tmpdir/$aname.error";
1782 $tar .= 'z' if $atype eq 'tgz';
1783 if (`tar --help 2>/dev/null` =~ /--index-file/) {
1784 $tar .= " --index-file=$tarlist -f-";
1789 foreach my $x (split('#',${'opt_#'})) {
1790 $tar .= " --exclude=$x";
1795 $file =~ s/([^\w\-\@\#%,.=+~_:])/\\$1/g;
1798 # print "calculating archive size... ";
1799 open $tar,"$tar 2>$tarerror|" or die "$0: cannot run tar - $!\n";
1800 $t0 = int(time) if -t STDOUT;
1801 while ($b = read $tar,$_,$bs) {
1806 printf "Archive size: %d MB\r",int($filesize/M);
1811 printf "Archive size: %d MB\n",int($filesize/M) if -t STDOUT;
1812 unless (close $tar) {
1814 if (open $tarerror,$tarerror) {
1819 unlink $tarlist,$tarerror;
1820 die "$0: tar error:\n$_";
1822 $file = "$aname.$atype";
1823 $filename = encode_utf8($file);
1824 undef $SH; # force reconnect (timeout!)
1829 $filename = encode_utf8(${'opt_='} || $file);
1832 $filename =~ s/^[a-z]://;
1833 $filename =~ s/.*\\//;
1835 $filename =~ s:.*/::;
1836 $filename =~ s:[\r\n]+: :g;
1839 } elsif (not $opt_g and not $opt_s) {
1840 $filesize = -s $file or die "$0: $file is empty or not readable\n";
1844 $filename .= '.gpg' if $opt_g;
1849 $fileid = int(time);
1852 $fileid = md5_hex(fmd(@ARGV));
1854 $fileid = fileid($file);
1860 $file = $filename = '';
1866 @hh = (); # HTTP header
1867 @hb = (); # HTTP body
1874 serverconnect($server,$port);
1875 query_sid($server,$port) unless $anonymous;
1878 $P{id} = $sid; # ugly hack!
1880 # ask server if this file has been already sent
1881 if ($file and not $xx and not
1882 ($opt_s or $opt_g or $opt_o or $opt_d or $opt_l or $opt_L or ${'opt_/'}))
1884 ($seek,$location) = query_file($server,$port,$frecipient||$P{to},$P{from},
1885 $P{id},$filename,$fileid);
1886 if ($filesize == $seek) {
1887 print "Location: $location\n" if $location and $nomail;
1888 warn "$0: $file has been already transferred\n";
1890 } elsif ($seek and $seek < $filesize) {
1891 $resume = " (resuming at byte $seek)";
1892 } elsif ($filesize <= $seek) {
1896 sleep 1; # do not overrun proxy
1897 serverconnect($server,$port);
1902 if ($chunksize and $proxy and $port != 443
1903 and $filesize - $seek > $chunksize - $bs) {
1904 if ($features !~ /MULTIPOST/) {
1905 die sprintf("$0: server does not support chunked multi-POST needed for"
1906 ." files > %d MB via proxy\n",$chunksize/M);
1908 $opt_o = 0; # no overwriting mode for next chunks
1909 $fpsize = $chunksize - $bs;
1911 $fpsize = $filesize - $seek;
1914 $boundary = randstring(48);
1917 $P{filesize} = $filesize;
1919 # send HTTP POST variables
1922 @pv = qw'from to skey keep autodelete comment seek filesize';
1925 @pv = qw'from to gkey keep autodelete comment seek filesize';
1927 @pv = qw'from to id replyto keep autodelete comment command seek filesize';
1929 foreach my $v (@pv) {
1932 push @hb,"--$boundary";
1933 push @hb,"Content-Disposition: form-data; name=\"$name\"";
1935 push @hb,encode_utf8($P{$v});
1939 # at last, POST the file
1941 push @hb,"--$boundary";
1942 push @hb,"Content-Disposition: form-data; name=\"FILE\"; filename=\"$filename\"";
1944 if ($opt_M) { push @hb,"Content-Type: application/x-mime" }
1945 else { push @hb,"Content-Type: application/octet-stream" }
1947 $flink = abs_path($file);
1948 push @hb,"Content-Location: $flink";
1950 # push @hb,"Content-Length: " . ((-s $file||0) - $seek); # optional header!
1951 push @hb,"Content-Length: $fpsize"; # optional header! NOT filesize!
1952 push @hb,"X-File-ID: $fileid";
1957 # prevent proxy chunked mode reply
1958 $connection = "close";
1961 push @hb,"--$boundary--";
1966 $length = length(join('',@hb)) + scalar(@hb)*2 + $fpsize;
1969 if ($file and not $opt_d) {
1970 if ($flink) { $hb[-2] = $flink }
1971 else { $hb[-2] = '(file content)' }
1973 # any other extra URL arguments
1975 $opt_X = "?$::opt_X" if $::opt_X and $file;
1978 push @hh,"POST $proxy_prefix/fup$opt_X HTTP/1.1";
1979 push @hh,"Host: $server:$port";
1980 push @hh,"User-Agent: $useragent";
1981 push @hh,"Content-Length: $length";
1982 push @hh,"Content-Type: multipart/form-data; boundary=$boundary";
1983 push @hh,"Connection: $connection" if $connection;
1987 print "--> $_\n" foreach (@hh,@hb);
1990 $SIG{PIPE} = \&sigpipehandler;
1991 # foreach $sig (keys %SIG) {
1992 # eval '$SIG{$sig} = sub { print "\n!!! SIGNAL '.$sig.' !!!\n"; exit; }';
1997 pop @hb unless $flink;
1998 nvtsend(@hh,@hb) or do {
1999 warn "$0: server has closed the connection, reconnecting...\n";
2001 goto FORMDATAPOST; # necessary: new $sid ==> new @hh
2004 unless ($opt_d or $flink) {
2006 $t0 = $t2 = int(time);
2013 open $file,"gpg -e -r $to|" or die "$0: cannot run gpg - $!\n";
2015 open $file,'>&=STDIN' or die "$0: cannot open STDIN - $!\n";
2019 open $file,"$tar|gpg -e -r $to|" or die "$0: cannot run tar&gpg - $!\n";
2021 open $file,"$tar|" or die "$0: cannot run tar - $!\n";
2025 if (defined $tpid and $tpid == 0) {
2027 if (open $tarlist,$tarlist) {
2028 # print "\n$tar|\n"; system "ls -l $tarlist";
2030 while (<$tarlist>) {
2031 print ' 'x(length($file)+40),"\r",$_;
2038 $SIG{CHLD} = 'IGNORE';
2041 print "Fast forward to byte $seek (resuming)\n";
2042 readahead($file,$seek);
2047 $fileq =~ s/([^\w\-\@\#%,.=+~_:])/\\$1/g;
2048 open $file,"gpg -e -r $to <$fileq|" or die "$0: cannot run gpg - $!\n";
2050 open $file,$file or die "$0: cannot read $file - $!\n";
2059 print $rcamel[0] if ${'opt_+'};
2061 while (my $b = read $file,$buf,$bs) {
2062 print {$SH} $buf or &sigpipehandler;
2064 if ($filesize > 0 and $bytes+$seek > $filesize) {
2065 die "$0: $file filesize has grown while uploading\n";
2069 if (${'opt_+'} and int($t2*10)>$tc) {
2070 print $rcamel[$tc%2+1];
2073 if (not $opt_q and -t STDOUT and int($t2)>$t1) {
2074 &sigpipehandler unless $SH->connected;
2075 # smaller block size is better on slow links
2076 $bs = 4096 if $t1 and $bs>4096 and $bytes/($t2-$t0)<65536;
2077 if ($filesize > 0) {
2078 $pct = sprintf "(%d%%)",int(($bytes+$seek)/$filesize*100);
2080 if ($bytes>2*M and $bs>4096) {
2081 printf STDERR "%s: %d MB of %d MB %s %d kB/s \r",
2082 $opt_s||$opt_a||$file,
2083 int(($bytes+$seek)/M),
2086 int($bt/k/($t2-$tt));
2088 printf STDERR "%s: %d kB of %d MB %s %d kB/s \r",
2089 $opt_s||$opt_a||$file,
2090 int(($bytes+$seek)/k),
2093 int($bt/k/($t2-$tt));
2096 # time window for transfer rate calculation
2102 last if $filesize > 0 and $bytes >= $fpsize;
2103 sleep 1 while ($opt_m and $bytes/k/(time-$t0||1) > $opt_m);
2105 close $file; # or die "$0: error while reading $file - $!\n";
2108 print $rcamel[2] if ${'opt_+'};
2110 # terminate tar verbose output job
2118 if (not $chunksize and $bytes+$seek < $filesize) {
2119 die "$0: $file filesize has shrunk while uploading\n";
2122 if ($seek or $chunksize and $chunksize < $filesize) {
2124 printf STDERR "%s: %d MB in %d s (%d kB/s)",
2125 $opt_s||$opt_a||$file,
2129 if ($bytes+$seek == $filesize) {
2130 printf STDERR ", total %d MB\n",int($filesize/M);
2132 printf STDERR ", chunk #%d : %d MB\n",
2133 $chunk,int(($bytes+$seek)/M);
2136 printf STDERR "%s: %d kB in %d s (%d kB/s)",
2137 $opt_s||$opt_a||$file,
2141 if ($bytes+$seek == $filesize) {
2142 printf STDERR ", total %d kB\n",int($filesize/k);
2144 printf STDERR ", chunk #%d : %d kB\n",
2145 $chunk,int(($bytes+$seek)/k);
2150 printf STDERR "%s: %d MB in %d s (%d kB/s) \n",
2151 $opt_s||$opt_a||$file,
2156 printf STDERR "%s: %d kB in %d s (%d kB/s) \n",
2157 $opt_s||$opt_a||$file,
2164 if (-t STDOUT and not ($opt_s or $opt_g)) {
2165 print STDERR "waiting for server ok..."
2171 print {$SH} "\r\n--$boundary--\r\n";
2173 # special handling of streaming file because of stunnel tcp shutdown bug
2174 if ($opt_s or $opt_g) {
2177 serverconnect($server,$port);
2178 query_sid($server,$port) unless $anonymous;
2179 ($seek,$location) = query_file($server,$port,$P{to},$P{from},$sid,
2181 if ($seek != $bytes) {
2182 die "$0: streamed $bytes bytes but server received $seek bytes\n";
2184 return "X-Location: $location\n";
2190 printf STDERR "%s: %d MB\n",$flink,int($bytes/M);
2192 printf STDERR "%s: %d kB\n",$flink,int($bytes/k);
2200 # SuSe: Can't locate object method "BINMODE" via package "IO::Socket::SSL::SSL_HANDLE"
2201 # binmode $SH,':utf8';
2203 if (not $opt_q and $file and -t STDOUT) {
2204 print STDERR "\r \r";
2208 print "<-- $_\n" if $opt_v;
2209 last if @r and $r[0] =~ / 204 / and /^$/ or /<\/html>/i;
2210 push @r,decode_utf8($_);
2216 if ($proxy and $fpsize+$seek < $filesize) {
2227 my @rc = ('A'..'Z','a'..'z',0..9 );
2231 for (1..$n) { $rs .= $rc[int(rand($rn))] };
2237 my $zipbase = shift;
2241 my ($zsize,$size,$n);
2243 $zipbase =~ s/\.zip$//;
2244 map { s/([^_\w\+\-\.])/\\$1/g } @files;
2246 open my $ff,"find @files|" or die "$0: cannot search for @_ - $!\n";
2253 die "$0: too many zip-archives\n";
2256 while ($file = <$ff>) {
2258 # next if -l $file or not -f $file;
2259 next unless -f $file;
2261 if ($size > 2147480000) {
2263 die "$0: $file too big for zip\n";
2265 if ($zsize + $size > 2147000000) {
2266 push @zipfiles,zip($zipbase.'_'.$n.'.zip',@files);
2277 push @zipfiles,zip($zipbase.'_'.$n.'.zip',@files);
2289 # if ($opt_c) { $cmd = "zip -@ $zip" }
2290 # else { $cmd = "zip -0 -@ $zip" }
2291 $cmd = "zip -@ $zip";
2293 ${'opt_#'} =~ s/#/ /g;
2294 $cmd .= " -x ".${'opt_#'};
2296 print $cmd,"\n" if $opt_v;
2297 open $cmd,"|$cmd" or die "$0: cannot create $zip - $!\n";
2299 print {$cmd} $_."\n";
2300 print " $_\n" if $opt_v;
2302 close $cmd or die "$0: zip failed - $!\n";
2318 return $_ if length($_);
2325 my ($server,$port,$to,$from,$id,$filename,$fileid) = @_;
2328 my ($head,$location);
2329 my ($response,$fexsrv);
2334 $to = $AB{$to} if $AB{$to};
2335 $filename =~ s/([^_=:,;<>()+.\w\-])/'%'.uc(unpack("H2",$1))/ge; # urlencode
2337 $head = "HEAD $proxy_prefix/fop/$to/$from/$filename??SKEY=$id HTTP/1.1";
2339 $head = "HEAD $proxy_prefix/fop/$to/$from/$filename??GKEY=$id HTTP/1.1";
2341 $head = "HEAD $proxy_prefix/fop/$to/$from/$filename??ID=$id HTTP/1.1";
2343 sendheader("$server:$port",$head);
2345 unless (defined $_ and /\w/) {
2346 die "$0: no response from server\n";
2349 print "<-- $_" if $opt_v;
2350 unless (/^HTTP.* 200/) {
2355 print "<-- $_" if $opt_v;
2356 $fexsrv = $_ if /^(Server: fexsrv|X-Features:)/;
2359 die "$0: no fexserver at $server:$port\n" unless $fexsrv;
2360 die "$0: server response: $response";
2364 print "<-- $_" if $opt_v;
2366 if (/^Content-Length:\s+(\d+)/) { $seek = $1 }
2367 if (/^X-File-ID:\s+(.+)/) { $qfileid = $1 }
2368 if (/^X-Features:\s+(.+)/) { $features = $1 }
2369 if (/^X-Location:\s+(.+)/) { $location = $1 }
2372 # return true seek only if file is identified
2373 $seek = 0 if $qfileid and $qfileid ne $fileid;
2375 return ($seek,$location);
2379 sub edit_address_book {
2382 my $ab = "$fexhome/ADDRESS_BOOK";
2386 die "$0: address book not available for subusers\n" if $skey;
2387 die "$0: address book not available for group members\n" if $gkey;
2389 female_mode("edit your address book?") if $opt_F;
2391 %AB = query_address_book($server,$port,$user);
2392 if ($AB{ADDRESS_BOOK} !~ /\w/) {
2394 "# Format: alias e-mail-address # Comment\n".
2396 "framstag framstag\@rus.uni-stuttgart.de\n";
2398 open $ab,">$ab" or die "$0: cannot write to $ab - $!\n";
2399 print {$ab} $AB{ADDRESS_BOOK};
2407 serverconnect($server,$port);
2408 query_sid($server,$port);
2421 sub query_address_book {
2422 my ($server,$port,$user) = @_;
2423 my ($req,$alias,$address,$options,$comment,$cl,$ab,$b);
2428 serverconnect($server,$port);
2429 query_sid($server,$port);
2432 $req = "GET $proxy_prefix/fop/$user/$user/ADDRESS_BOOK?ID=$sid HTTP/1.1";
2433 sendheader("$server:$port",$req);
2435 unless (defined $_ and /\w/) {
2436 die "$0: no response from server\n";
2439 print "<-- $_" if $opt_v;
2440 unless (/^HTTP.* 200/) {
2441 if (/^HTTP.* 404/) {
2442 while (<$SH>) { last if /^\r?\n/ }
2445 # s:HTTP/[\d\. ]+::;
2446 # die "$0: server response: $_";
2454 print "<-- $_" if $opt_v;
2456 $cl = $1 if /^Content-Length: (\d+)/;
2466 print "<-- $_\n" if $opt_v;
2470 ($alias,$address,$options) = split;
2472 if ($options) { $options =~ s/[()]//g }
2473 else { $options = '' }
2474 $AB{$alias} = $address;
2475 $AB{$alias}->{options} = $options||'';
2476 $AB{$alias}->{comment} = $comment||'';
2477 if ($options and $options =~ /keep=(\d+)/i) {
2478 $AB{$alias}->{keep} = $1;
2480 if ($options and $options =~ /autodelete=(\w+)/i) {
2481 $AB{$alias}->{autodelete} = $1;
2489 $AB{ADDRESS_BOOK} = $ab;
2495 # sets global $sid $features $timeout # ugly hack! :-}
2497 my ($server,$port) = @_;
2504 return if $features; # early return if we know enough
2505 $req = "OPTIONS FEX HTTP/1.1";
2507 return if $features; # early return if we know enough
2508 $req = "GET $proxy_prefix/SID HTTP/1.1";
2510 $req = "GET SID HTTP/1.1";
2513 sendheader("$server:$port",$req,"User-Agent: $useragent");
2515 unless (defined $_ and /\w/) {
2516 print "\n" if $opt_v;
2517 die "$0: no response from server\n";
2520 print "<-- $_" if $opt_v;
2522 if (/^HTTP.* [25]0[01] /) {
2523 if (not $proxy and $port ne 443 and /^HTTP.* 201 (.+)/) {
2524 $sid = 'MD5H:'.md5_hex($id.$1);
2528 print "<-- $_" if $opt_v;
2529 $features = $1 if /^X-Features: (.+)/;
2530 $timeout = $1 if /^X-Timeout: (\d+)/;
2533 } elsif (/^HTTP.* 301 /) {
2534 while (<$SH>) { last if /Location/ }
2535 die "$0: cannot use $server:$port because server has a redirection to\n".$_;
2537 # no SID support - perhaps transparent web proxy?
2540 print "<-- $_" if $opt_v;
2541 $fexsrv = $_ if /^(Server: fexsrv|X-Features:)/;
2544 die "$0: no fexserver at $server:$port\n" unless $fexsrv;
2545 serverconnect($server,$port);
2549 # warn "proxy: $proxy\n";
2551 serverconnect($server,$port);
2559 my ($from,$id,$save) = @_;
2562 my ($url,$B,$b,$t0,$t1,$cl);
2567 $url = "$proxy_prefix/fop/$from/$from/$xx?ID=$id";
2569 sendheader("$server:$port","GET $url HTTP/1.0","User-Agent: $useragent");
2573 print "<-- $_" if $opt_v;
2574 $cl = $1 if /^Content-Length:\s(\d+)/;
2575 # $ft = $1 if /^X-File-Type:\s(.+)/;
2579 die "$0: no Content-Length in server-reply\n" unless $cl;
2581 open F,">$save" or die "$0: cannot write to $save - $!\n";
2584 $t0 = $t1 = int(time);
2587 while ($b = read($SH,$_,$bs)) {
2590 if (int(time) > $t1) {
2594 print STDERR $ts,"\r";
2598 sleep 1 while ($opt_m and $B/k/(time-$t0||1) > $opt_m);
2601 print STDERR ts($B,$cl),"\n";
2609 return sprintf("transferred: %d MB (%d%%)",int($b/M),int($b/$tb*100));
2613 sub sigpipehandler {
2614 $SIG{ALRM} = sub { };
2619 kill 9,$tpid if $tpid;
2620 if (@_ and $opt_v) {
2621 die "\n$0: ($$) server error: @_\n";
2623 if (@_ and $_[0] =~ /^HTTP.* \d+ (.*)/) {
2624 die "\n$0: server error: $1\n";
2628 warn "\n$0: connection to $server died\n";
2629 warn "retrying after $timeout seconds...\n";
2631 if ($windoof) { exec $^X,$0,@_ARGV }
2632 else { exec $_0,@_ARGV }
2637 sub checkrecipient {
2638 my ($from,$to) = @_;
2646 command => 'CHECKRECIPIENT',
2649 $_ = shift @r or die "$0: no reply from server\n";
2654 if (s/X-(Recipient: .+)/$1\n/) {
2655 s/autodelete=\w+/autodelete=$opt_D/ if $opt_D;
2656 s/keep=\d+/keep=$opt_k/ if $opt_k;
2658 $frecipient ||= (split)[1];
2662 http_response($_,@r);
2667 # get ID data from ID file
2671 $fexcgi = getline($idf) || die "$0: no FEX-URL in $idf\n";
2672 $from = getline($idf) || die "$0: no FROM in $idf\n";
2673 $id = getline($idf) || die "$0: no ID in $idf\n";
2674 if ($fexcgi =~ s/!([\w.-]+:\d+)(:(\d+))?//) {
2676 $chunksize = $3 || 0;
2678 unless ($fexcgi =~ /^[_:=\w\-\.\/\@\%]+$/) {
2679 die "$0: illegal FEX-URL \"$fexcgi\" in $idf\n";
2681 unless ($from =~ /^[_:=\w\-\.\/\@\%\+]+$/) {
2682 die "$0: illegal FROM \"$from\" in $idf\n";
2692 print "file to send: ";
2693 chomp($file = <STDIN>);
2697 warn "$file does not exist\n";
2699 print "recipient (e-mail address): ";
2700 chomp($to = <STDIN>);
2701 die $usage unless $to;
2704 chomp($opt_C = <STDIN>);
2706 @ARGV = ($file,$to);
2711 if (system(@_) < 0) { die "failed: @_\n" }
2715 # emulate seek on a pipe
2717 my $fh = shift; # filehandle
2718 my $ba = shift; # bytes ahead
2726 $n = $bs if $n > $bs;
2727 $s += read $fh,$_,$n;
2732 # fileid is inode and mtime
2734 my @s = stat(shift);
2735 return @s ? $s[1].$s[9] : int(time);
2739 # collect file meta data (filename, inode, mtime)
2745 foreach $file (@files) {
2746 if (not -l $file and -d $file) {
2748 if (opendir $dir,$dir) {
2749 while (defined ($file = readdir($dir))) {
2750 next if $file eq '..';
2752 $fmd .= $file.fileid($dir);
2754 $fmd .= fmd("$dir/$file");
2760 $fmd .= $file.fileid($file);
2768 # from MIME::Base64::Perl
2774 tr|A-Za-z0-9+=/||cd;
2776 tr|A-Za-z0-9+/| -_|;
2777 return "" unless length;
2780 for ($i = 0; $i <= $l; $i += 60) {
2781 $uu .= "M" . substr($_,$i,60);
2785 $uu .= chr(32+(length)*3/4) . $_;
2787 return unpack("u",$uu);
2793 if (open my $tty,'/dev/tty') {
2797 " [p] perhaps - don't know\n",
2801 if (/^y/i) { return }
2803 if (/^p/i) { int(rand(2)) ? return : exit }
2810 local $_ = shift || <$SH>;
2814 $_ = <$SH> unless $_;
2815 unless (defined $_ and /\w/) {
2816 die "$0: no response from server\n";
2819 # CGI fatalsToBrowser
2820 if (/^HTTP.* 500/) {
2821 @r = <$SH> unless @r;
2823 die "$0: server error: $_\n@r\n";
2825 unless (/^HTTP.* 200/) {
2827 $error =~ s/HTTP.[\s\d.]+//;
2830 print "<-- $_" while <$SH>;
2832 die "$0: server error: $error\n";
2835 print "<-- $_\n" if $opt_v;
2847 my $cfb = '### common functions ###';
2852 open $0,$0 or die "cannot read $0 - $!\n";
2858 foreach my $p (qw(fexget sexsend)) {
2859 open $p,$p or die "cannot read $p - $!\n";
2862 s/\n$cfb.*/\n$cfb\n$cfc/s;
2864 open $p,'>',$p or die "cannot write $p - $!\n";
2869 exec "l $0 fexget sexsend";
2873 ### common functions ###
2877 my @d = localtime((stat shift)[9]);
2878 return sprintf('%d%02d%02d',$d[5]+1900,$d[4]+1,$d[3]);
2884 s/\%([a-f\d]{2})/chr(hex($1))/ige;
2890 # set SSL/TLS options
2891 $SSL{SSL_verify_mode} = $ENV{SSLVERIFY} if defined($ENV{SSLVERIFY});
2892 foreach my $opt (qw(
2901 $SSL{$opt} = $ENV{$env} if defined($ENV{$env});
2904 if ($SSL{SSL_verify_mode}) {
2906 unless ($SSL{SSL_ca_path} or $SSL{SSL_ca_file}) {
2907 die "$0: \$SSLVERIFYMODE, but not valid \$SSLCAPATH or \$SSLCAFILE\n";
2909 } elsif (defined($SSL{SSL_verify_mode})) {
2910 # user has set SSLVERIFY=0 !
2913 $SSL{SSL_verify_mode} = 1 if $SSL{SSL_ca_path} or $SSL{SSL_ca_file};
2919 return if $SSL{SSL_ca_file} or $SSL{SSL_ca_path};
2920 foreach (qw(/etc/ssl/certs/ca-certificates.crt)) {
2922 $SSL{SSL_ca_file} = $_;
2926 foreach (qw(/etc/ssl/certs /etc/pki/tls/certs)) {
2928 $SSL{SSL_ca_path} = $_;
2936 my ($server,$port) = @_;
2937 my $connect = "CONNECT $server:$port HTTP/1.1";
2940 if ($opt_v and $port == 443 and %SSL) {
2941 foreach my $v (keys %SSL) {
2942 printf "%s => %s\n",$v,$SSL{$v};
2947 tcpconnect(split(':',$proxy));
2949 printf "--> %s\n",$connect if $opt_v;
2950 nvtsend($connect,"");
2953 printf "<-- $_"if $opt_v;
2954 unless (/^HTTP.1.. 200/) {
2955 die "$0: proxy error : $_";
2957 eval "use IO::Socket::SSL";
2958 die "$0: cannot load IO::Socket::SSL\n" if $@;
2959 $SH = IO::Socket::SSL->start_SSL($SH,%SSL);
2962 tcpconnect($server,$port);
2964 # if ($port == 443 and $opt_v) {
2965 # printf "%s\n",$SH->get_cipher();
2970 # set up tcp/ip connection
2972 my ($server,$port) = @_;
2980 # eval "use IO::Socket::SSL qw(debug3)";
2981 eval "use IO::Socket::SSL";
2982 die "$0: cannot load IO::Socket::SSL\n" if $@;
2983 $SH = IO::Socket::SSL->new(
2984 PeerAddr => $server,
2990 $SH = IO::Socket::INET->new(
2991 PeerAddr => $server,
3000 die "$0: cannot connect $server:$port - $@\n";
3003 print "TCPCONNECT to $server:$port\n" if $opt_v;
3012 push @head,"Host: $sp";
3014 foreach $head (@head) {
3015 print "--> $head\n" if $opt_v;
3016 print {$SH} $head,"\r\n";
3018 print "-->\n" if $opt_v;
3024 local $SIG{PIPE} = sub { $sigpipe = "@_" };
3028 die "$0: internal error: no active network handle\n" unless $SH;
3029 die "$0: remote host has closed the link\n" unless $SH->connected;
3031 foreach my $line (@_) {
3032 print {$SH} $line,"\r\n";
3043 # from MIME::Base64::Perl
3050 $res = join '',map(pack('u',$_)=~ /^.(\S*)/, ($_[0]=~/(.{1,45})/gs));
3051 $res =~ tr|` -_|AA-Za-z0-9+/|;
3052 $padding = (3-length($_[0])%3)%3;
3053 $res =~ s/.{$padding}$/'=' x $padding/e if $padding;