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 = 20150826;
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,$https);
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
111 -c compress file with gzip
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 You can list an uploaded file in more detail with
204 Where # is the file number.
206 If you want to modify the keep time, comment or auto-delete behaviour of an
207 already uploaded file then you first have to query the file number with:
209 and then for example set the keep time to 30 days with:
211 Where # is the file number.
213 With option -a you can send several files or whole directories within a single
214 archive file. The archive types tar and tgz are build on-the-fly (streaming)
215 whereas archive types zip and 7z need a temporary archive file on local disk.
217 With option -s you can send any data coming from a pipe (STDIN) as a file
218 without wasting local disc space.
220 With option -X you can specify any parameter, e.g.: -X autodelete=yes
222 For HTTPS you can set the environment variables:
223 SSLVERIFY=1 # activate server identity verification
224 SSLVERSION=TLSv1 # this is the default
225 SSLCAPATH=/etc/ssl/certs # path to trusted (root) certificates
226 SSLCAFILE=/etc/ssl/cert.pem # file with trusted (root) certificates
227 SSLCIPHERLIST=HIGH:!3DES # see http://www.openssl.org/docs/apps/ciphers.html
229 Partner program xx is an internet clipboard. See: xx -h
231 Partner program fexget is for downloading. See: fexget -h
233 For temporary usage of a HTTP proxy use:
234 $0 -P your_proxy:port:chunksize_in_MB file recipient
236 $0 -P wwwproxy.uni-stuttgart.de.de:8080:1024 4GB.tar .
238 For temporary usage of an alternative F*EX server or user use:
239 FEXID="FEXSERVER USER AUTHID" $0 file recipient
241 FEXID="fex.flupp.org gaga\@flupp.org blubb" $0 big.file framstag\@rus.uni-stuttgart.de
243 You can define aliases (and optional fexsend options) in \$HOME/.fex/config.pl:
245 'alias1' => 'user1\@domain1.org',
246 'alias2' => 'user2\@domain2.org',
247 'both' => 'user1\@domain1.org,user2\@domain2.org',
248 'extra' => 'extra\@special.net:-i other -K -k 30',
251 fexsend also respects aliases in $HOME/.mutt/aliases
252 The alias priority is (descending):
253 \$HOME/.fex/config.pl
255 fexserver address book
257 In \$HOME/.fex/config.pl you can also set the SSL* environment variables and the
258 \$opt_* variables, e.g.:
260 \$ENV{SSLVERSION} = 'TLSv1';
280 if ($windoof and not @ARGV and not $ENV{PROMPT}) {
281 # restart with cmd.exe to have mouse cut+paste
282 exec qw'cmd /k',$0,'-W';
286 unless (-d $fexhome) {
287 mkdir $fexhome,0700 or die "$0: cannot create FEXHOME $fexhome - $!\n";
290 unless (-d $tmpdir) {
291 mkdir $tmpdir,0700 or die "$0: cannot create tmpdir $tmpdir - $!\n";
294 my @_ARGV = @ARGV; # save arguments
296 our ($opt_q,$opt_h,$opt_H,$opt_v,$opt_m,$opt_c,$opt_k,$opt_d,$opt_l,$opt_I,
297 $opt_K,$opt_D,$opt_u,$opt_f,$opt_a,$opt_C,$opt_R,$opt_M,$opt_L,$opt_Q,
298 $opt_A,$opt_i,$opt_z,$opt_Z,$opt_b,$opt_P,$opt_x,$opt_X,$opt_V,$opt_U,
299 $opt_s,$opt_o,$opt_g,$opt_F,$opt_n,$opt_r,$opt_S,$opt_N);
302 $opt_q = 1 if @ARGV and $ARGV[-1] eq '--' and pop @ARGV or not -t STDOUT;
303 $opt_h = $opt_v = $opt_m = $opt_I = 0;
305 $_ = "$fexhome/config.pl"; require if -f;
306 getopts('hvIm:') or die $usage;
308 $opt_h = $opt_v = $opt_m = $opt_c = $opt_k = $opt_d = $opt_l = $opt_I = 0;
309 $opt_H = $opt_K = $opt_D = $opt_R = $opt_M = $opt_L = $opt_Q = $opt_A = 0;
310 $opt_x = $opt_o = $opt_g = $opt_V = $opt_U = $opt_F = $opt_n = $opt_q = 0;
312 ${'opt_@'} = ${'opt_!'} = ${'opt_+'} = ${'opt_.'} = ${'opt_/'} = 0;
313 ${'opt_='} = ${'opt_#'} = '';
314 $opt_u = $opt_f = $opt_a = $opt_C = $opt_i = $opt_b = $opt_P = $opt_X = '';
315 $opt_s = $opt_r = '';
316 $_ = "$fexhome/config.pl"; require if -f;
317 getopts('hHvcdognVDKlILUARWMFzZqQS@!+./r:m:k:u:f:a:s:C:i:b:P:x:X:N:=:#:')
326 print "Version: $version\n";
329 if ($opt_K and $opt_D) {
330 die "$0: you cannot use both options -D and -K\n";
333 if ($opt_a and $opt_c) {
334 die "$0: you cannot use both options -a and -c\n";
337 if ($opt_a and $opt_s) {
338 die "$0: you cannot use both options -a and -s\n";
341 if ($opt_g and $opt_c) {
346 if ($opt_f and $opt_f !~ /^\d+$/) {
347 die "$0: option -f needs a number, see $0 -l\n";
350 if ($opt_I and $opt_R) {
351 die "$0: you cannot use both options -I and -R\n";
354 # $opt_C is COMMENT command in F*EX protocol
357 ($opt_l or $opt_L) ? 'LIST':
358 ($opt_Q) ? 'CHECKQUOTA':
359 ($opt_S) ? 'LISTSETTINGS':
360 ($opt_Z) ? 'RECEIVEDLOG':
361 ($opt_z) ? 'SENDLOG':
362 (${'opt_!'}) ? 'FOPLOG':
374 female_mode("show help?") if $opt_F;
386 die $usage if $opt_m and $opt_m !~ /^\d+/;
389 if ($opt_P =~ /^([\w.-]+:\d+)(:(\d+))?/) {
391 $chunksize = $3 || 0;
393 die "$0: proxy must be: SERVER:PORT\n";
397 if ($FEXID = $ENV{FEXID}) {
398 $FEXID = decode_b64($FEXID) if $FEXID !~ /\s/;
399 ($fexcgi,$from,$id) = split(/\s+/,$FEXID);
401 if ($windoof and not -f $idf) { &init_id }
402 if (open $idf,$idf) {
409 # convert old idxx file
410 if ($idf and open $idf,$idf.'xx') {
413 if (open $idf,'>>',$idf) {
414 print {$idf} "\n[xx]\n",
424 if ($FEXXX = $ENV{FEXXX}) {
425 $FEXXX = decode_b64($FEXXX) if $FEXXX !~ /\s/;
426 ($fexcgi,$from,$id) = split(/\s+/,$FEXXX);
427 } elsif (open $idf,$idf) {
430 $proxy = $proxy_prefix = '';
442 $proxy = $proxy_prefix = '';
443 open $idf,$idf or die "$0: cannot open $idf - $!\n";
451 die "$0: no [$opt_i] in $idf\n" unless $_;
456 if ($xx) { &show_id }
461 if (@ARGV > 1 and $ARGV[-1] =~ /(^|\/)anonymous/) {
462 $fexcgi = $1 if $ARGV[-1] =~ s:(.+)/::;
463 die "usage: $0 [options] file FEXSERVER/anonymous\n" unless $fexcgi;
464 $anonymous = $from = 'anonymous';
465 $sid = $id = 'ANONYMOUS';
466 } elsif (@ARGV > 1 and $id eq 'PUBLIC') {
467 $public = $sid = $id;
468 } elsif (@ARGV > 1 and $ARGV[-1] =~ m{^(https?://[\w.-]+(:\d+)?/fup\?[sg]key=\w+)}) {
470 $skey = $1 if $fexcgi =~ /skey=(\w+)/;
471 $gkey = $1 if $fexcgi =~ /gkey=(\w+)/;
474 $fexcgi = $opt_u if $opt_u;
476 if (not -e $idf and not ($fexcgi and $from and $id)) {
477 die "$0: no ID file $idf found, use \"fexsend -I\" to create it\n";
481 die "$0: no FEX URL found, use \"$0 -u URL\" or \"$0 -I\"\n";
484 unless ($from and $id) {
485 die "$0: no sender found, use \"$0 -f FROM:ID\" or \"$0 -I\"\n";
488 if ($fexcgi !~ /^http/) {
489 if ($fexcgi =~ /:443/) { $fexcgi = "https://$fexcgi" }
490 else { $fexcgi = "http://$fexcgi" }
498 $port = 443 if $server =~ s{https://}{};
499 $port = $1 if $server =~ s/:(\d+)//;
502 # $opt_s and die "$0: cannot use -s with https due to stunnel bug\n";
503 # $opt_g and die "$0: cannot use -g with https due to stunnel bug\n";
507 $server =~ s{http://}{};
510 # $chunksize = 4*k unless $chunksize;
514 if ($port == 80) { $proxy_prefix = "http://$server" }
515 elsif ($port != 443) { $proxy_prefix = "http://$server:$port" }
518 # xx: special file exchange between own accounts
520 my $transferfile = "$tmpdir/STDFEX";
523 $transferfile = "$tmpdir/xx:xxx";
524 } elsif (@ARGV and $ARGV[0] =~ /^:([\w.=+-]+)$/) {
525 $transferfile = "$tmpdir/xx:$1";
528 open my $lock,'>>',$transferfile
529 or die "$0: cannot write $transferfile - $!\n";
530 flock($lock,LOCK_EX|LOCK_NB)
531 or die "$0: $transferfile is locked by another process\n";
532 truncate $transferfile,0;
533 if (not @ARGV and -t) {
534 &get_xx($transferfile);
536 &send_xx($transferfile);
543 &inquire if $windoof and not @ARGV and not
544 ($opt_l or $opt_L or $opt_Q or $opt_A or $opt_U or $opt_I or
545 $opt_f or $opt_x or $opt_N);
548 $opt_C = "!SHORTMAIL! $opt_C";
551 if ($opt_n or $opt_C =~ /NOMAIL|!#!/) {
555 unless ($skey or $gkey or $anonymous) {
557 $opt_f||$opt_x||$opt_Q||$opt_l||$opt_L||$opt_U||$opt_z||$opt_Z||$opt_A
558 ||$opt_d||${'opt_!'}||${'opt_@'})
559 ) { warn "Server/User: $fexcgi/$from\n" }
562 if ($opt_V and not @ARGV) { exit }
563 if ($opt_f) { &forward }
564 elsif ($opt_x) { &modify }
565 elsif ($opt_N) { &renotify }
566 elsif ($opt_Q) { &query_quotas }
567 elsif ($opt_S) { &query_settings }
568 elsif ($opt_l or $opt_L) { &list }
569 elsif ($opt_U) { &show_URL }
570 elsif ($opt_z or $opt_Z or ${'opt_!'}) { &get_log }
571 elsif ($opt_A) { edit_address_book($from) }
572 elsif (${'opt_@'}) { &show_address_book }
573 elsif ($opt_d and $anonymous) { &purge }
574 elsif ($opt_d and $ARGV[-1] =~ /^\d+$/) { &delete }
580 # initialize ID file or show ID
590 $fexcgi = $from = $id = '';
592 unless (-d $fexhome) {
593 mkdir $fexhome,0700 or die "$0: cannot create FEXHOME $fexhome - $!\n";
597 if (not $tag and open $idf,$idf) {
600 last if /^\[$opt_i\]/;
608 chomp($fexcgi,$from,$id);
609 $FEXID = encode_b64("$fexcgi $from $id");
611 print "# hint: to edit the ID file $idf use \"$0 -I .\" #\n";
612 print "export FEXID=$FEXID\n";
613 print "history -d \$((HISTCMD-1));history -d \$((HISTCMD-1))\n";
615 print "FEXID=$FEXID\n";
619 die "$0: no ID data found\n";
623 if ($tag and $tag eq '.') { exec $ENV{EDITOR}||'vi',$idf }
625 if ($tag) { print "F*EX server URL for [$tag]: " }
626 else { print "F*EX server URL: " }
628 $fexcgi =~ s/[\s\n]//g;
629 die "you MUST provide a FEX-URL!\n" unless $fexcgi;
630 if ($fexcgi =~ /\?/) {
631 $from = $1 if $fexcgi =~ /\bfrom=(.+?)(&|$)/i;
632 $id = $1 if $fexcgi =~ /\bid=(.+?)(&|$)/i;
633 # $skey = $1 if $fexcgi =~ /\bskey=(.+?)(&|$)/i;
634 # $gkey = $1 if $fexcgi =~ /\bgkey=(.+?)(&|$)/i;
635 die "$0: cannot use GKEY URL in ID file\n" if $fexcgi =~ /gkey=/i;
636 die "$0: cannot use SKEY URL in ID file\n" if $fexcgi =~ /skey=/i;
639 unless ($fexcgi =~ /^[_:=\w\-\.\/\@\%]+$/) {
640 die "\"$fexcgi\" is not a legal FEX-URL!\n";
642 $fexcgi =~ s:/fup/*$::;
643 print "proxy address (hostname:port or empty if none): ";
645 $proxy =~ s/[\s\n]//g;
646 if ($proxy =~ /^[\w.-]+:\d+$/) {
648 } elsif ($proxy =~ /\S/) {
649 die "wrong proxy address format\n";
654 print "proxy POST limit in MB (use 2048 if unknown): ";
664 $from = 'GROUPMEMBER';
668 print "Your e-mail address as registered at $fexcgi: ";
670 $from =~ s/[\s\n]//g;
671 die "you MUST provide your e-mail address!\n" unless $from;
673 unless ($from =~ /^[_:=\w\-\.\/\@\%\+]+$/) {
674 die "\"$from\" is not a legal e-mail address!\n";
677 print "Your auth-ID for $from at $fexcgi: ";
680 die "you MUST provide your ID!\n" unless $id;
683 if (open $idf,'>>',$idf) {
684 print {$idf} "\n[$tag]\n" if $tag and -s $idf;
685 print {$idf} "$fexcgi$proxy\n",
689 print "data written to $idf\n";
691 die "$0: cannot write to $idf - $!\n";
697 my ($fexcgi,$from,$id);
698 if (open $idf,$idf) {
710 die "$0: too few data in $idf" unless defined $id;
714 $FEXXX = encode_b64("$fexcgi $from $id");
716 print "export FEXXX=$FEXXX\n";
717 print "history -d \$((HISTCMD-1));history -d \$((HISTCMD-1))\n";
719 print "FEXXX=$FEXXX\n";
722 die "$0: cannot read $idf - $!\n";
728 my $fs = shift @ARGV or die $usage;
729 my $mail = shift @ARGV or die $usage;
731 my ($server,$user,$id);
733 die "$0: $idf does already exist\n" if -e $idf;
735 if ($fs =~ /^https/) {
736 die "$0: cannot handle https at this time\n";
739 $fs =~ s{^http://}{};
741 if ($fs =~ s/:(\d+)//) { $port = $1 }
744 tcpconnect($fs,$port);
745 sendheader("$fs:$port","GET $proxy_prefix/fur?user=$mail&verify=no HTTP/1.1");
750 printf "<-- $_"if $opt_v;
756 printf "<-- $_"if $opt_v;
757 if (m{http://(.*)/fup\?from=(.+)&ID=(.+)}) {
762 if (open F,">$idf") {
768 print "user data written to $idf\n";
769 print "you can now fex!\n";
772 die "$0: cannot write to $idf - $!\n";
777 die "$0: no account data received from F*EX server\n";
783 my $transferfile = shift;
787 $SIG{PIPE} = $SIG{INT} = sub {
788 unlink $transferfile;
792 if ($0 eq 'xxx') { @tar = qw'tar -cv' }
793 else { @tar = qw'tar -cvz' }
796 if ("@ARGV" eq '-') {
797 # store STDIN to transfer file
798 shelldo("cat >> $transferfile");
800 print "making tar transfer file $transferfile :\n";
801 # single file? then add this directly
802 if (scalar @ARGV == 1) {
804 # strip path if not ending with /
805 if ($ARGV[0] =~ m:(.+)/(.+): and $2 !~ m:/$:) {
806 ($dir,$file) = ($1,$2);
807 chdir $dir or die "$0: $dir - $!\n";
812 shelldo(@tar,qw'--dereference -f',$transferfile,$file);
814 shelldo(@tar,'-f',$transferfile,$file);
817 shelldo(@tar,'-f',$transferfile,@ARGV);
820 unlink $transferfile;
822 die "$0: interrupted making tar transfer file\n";
824 die "$0: error while making tar transfer file\n";
829 # write input from pipe to transfer file
830 shelldo("cat >> $transferfile");
833 die "$0: no transfer file\n" unless -s $transferfile;
835 serverconnect($server,$port);
836 query_sid($server,$port);
842 file => $transferfile,
844 autodelete => $transferfile =~ /STDFEX/ ? 'NO' : 'DELAY',
847 # open P,'|w3m -T text/html -dump' or die "$0: w3m - $!\n";
850 if ($transferfile =~ /:/ and $0 ne 'xxx') {
851 if ("@r" =~ /\s(X-)?Location: (http.*)\s/) {
852 print "wget -O- $2 | tar xvzf -\n";
856 unlink $transferfile;
864 female_mode("query quotas?") if $opt_F;
872 die "$0: no response from fex server $server\n" unless @r;
874 unless (/^HTTP.* 2/) {
876 die "$0: server response: $_\n";
878 if (($_) = grep(/^X-Sender-Quota/,@r) and /(\d+)\s+(\d+)/) {
879 print "sender quota (used): $1 ($2) MB\n";
881 print "sender quota: unlimited\n";
883 if (($_) = grep(/^X-Recipient-Quota/,@r) and /(\d+)\s+(\d+)/) {
884 print "recipient quota (used): $1 ($2) MB\n";
886 print "recipient quota: unlimited\n";
895 female_mode("query settings?") if $opt_F;
898 print "ID data from \$FEXID\n";
900 print "ID data from $idf\n";
902 die "$0: found no ID\n";
904 print "server: $fexcgi\n";
905 print "user: $from\n";
906 print "auth-ID: $id\n";
916 die "$0: no response from fex server $server\n" unless @r;
918 unless (/^HTTP.* 2/) {
920 die "$0: server response: $_\n";
922 if (($_) = grep(/^X-Autodelete/,@r) and /:\s+(\w+)/) {
923 print "autodelete: $1\n";
925 if (($_) = grep(/^X-Default-Keep/,@r) and /(\d+)/) {
926 print "default keep: $1 days\n";
928 if (($_) = grep(/^X-Default-Locale/,@r) and /:\s+(\w+)/) {
929 print "default locale: $1\n";
931 if (($_) = grep(/^X-MIME/,@r) and /:\s+(\w+)/) {
932 print "display file with browser: $1\n";
934 if (($_) = grep(/^X-Sender-Quota/,@r) and /(\d+)\s+(\d+)/) {
935 print "sender quota (used): $1 ($2) MB\n";
937 print "sender quota: unlimited\n";
939 if (($_) = grep(/^X-Recipient-Quota/,@r) and /(\d+)\s+(\d+)/) {
940 print "recipient quota (used): $1 ($2) MB\n";
942 print "recipient quota: unlimited\n";
953 female_mode("list spooled files?") if $opt_F;
955 if ($opt_l and $n = shift @ARGV and $n =~ /^\d+$/) {
956 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
958 if (/^\s*(\d+)\) (\w+) (.+)/ and $1 eq $n) {
959 serverconnect($server,$port) unless $SH;
962 "GET $proxy_prefix/fop/$2/$2?LIST HTTP/1.1",
963 "User-Agent: $useragent",
967 print "<-- $_" if $opt_v;
969 print "<-- $_" if $opt_v;
977 } elsif (s:HTTP/[\d\. ]+::) {
978 die "$0: server response: $_";
980 die "$0: no response from fex server $server\n";
985 die "$0: file \#$n not found in fexlist\n";
989 to => $opt_l ? '*' : $from,
993 die "$0: no response from fex server $server\n" unless @r;
995 unless (/^HTTP.* 200/) {
997 die "$0: server response: $_\n";
1002 open $fexlist,">$fexlist" or die "$0: cannot write $fexlist - $!\n";
1004 next unless /<pre>/ or $data;
1007 if (/<a href=".*dkey=(\w+).*?">/) { $dkey = $1 }
1009 # $_ = encode_utf8($_);
1016 print {$fexlist} "\n$1\n";
1017 } elsif (m/(\d+) MB (.+)/) {
1019 printf "%4s) %8d MB %s\n","#$n",$1,$2;
1020 printf {$fexlist} "%3d) %s %s\n",$n,$dkey,$2;
1026 # list received files
1029 next unless /<pre>/ or $data;
1033 if (/(from .* :)/) {
1036 if (m{(\d+) (MB.*)<a href="(https?://.*/fop/\w+/.+)">(.+)</a>( ".*")?}) {
1037 printf "%8d %s%s%s\n",$1,$2,$3,($5||'');
1045 printf "%s/fup/%s\n",$fexcgi,encode_b64("from=$from&id=$id");
1059 die "$0: no response from fex server $server\n" unless @r;
1061 unless (/^HTTP.* 200/) {
1063 die "$0: server response: $_\n";
1066 foreach (@r) { print "$_\n" }
1070 sub show_address_book {
1075 %AB = query_address_book($server,$port,$from);
1076 foreach $alias (sort keys %AB) {
1077 next if $alias eq 'ADDRESS_BOOK';
1078 $_ = sprintf "%s = %s (%s) # %s\n",
1081 $AB{$alias}->{options},
1082 $AB{$alias}->{comment};
1091 die "$0: not yet implemented\n";
1099 $opt_d = shift @ARGV;
1100 die "$usage: $0 -d #\n" if $opt_d !~ /^\d+$/;
1102 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
1103 while (<$fexlist>) {
1104 if (/^to (.+\@.+) :/) {
1106 } elsif (/^\s*(\d+)\) (\w+) (.+)/ and $1 eq $opt_d) {
1107 serverconnect($server,$port) unless $SH;
1110 "GET $proxy_prefix/fop/$2/$2?DELETE HTTP/1.1",
1111 "User-Agent: $useragent",
1115 print "<-- $_" if $opt_v;
1116 if (/^HTTP.* 200/) {
1119 last if /^\n/; # ignore HTML output
1120 print "<-- $_" if $opt_v;
1121 if (/^X-File:.*\/(.+)/) {
1122 printf "%s deleted\n",decode_utf8(urldecode($1));
1126 } elsif (s:HTTP/[\d\. ]+::) {
1127 die "$0: server response: $_";
1129 die "$0: no response from fex server $server\n";
1135 sleep 1; # do not overrun server
1146 my ($data,$aname,$alias);
1153 if ($from =~ /^SUBUSER|GROUPMEMBER$/) {
1156 # look for single @ in arguments
1157 for (my $i=1; $i<$#ARGV; $i++) {
1158 if ($ARGV[$i] eq '@') {
1159 $ARGV[$i] = join(',',@ARGV[$i+1 .. $#ARGV]);
1164 $to = pop @ARGV or die $usage;
1167 $nomail = $opt_C ||= 'NOMAIL';
1171 $nomail = $opt_C ||= 'NOMAIL';
1173 if ($opt_g and $to =~ /,/) {
1174 die "$0: encryption is supported to only one recipient\n";
1176 if ($to =~ m{^https?://.*/fup\?skey=(\w+)}) {
1181 if ($to =~ m{^https?://.*/fup\?gkey=(\w+)}) {
1182 $from = 'GROUPMEMBER';
1187 @to = split(',',lc($to));
1189 die $usage unless @ARGV or $opt_a or $opt_s;
1190 die $usage if $opt_s and @ARGV;
1192 # early serverconnect necessary for X-Features info
1193 serverconnect($server,$port);
1197 sendheader("$server:$port","OPTIONS FEX HTTP/1.1");
1200 die "$0: no response from fex server $server\n" unless $_;
1201 print "<-- $_" if $opt_v;
1202 if (/^HTTP.* 201/) {
1205 print "<-- $_" if $opt_v;
1207 $aok = $_ if /X-Features:.*ANONYMOUS/;
1209 die "$0: no anonymous support on server $server\n" unless $aok;
1211 die "$0: bad response from server $server : $_\n";
1216 query_sid($server,$port);
1218 if ($from eq 'SUBUSER') {
1220 # die "skey=$skey\nid=$id\nsid=$sid\n";
1223 if ($from eq 'GROUPMEMBER') {
1229 $opt_C ||= 'NOMAIL';
1230 } elsif ($to =~ m:^(//.*):) {
1232 if ($features =~ /XKEY/) {
1236 die "$0: server does not support XKEY\n";
1238 } elsif (grep /^[^@]*$/,@to and not $skey and not $gkey) {
1239 %AB = query_address_book($server,$port,$from);
1241 serverconnect($server,$port);
1242 query_sid($server,$port);
1245 # alias in local config?
1247 if ($alias{$to} =~ /(.+?):(.+)/) {
1252 # special extra upload
1253 system $0,split(/\s/,$opt),@argv,$ato;
1259 # alias in server address book?
1261 # do not substitute alias with expanded addresses because then
1262 # keep and autodelete options from address book will get lost
1265 # look for mutt aliases
1266 elsif ($to !~ /@/ and $to ne $from) {
1267 $to = get_mutt_alias($to);
1272 $to = join(',',grep /./,@to) or exit;
1273 # warn "Server/User: $fexcgi/$from\n" unless $opt_q;
1276 not $skey and not $gkey
1278 and $features =~ /CHECKRECIPIENT/
1279 and $opt_C !~ /^(DELETE|LIST|RECEIVEDLOG|SENDLOG|FOPLOG)$/
1281 checkrecipient($from,$to);
1283 serverconnect($server,$port);
1284 query_sid($server,$port);
1289 if (@ARGV > 1 and not ($opt_a or $opt_s or $opt_d)) {
1290 print "Archive name (name.tar, name.tgz or name.zip) or [ENTER] to send file for file:\n";
1299 $opt_s =~ s/[^\w_.+-]/_/g;
1304 $opt_a =~ s/[^\w_.+-]/_/g;
1305 if ($opt_a =~ /(.+)\.(zip|tar|tgz|7z)$/) {
1309 die "$0: archive name must be one of ".
1310 "$opt_a.tar $opt_a.tgz $opt_a.zip\n";
1312 # no file argument left?
1314 # use file name as archive name
1319 foreach my $file (@ARGV) {
1320 die "$0: cannot read $file\n" unless -l $file or -r $file;
1322 $opt_a .= ".$atype" if $opt_a !~ /\.$atype$/;
1323 $transferfile = "$tmpdir/$opt_a";
1324 unlink $transferfile;
1325 print "Making fex archive ($opt_a):\n";
1326 if ($atype eq 'zip') {
1328 # if ($opt_c) { system(qw'7z a -tzip',$transferfile,@ARGV) }
1329 # else { system(qw'7z a -tzip -mm=copy',$transferfile,@ARGV) }
1330 system(qw'7z a -tzip',$transferfile,@ARGV);
1331 @files = ($transferfile);
1333 # zip archives must be < 2 GB, so split as necessary
1334 @files = zipsplit($transferfile,@ARGV);
1335 if (scalar(@files) == 1) {
1336 $transferfile = $files[0];
1337 $transferfile =~ s/_1.zip$/.zip/;
1338 rename $files[0],$transferfile;
1339 @files = ($transferfile);
1342 @transferfiles = @files;
1343 } elsif ($atype eq '7z') {
1344 # http://www.7-zip.org/
1345 my @X = (); # exclude list
1347 foreach my $x (split('#',${'opt_#'})) {
1351 if ($opt_c) { system(qw'7z a',@X,$transferfile,@ARGV) }
1352 else { system(qw'7z a -t7z -mx0',@X,$transferfile,@ARGV) }
1353 @transferfiles = @files = ($transferfile);
1354 } elsif ($atype eq 'tar') {
1356 system(qw'7z a -ttar',$transferfile,@ARGV);
1357 @transferfiles = @files = ($transferfile);
1359 ## tar is now handled by formdatapost()
1360 # system(qw'tar cvf',$transferfile,@ARGV);
1363 } elsif ($atype eq 'tgz') {
1365 die "$0: archive type tgz not available, use tar, zip or 7z\n";
1367 ## tgz is now handled by formdatapost()
1368 # system(qw'tar cvzf',$transferfile,@ARGV);
1372 die "$0: unknown archive format \"$atype\"\n";
1375 if (@transferfiles) {
1377 # error in making transfer archive?
1379 unlink @transferfiles;
1380 die "$0: $! - aborting upload\n";
1383 # maybe timeout, so make new connect
1384 if (time-$t0 >= $timeout) {
1385 serverconnect($server,$port);
1386 query_sid($server,$port) unless $anonymous;
1406 die "$0: $file is not a regular file, try option -a\n"
1408 die "$0: $file does not exist\n";
1411 die "$0: cannot read $file\n" unless -r $file;
1418 foreach my $file (@files) {
1419 my @s = stat($file);
1420 unless (@s and ($s[2] & S_IROTH) and -r $file) {
1421 die "$0: $file is not world readable\n";
1426 foreach my $file (@files) {
1427 sleep 1; # do not overrun server!
1428 unless (-s $file or $opt_d or $opt_a or $opt_s) {
1429 die "$0: cannot send empty file $file\n";
1431 female_mode("send file $file?") if $opt_F;
1440 autodelete => $opt_D,
1443 if (not @r or not grep /\w/,@r) {
1444 die "$0: no response from server\n";
1446 if (($r) = grep /^ERROR:/,@r) {
1447 if ($anonymous and $r =~ /purge it/) {
1448 die "$0: file is already on server for $to - use another anonymous recipent\n";
1452 die "$0: server error: $r\n";
1455 if (($r) = grep /<h3>\Q$file/,@r) {
1459 if ($opt_a !~ /^afex_\d+\.tar$/ and $file !~ /afex_\d+\.tar$/) {
1460 # print grep({s/^(X-Recipient:.*\((.+)\))/Parameters: $2\n/i} @r);
1462 my ($recipient,$location);
1464 if (/^(X-)?(Recipient.*)/i) {
1466 if (/notification=no/i) { $nonot = 1 }
1469 if (/^(X-)?(Location.*)/i) {
1471 if ($from eq $to or $from =~ /^\Q$to\E@/i
1472 or $nomail or $anonymous or $nonot) {
1473 print "$recipient\n";
1474 print "$location\n";
1478 unless ($opt_d or $location) {
1479 if (scalar(@r) == 1) {
1480 die "$0: server error: @r\n";
1482 if ($r[0] !~ /HTTP.1.. 2/ and $r[0] =~ /HTTP.[\s\d.]+(.+)/) {
1483 die "$0: server error: $1\n";
1485 die "$0: server error:\n".join("\n",@r)."\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 '.';
1515 if ($to !~ /@/ and $to ne $from) {
1516 $to = get_mutt_alias($to);
1519 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
1520 while (<$fexlist>) {
1521 if (/^\s*(\d+)\) (\w+) \[\d+ d\] (.+)/ and $1 eq $opt_f) {
1525 if ($file =~ s/ "(.*)"$//) {
1526 $opt_C ||= $1 if $1 ne 'NOMAIL';
1534 die "$0: file #$opt_f not found in fexlist\n";
1537 female_mode("forward file #$opt_f?") if $opt_F;
1539 serverconnect($server,$port);
1540 query_sid($server,$port);
1542 $req = "GET $proxy_prefix/fup?"
1543 ."from=$from&ID=$sid&to=$to&dkey=$dkey&command=FORWARD";
1544 $req .= "&comment=$opt_C" if $opt_C;
1545 $req .= "&keep=$opt_k" if $opt_k;
1546 $req .= "&autodelete=$opt_D" if $opt_D;
1547 $req .= "&$opt_X" if $opt_X;
1548 $req .= " HTTP/1.1";
1549 sendheader("$server:$port",$req);
1552 $fp =~ s/[^\w_.-]/.+/g; # because of UTF8 filename
1555 $status = 0 if /"$fp"/;
1556 print if $opt_v or /"$fp"/;
1560 die "$0: server failed, rerun command with option -v\n";
1568 my ($to,$n,$dkey,$file,$req,$recipient);
1571 die $usage if @ARGV;
1573 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
1574 while (<$fexlist>) {
1575 if (/^\s*(\d+)\) (\w+) \[\d+ d\] (.+)/ and $1 eq $opt_N) {
1584 die "$0: file #$opt_N not found in fexlist\n";
1587 female_mode("resend notification for file #$opt_N?") if $opt_F;
1589 serverconnect($server,$port);
1590 query_sid($server,$port);
1592 $req = "GET $proxy_prefix/fup?"
1593 ."from=$from&ID=$sid&dkey=$dkey&command=RENOTIFY"
1595 sendheader("$server:$port",$req);
1599 print "<-- $_" if $opt_v;
1601 if (/^X-Notify: (.+)\/(.+)\/(.+)/) {
1608 print "notification e-mail for $file has been resent to $recipient\n";
1611 die "$0: server failed\n";
1613 die "$0: server failed, rerun command with option -v\n";
1623 my ($n,$dkey,$file,$req);
1626 die $usage if @ARGV;
1627 die $usage unless $opt_C or $opt_k or $opt_D;
1629 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
1630 while (<$fexlist>) {
1631 if (/^\s*(\d+)\) (\w+) \[\d+ d\] (.+)/ and $1 eq $opt_x) {
1635 $file =~ s/ "(.*)"$//;
1642 die "$0: file #$opt_x not found in fexlist\n";
1645 female_mode("modify file #$opt_x?") if $opt_F;
1647 serverconnect($server,$port);
1648 query_sid($server,$port);
1650 $req = "GET $proxy_prefix/fup?"
1651 ."from=$from&ID=$sid&dkey=$dkey&command=MODIFY";
1652 $req .= "&comment=$opt_C" if $opt_C;
1653 $req .= "&keep=$opt_k" if $opt_k;
1654 $req .= "&autodelete=$opt_D" if $opt_D;
1655 $req .= " HTTP/1.1";
1656 sendheader("$server:$port",$req);
1671 my $transferfile = shift;
1675 # get transfer file from FEX server
1677 serverconnect($server,$port);
1678 query_sid($server,$port);
1681 xxget($from,$sid,$transferfile);
1684 unless (-s $transferfile) {
1685 unlink $transferfile;
1689 # no further processing if delivering to pipe
1690 exec 'cat',$transferfile unless -t STDOUT;
1692 if ($ft = `file $transferfile 2>/dev/null`) {
1693 if ($ft =~ /compressed/) {
1694 rename $transferfile,"$transferfile.gz";
1695 shelldo(ws("gunzip $transferfile.gz"));
1697 $ft = `file $transferfile`;
1699 # file command failed, so we look ourself into the file...
1700 elsif (open $transferfile,$transferfile) {
1701 read $transferfile,$_,4;
1702 close $transferfile;
1704 if (/\x1F\x8B\x08\x00/) {
1705 rename $transferfile,"$transferfile.gz";
1706 shelldo(ws("gunzip $transferfile.gz"));
1708 $ft = 'tar archive';
1711 if ($ft =~ /tar archive/) {
1712 rename $transferfile,"$transferfile.tar";
1713 $transferfile .= '.tar';
1717 print "Files in transfer-container:\n\n";
1718 shelldo(ws("tar tvf $transferfile"));
1719 print "\nExtract these files? [Yn] ";
1723 print "keeping $transferfile\n";
1725 my $untar = "tar xvf";
1726 # if ($> == 0 and `tar --help 2>&1` =~ /gnu/) {
1727 # $untar = "tar --no-same-owner -xvf";
1729 system("$untar $transferfile && rm $transferfile");
1730 die "$0: error while untaring, see $transferfile\n" if -f $transferfile;
1733 exec 'cat',$transferfile;
1741 my ($boundary,$filename,$filesize,$length,$buf,$file,$fpsize,$resume,$seek);
1743 my (@hh,@hb,@r,@pv,$to);
1745 my ($t0,$t1,$t2,$tt,$tc);
1746 my $bs = 2**16; # blocksize for reading and sending file
1747 my $fileid = int(time);
1749 my $connection = '';
1751 my ($tar,$aname,$atype,$tarlist,$tarerror,$location,$transferfile);
1754 if (defined($file = $P{file})) {
1756 $to = $AB{$P{to}} || $P{to}; # for gpg
1758 # special file: stream from STDIN
1760 $filename = encode_utf8($file);
1768 $if =~ s/([^_\w\.\-])/\\$1/g;
1769 $transferfile = $tmpdir . '/' . basename($file) . '.gz';
1770 $of = $transferfile;
1771 $of =~ s/([^_\w\.\-])/\\$1/g;
1772 shelldo("gzip <$if>$of");
1773 $filesize = -s $transferfile;
1774 die "$0: cannot gzip $file\n" unless $filesize;
1775 $file = $transferfile;
1778 # special file: tar-on-the-fly
1779 if (not $windoof and $opt_a and $file =~ /(.+)\.(tar|tgz)$/) {
1782 $tarlist = "$tmpdir/$aname.list";
1783 $tarerror = "$tmpdir/$aname.error";
1785 $tar .= 'z' if $atype eq 'tgz';
1786 if (`tar --help 2>/dev/null` =~ /--index-file/) {
1787 $tar .= " --index-file=$tarlist -f-";
1792 foreach my $x (split('#',${'opt_#'})) {
1793 $tar .= " --exclude=$x";
1798 $file =~ s/([^\w\-\@\#%,.=+~_:])/\\$1/g;
1801 # print "calculating archive size... ";
1802 open $tar,"$tar 2>$tarerror|" or die "$0: cannot run tar - $!\n";
1803 $t0 = int(time) if -t STDOUT;
1804 while ($b = read $tar,$_,$bs) {
1809 printf "Archive size: %d MB\r",int($filesize/M);
1814 printf "Archive size: %d MB\n",int($filesize/M) if -t STDOUT;
1815 unless (close $tar) {
1817 if (open $tarerror,$tarerror) {
1822 unlink $tarlist,$tarerror;
1823 die "$0: tar error:\n$_";
1825 $file = "$aname.$atype";
1826 $filename = encode_utf8($file);
1827 undef $SH; # force reconnect (timeout!)
1832 $filename = encode_utf8(${'opt_='} || $file);
1835 $filename =~ s/^[a-z]://;
1836 $filename =~ s/.*\\//;
1838 $filename =~ s:.*/::;
1839 $filename =~ s:[\r\n]+: :g;
1842 } elsif (not $opt_g and not $opt_s) {
1843 $filesize = -s $file or die "$0: $file is empty or not readable\n";
1847 $filename .= '.gpg' if $opt_g;
1852 $fileid = int(time);
1855 $fileid = md5_hex(fmd(@ARGV));
1857 $fileid = fileid($file);
1863 $file = $filename = '';
1869 @hh = (); # HTTP header
1870 @hb = (); # HTTP body
1877 serverconnect($server,$port);
1878 query_sid($server,$port) unless $anonymous;
1881 $P{id} = $sid; # ugly hack!
1883 # ask server if this file has been already sent
1884 if ($file and not $xx and not
1885 ($opt_s or $opt_g or $opt_o or $opt_d or $opt_l or $opt_L or ${'opt_/'}))
1887 ($seek,$location) = query_file($server,$port,$frecipient||$P{to},$P{from},
1888 $P{id},$filename,$fileid);
1889 if ($filesize == $seek) {
1890 print "Location: $location\n" if $location and $nomail;
1891 warn "$0: $file has been already transferred\n";
1893 } elsif ($seek and $seek < $filesize) {
1894 $resume = " (resuming at byte $seek)";
1895 } elsif ($filesize <= $seek) {
1899 sleep 1; # do not overrun proxy
1900 serverconnect($server,$port);
1905 if ($chunksize and $proxy and $port != 443
1906 and $filesize - $seek > $chunksize - $bs) {
1907 if ($features !~ /MULTIPOST/) {
1908 die sprintf("$0: server does not support chunked multi-POST needed for"
1909 ." files > %d MB via proxy\n",$chunksize/M);
1911 $opt_o = 0; # no overwriting mode for next chunks
1912 $fpsize = $chunksize - $bs;
1914 $fpsize = $filesize - $seek;
1917 $boundary = randstring(48);
1920 $P{filesize} = $filesize;
1922 # send HTTP POST variables
1925 @pv = qw'from to skey keep autodelete comment seek filesize';
1928 @pv = qw'from to gkey keep autodelete comment seek filesize';
1930 @pv = qw'from to id replyto keep autodelete comment command seek filesize';
1932 foreach my $v (@pv) {
1935 push @hb,"--$boundary";
1936 push @hb,"Content-Disposition: form-data; name=\"$name\"";
1938 push @hb,encode_utf8($P{$v});
1942 # at last, POST the file
1944 push @hb,"--$boundary";
1945 push @hb,"Content-Disposition: form-data; name=\"FILE\"; filename=\"$filename\"";
1947 if ($opt_M) { push @hb,"Content-Type: application/x-mime" }
1948 else { push @hb,"Content-Type: application/octet-stream" }
1950 $flink = abs_path($file);
1951 push @hb,"Content-Location: $flink";
1953 # push @hb,"Content-Length: " . ((-s $file||0) - $seek); # optional header!
1954 push @hb,"Content-Length: $fpsize"; # optional header! NOT filesize!
1955 push @hb,"X-File-ID: $fileid";
1960 # prevent proxy chunked mode reply
1961 $connection = "close";
1964 push @hb,"--$boundary--";
1969 $length = length(join('',@hb)) + scalar(@hb)*2 + $fpsize;
1972 if ($file and not $opt_d) {
1973 if ($flink) { $hb[-2] = $flink }
1974 else { $hb[-2] = '(file content)' }
1976 # any other extra URL arguments
1978 $opt_X = "?$::opt_X" if $::opt_X and $file;
1981 push @hh,"POST $proxy_prefix/fup$opt_X HTTP/1.1";
1982 push @hh,"Host: $server:$port";
1983 push @hh,"User-Agent: $useragent";
1984 push @hh,"Content-Length: $length";
1985 push @hh,"Content-Type: multipart/form-data; boundary=$boundary";
1986 push @hh,"Connection: $connection" if $connection;
1990 print "--> $_\n" foreach (@hh,@hb);
1993 $SIG{PIPE} = \&sigpipehandler;
1994 # foreach $sig (keys %SIG) {
1995 # eval '$SIG{$sig} = sub { print "\n!!! SIGNAL '.$sig.' !!!\n"; exit; }';
2000 pop @hb unless $flink;
2001 nvtsend(@hh,@hb) or do {
2002 warn "$0: server has closed the connection, reconnecting...\n";
2004 goto FORMDATAPOST; # necessary: new $sid ==> new @hh
2007 unless ($opt_d or $flink) {
2009 $t0 = $t2 = int(time);
2016 open $file,"gpg -e -r $to|" or die "$0: cannot run gpg - $!\n";
2018 open $file,'>&=STDIN' or die "$0: cannot open STDIN - $!\n";
2022 open $file,"$tar|gpg -e -r $to|" or die "$0: cannot run tar&gpg - $!\n";
2024 open $file,"$tar|" or die "$0: cannot run tar - $!\n";
2028 if (defined $tpid and $tpid == 0) {
2030 if (open $tarlist,$tarlist) {
2031 # print "\n$tar|\n"; system "ls -l $tarlist";
2033 while (<$tarlist>) {
2034 print ' 'x(length($file)+40),"\r",$_;
2041 $SIG{CHLD} = 'IGNORE';
2044 print "Fast forward to byte $seek (resuming)\n";
2045 readahead($file,$seek);
2050 $fileq =~ s/([^\w\-\@\#%,.=+~_:])/\\$1/g;
2051 open $file,"gpg -e -r $to <$fileq|" or die "$0: cannot run gpg - $!\n";
2053 open $file,$file or die "$0: cannot read $file - $!\n";
2062 print $rcamel[0] if ${'opt_+'};
2064 $SIG{ALRM} = sub { retry("timed out") };
2065 while (my $b = read $file,$buf,$bs) {
2068 print {$SH} $buf or &sigpipehandler;
2070 syswrite $SH,$buf or &sigpipehandler;
2074 if ($filesize > 0 and $bytes+$seek > $filesize) {
2075 die "$0: $file filesize has grown while uploading\n";
2079 if (${'opt_+'} and int($t2*10)>$tc) {
2080 print $rcamel[$tc%2+1];
2083 if (not $opt_q and -t STDOUT and int($t2)>$t1) {
2084 &sigpipehandler unless $SH->connected;
2085 # smaller block size is better on slow links
2086 $bs = 4096 if $t1 and $bs>4096 and $bytes/($t2-$t0)<65536;
2087 if ($filesize > 0) {
2088 $pct = sprintf "(%d%%)",int(($bytes+$seek)/$filesize*100);
2090 if ($bytes>2*M and $bs>4096) {
2091 printf STDERR "%s: %d MB of %d MB %s %d kB/s \r",
2092 $opt_s||$opt_a||$file,
2093 int(($bytes+$seek)/M),
2096 int($bt/k/($t2-$tt));
2098 printf STDERR "%s: %d kB of %d MB %s %d kB/s \r",
2099 $opt_s||$opt_a||$file,
2100 int(($bytes+$seek)/k),
2103 int($bt/k/($t2-$tt));
2106 # time window for transfer rate calculation
2112 last if $filesize > 0 and $bytes >= $fpsize;
2113 sleep 1 while ($opt_m and $bytes/k/(time-$t0||1) > $opt_m);
2115 close $file; # or die "$0: error while reading $file - $!\n";
2118 print $rcamel[2] if ${'opt_+'};
2120 # terminate tar verbose output job
2128 if (not $chunksize and $bytes+$seek < $filesize) {
2129 die "$0: $file filesize has shrunk while uploading\n";
2132 if ($seek or $chunksize and $chunksize < $filesize) {
2134 printf STDERR "%s: %d MB in %d s (%d kB/s)",
2135 $opt_s||$opt_a||$file,
2139 if ($bytes+$seek == $filesize) {
2140 printf STDERR ", total %d MB\n",int($filesize/M);
2142 printf STDERR ", chunk #%d : %d MB\n",
2143 $chunk,int(($bytes+$seek)/M);
2146 printf STDERR "%s: %d kB in %d s (%d kB/s)",
2147 $opt_s||$opt_a||$file,
2151 if ($bytes+$seek == $filesize) {
2152 printf STDERR ", total %d kB\n",int($filesize/k);
2154 printf STDERR ", chunk #%d : %d kB\n",
2155 $chunk,int(($bytes+$seek)/k);
2160 printf STDERR "%s: %d MB in %d s (%d kB/s) \n",
2161 $opt_s||$opt_a||$file,
2166 printf STDERR "%s: %d kB in %d s (%d kB/s) \n",
2167 $opt_s||$opt_a||$file,
2174 if (-t STDOUT and not ($opt_s or $opt_g)) {
2175 print STDERR "waiting for server ok..."
2181 print {$SH} "\r\n--$boundary--\r\n";
2183 # special handling of streaming file because of stunnel tcp shutdown bug
2184 if ($opt_s or $opt_g) {
2187 serverconnect($server,$port);
2188 query_sid($server,$port) unless $anonymous;
2189 ($seek,$location) = query_file($server,$port,$P{to},$P{from},$sid,
2191 if ($seek != $bytes) {
2192 die "$0: streamed $bytes bytes but server received $seek bytes\n";
2194 return "X-Location: $location\n";
2200 printf STDERR "%s: %d MB\n",$flink,int($bytes/M);
2202 printf STDERR "%s: %d kB\n",$flink,int($bytes/k);
2210 # SuSe: Can't locate object method "BINMODE" via package "IO::Socket::SSL::SSL_HANDLE"
2211 # binmode $SH,':utf8';
2213 if (not $opt_q and $file and -t STDOUT) {
2214 print STDERR "\r \r";
2218 print "<-- $_\n" if $opt_v;
2219 last if @r and $r[0] =~ / 204 / and /^$/ or /<\/html>/i;
2220 push @r,decode_utf8($_);
2226 if ($proxy and $fpsize+$seek < $filesize) {
2237 my @rc = ('A'..'Z','a'..'z',0..9 );
2241 for (1..$n) { $rs .= $rc[int(rand($rn))] };
2247 my $zipbase = shift;
2251 my ($zsize,$size,$n);
2253 $zipbase =~ s/\.zip$//;
2254 map { s/([^_\w\+\-\.])/\\$1/g } @files;
2256 open my $ff,"find @files|" or die "$0: cannot search for @_ - $!\n";
2263 die "$0: too many zip-archives\n";
2266 while ($file = <$ff>) {
2268 # next if -l $file or not -f $file;
2269 next unless -f $file;
2271 if ($size > 2147480000) {
2273 die "$0: $file too big for zip\n";
2275 if ($zsize + $size > 2147000000) {
2276 push @zipfiles,zip($zipbase.'_'.$n.'.zip',@files);
2287 push @zipfiles,zip($zipbase.'_'.$n.'.zip',@files);
2299 # if ($opt_c) { $cmd = "zip -@ $zip" }
2300 # else { $cmd = "zip -0 -@ $zip" }
2301 $cmd = "zip -@ $zip";
2303 ${'opt_#'} =~ s/#/ /g;
2304 $cmd .= " -x ".${'opt_#'};
2306 print $cmd,"\n" if $opt_v;
2307 open $cmd,"|$cmd" or die "$0: cannot create $zip - $!\n";
2309 print {$cmd} $_."\n";
2310 print " $_\n" if $opt_v;
2312 close $cmd or die "$0: zip failed - $!\n";
2328 return $_ if length($_);
2335 my ($server,$port,$to,$from,$id,$filename,$fileid) = @_;
2338 my ($head,$location);
2339 my ($response,$fexsrv);
2344 $to = $AB{$to} if $AB{$to};
2345 $filename =~ s/([^_=:,;<>()+.\w\-])/'%'.uc(unpack("H2",$1))/ge; # urlencode
2347 $head = "HEAD $proxy_prefix/fop/$to/$from/$filename??SKEY=$id HTTP/1.1";
2349 $head = "HEAD $proxy_prefix/fop/$to/$from/$filename??GKEY=$id HTTP/1.1";
2351 $head = "HEAD $proxy_prefix/fop/$to/$from/$filename??ID=$id HTTP/1.1";
2353 sendheader("$server:$port",$head);
2355 unless (defined $_ and /\w/) {
2356 die "$0: no response from server\n";
2359 print "<-- $_" if $opt_v;
2360 unless (/^HTTP.* 200/) {
2365 print "<-- $_" if $opt_v;
2366 $fexsrv = $_ if /^(Server: fexsrv|X-Features:)/;
2369 die "$0: no fexserver at $server:$port\n" unless $fexsrv;
2370 die "$0: server response: $response";
2374 print "<-- $_" if $opt_v;
2376 if (/^Content-Length:\s+(\d+)/) { $seek = $1 }
2377 if (/^X-File-ID:\s+(.+)/) { $qfileid = $1 }
2378 if (/^X-Features:\s+(.+)/) { $features = $1 }
2379 if (/^X-Location:\s+(.+)/) { $location = $1 }
2382 # return true seek only if file is identified
2383 $seek = 0 if $qfileid and $qfileid ne $fileid;
2385 return ($seek,$location);
2389 sub edit_address_book {
2392 my $ab = "$fexhome/ADDRESS_BOOK";
2396 die "$0: address book not available for subusers\n" if $skey;
2397 die "$0: address book not available for group members\n" if $gkey;
2399 female_mode("edit your address book?") if $opt_F;
2401 %AB = query_address_book($server,$port,$user);
2402 if ($AB{ADDRESS_BOOK} !~ /\w/) {
2404 "# Format: alias e-mail-address # Comment\n".
2406 "framstag framstag\@rus.uni-stuttgart.de\n";
2408 open $ab,">$ab" or die "$0: cannot write to $ab - $!\n";
2409 print {$ab} $AB{ADDRESS_BOOK};
2417 serverconnect($server,$port);
2418 query_sid($server,$port);
2431 sub query_address_book {
2432 my ($server,$port,$user) = @_;
2433 my ($req,$alias,$address,$options,$comment,$cl,$ab,$b);
2438 serverconnect($server,$port);
2439 query_sid($server,$port);
2442 $req = "GET $proxy_prefix/fop/$user/$user/ADDRESS_BOOK?ID=$sid HTTP/1.1";
2443 sendheader("$server:$port",$req);
2445 unless (defined $_ and /\w/) {
2446 die "$0: no response from server\n";
2449 print "<-- $_" if $opt_v;
2450 unless (/^HTTP.* 200/) {
2451 if (/^HTTP.* 404/) {
2452 while (<$SH>) { last if /^\r?\n/ }
2455 # s:HTTP/[\d\. ]+::;
2456 # die "$0: server response: $_";
2464 print "<-- $_" if $opt_v;
2466 $cl = $1 if /^Content-Length: (\d+)/;
2476 print "<-- $_\n" if $opt_v;
2480 ($alias,$address,$options) = split;
2482 if ($options) { $options =~ s/[()]//g }
2483 else { $options = '' }
2484 $AB{$alias} = $address;
2485 $AB{$alias}->{options} = $options||'';
2486 $AB{$alias}->{comment} = $comment||'';
2487 if ($options and $options =~ /keep=(\d+)/i) {
2488 $AB{$alias}->{keep} = $1;
2490 if ($options and $options =~ /autodelete=(\w+)/i) {
2491 $AB{$alias}->{autodelete} = $1;
2499 $AB{ADDRESS_BOOK} = $ab;
2505 # sets global $sid $features $timeout # ugly hack! :-}
2507 my ($server,$port) = @_;
2514 return if $features; # early return if we know enough
2515 $req = "OPTIONS FEX HTTP/1.1";
2517 return if $features; # early return if we know enough
2518 $req = "GET $proxy_prefix/SID HTTP/1.1";
2520 $req = "GET SID HTTP/1.1";
2523 sendheader("$server:$port",$req,"User-Agent: $useragent");
2525 unless (defined $_ and /\w/) {
2526 print "\n" if $opt_v;
2527 die "$0: no response from server\n";
2530 print "<-- $_" if $opt_v;
2532 if (/^HTTP.* [25]0[01] /) {
2533 if (not $proxy and $port ne 443 and /^HTTP.* 201 (.+)/) {
2534 $sid = 'MD5H:'.md5_hex($id.$1);
2538 print "<-- $_" if $opt_v;
2539 $features = $1 if /^X-Features: (.+)/;
2540 $timeout = $1 if /^X-Timeout: (\d+)/;
2543 } elsif (/^HTTP.* 301 /) {
2544 while (<$SH>) { last if /Location/ }
2545 die "$0: cannot use $server:$port because server has a redirection to\n".$_;
2547 # no SID support - perhaps transparent web proxy?
2550 print "<-- $_" if $opt_v;
2551 $fexsrv = $_ if /^(Server: fexsrv|X-Features:)/;
2554 die "$0: no fexserver at $server:$port\n" unless $fexsrv;
2555 serverconnect($server,$port);
2559 # warn "proxy: $proxy\n";
2561 serverconnect($server,$port);
2569 my ($from,$id,$save) = @_;
2572 my ($url,$B,$b,$t0,$t1,$cl);
2577 $url = "$proxy_prefix/fop/$from/$from/$xx?ID=$id";
2579 sendheader("$server:$port","GET $url HTTP/1.0","User-Agent: $useragent");
2583 print "<-- $_" if $opt_v;
2584 $cl = $1 if /^Content-Length:\s(\d+)/;
2585 # $ft = $1 if /^X-File-Type:\s(.+)/;
2589 die "$0: no Content-Length in server-reply\n" unless $cl;
2591 open F,">$save" or die "$0: cannot write to $save - $!\n";
2594 $t0 = $t1 = int(time);
2597 while ($b = read($SH,$_,$bs)) {
2600 if (int(time) > $t1) {
2604 print STDERR $ts,"\r";
2608 sleep 1 while ($opt_m and $B/k/(time-$t0||1) > $opt_m);
2611 print STDERR ts($B,$cl),"\n";
2619 return sprintf("transferred: %d MB (%d%%)",int($b/M),int($b/$tb*100));
2623 sub sigpipehandler {
2629 local $SIG{ALRM} = sub { };
2635 kill 9,$tpid if $tpid;
2636 if (@r and $opt_v) {
2637 die "\n$0: ($$) server error: @r\n";
2639 if (@r and $r[0] =~ /^HTTP.* \d+ (.*)/) {
2640 die "\n$0: server error: $1\n";
2644 warn "\n$0: connection to $server $reason\n";
2645 warn "retrying after $timeout seconds...\n";
2647 if ($windoof) { exec $^X,$0,@_ARGV }
2648 else { exec $_0,@_ARGV }
2653 sub checkrecipient {
2654 my ($from,$to) = @_;
2662 command => 'CHECKRECIPIENT',
2665 $_ = shift @r or die "$0: no reply from server\n";
2670 if (s/X-(Recipient: .+)/$1\n/) {
2671 s/autodelete=\w+/autodelete=$opt_D/ if $opt_D;
2672 s/keep=\d+/keep=$opt_k/ if $opt_k;
2674 $frecipient ||= (split)[1];
2678 http_response($_,@r);
2683 # get ID data from ID file
2687 $fexcgi = getline($idf) || die "$0: no FEX-URL in $idf\n";
2688 $from = getline($idf) || die "$0: no FROM in $idf\n";
2689 $id = getline($idf) || die "$0: no ID in $idf\n";
2690 if ($fexcgi =~ s/!([\w.-]+:\d+)(:(\d+))?//) {
2692 $chunksize = $3 || 0;
2694 unless ($fexcgi =~ /^[_:=\w\-\.\/\@\%]+$/) {
2695 die "$0: illegal FEX-URL \"$fexcgi\" in $idf\n";
2697 unless ($from =~ /^[_:=\w\-\.\/\@\%\+]+$/) {
2698 die "$0: illegal FROM \"$from\" in $idf\n";
2708 print "file to send: ";
2709 chomp($file = <STDIN>);
2713 warn "$file does not exist\n";
2715 print "recipient (e-mail address): ";
2716 chomp($to = <STDIN>);
2717 die $usage unless $to;
2720 chomp($opt_C = <STDIN>);
2722 @ARGV = ($file,$to);
2727 if (system(@_) < 0) { die "failed: @_\n" }
2731 # emulate seek on a pipe
2733 my $fh = shift; # filehandle
2734 my $ba = shift; # bytes ahead
2742 $n = $bs if $n > $bs;
2743 $s += read $fh,$_,$n;
2748 # fileid is inode and mtime
2750 my @s = stat(shift);
2751 return @s ? $s[1].$s[9] : int(time);
2755 sub get_mutt_alias {
2757 my $ma = $HOME.'/.mutt/aliases';
2761 open $ma,$ma or return $to;
2763 if (/^alias \Q$to\E\s/i) {
2771 warn "$0: ignoring mutt multi-alias $to = $alias\n";
2776 warn "$0: found mutt alias $to = $alias\n";
2782 return ($alias||$to);
2786 # collect file meta data (filename, inode, mtime)
2792 foreach $file (@files) {
2793 if (not -l $file and -d $file) {
2795 if (opendir $dir,$dir) {
2796 while (defined ($file = readdir($dir))) {
2797 next if $file eq '..';
2799 $fmd .= $file.fileid($dir);
2801 $fmd .= fmd("$dir/$file");
2807 $fmd .= $file.fileid($file);
2815 # from MIME::Base64::Perl
2821 tr|A-Za-z0-9+=/||cd;
2823 tr|A-Za-z0-9+/| -_|;
2824 return "" unless length;
2827 for ($i = 0; $i <= $l; $i += 60) {
2828 $uu .= "M" . substr($_,$i,60);
2832 $uu .= chr(32+(length)*3/4) . $_;
2834 return unpack("u",$uu);
2840 if (open my $tty,'/dev/tty') {
2844 " [p] perhaps - don't know\n",
2848 if (/^y/i) { return }
2850 if (/^p/i) { int(rand(2)) ? return : exit }
2857 local $_ = shift || <$SH>;
2861 $_ = <$SH> unless $_;
2862 unless (defined $_ and /\w/) {
2863 die "$0: no response from server\n";
2865 print "<-- $_\n" if $opt_v;
2867 # CGI fatalsToBrowser
2868 if (/^HTTP.* 500/) {
2869 @r = <$SH> unless @r;
2871 die "$0: server error: $_\n@r\n";
2873 unless (/^HTTP.* 200/) {
2875 $error =~ s/HTTP.[\s\d.]+//;
2876 @r = <$SH> unless @r;
2880 $error .= "\n".$_ if /^Location/;
2881 print "<-- $_\n" if $opt_v;
2883 die "$0: server error: $error\n";
2886 print "<-- $_\n" if $opt_v;
2898 my $cfb = '### common functions ###';
2903 open $0,$0 or die "cannot read $0 - $!\n";
2909 foreach my $p (qw(fexget sexsend)) {
2910 open $p,$p or die "cannot read $p - $!\n";
2913 s/\n$cfb.*/\n$cfb\n$cfc/s;
2915 open $p,'>',$p or die "cannot write $p - $!\n";
2920 exec "l $0 fexget sexsend";
2924 ### common functions ###
2928 my @d = localtime((stat shift)[9]);
2929 return sprintf('%d%02d%02d',$d[5]+1900,$d[4]+1,$d[3]);
2935 s/\%([a-f\d]{2})/chr(hex($1))/ige;
2941 # set SSL/TLS options
2942 $SSL{SSL_verify_mode} = $ENV{SSLVERIFY} if defined($ENV{SSLVERIFY});
2943 foreach my $opt (qw(
2952 $SSL{$opt} = $ENV{$env} if defined($ENV{$env});
2955 if ($SSL{SSL_verify_mode}) {
2957 unless ($SSL{SSL_ca_path} or $SSL{SSL_ca_file}) {
2958 die "$0: \$SSLVERIFYMODE, but not valid \$SSLCAPATH or \$SSLCAFILE\n";
2960 } elsif (defined($SSL{SSL_verify_mode})) {
2961 # user has set SSLVERIFY=0 !
2964 $SSL{SSL_verify_mode} = 1 if $SSL{SSL_ca_path} or $SSL{SSL_ca_file};
2970 return if $SSL{SSL_ca_file} or $SSL{SSL_ca_path};
2971 foreach (qw(/etc/ssl/certs/ca-certificates.crt)) {
2973 $SSL{SSL_ca_file} = $_;
2977 foreach (qw(/etc/ssl/certs /etc/pki/tls/certs)) {
2979 $SSL{SSL_ca_path} = $_;
2987 my ($server,$port) = @_;
2988 my $connect = "CONNECT $server:$port HTTP/1.1";
2992 tcpconnect(split(':',$proxy));
2994 printf "--> %s\n",$connect if $opt_v;
2995 nvtsend($connect,"");
2998 printf "<-- $_"if $opt_v;
2999 unless (/^HTTP.1.. 200/) {
3000 die "$0: proxy error : $_";
3003 $SH = IO::Socket::SSL->start_SSL($SH,%SSL);
3006 tcpconnect($server,$port);
3008 # if ($https and $opt_v) {
3009 # printf "%s\n",$SH->get_cipher();
3014 # set up tcp/ip connection
3016 my ($server,$port) = @_;
3024 # eval "use IO::Socket::SSL qw(debug3)";
3026 $SH = IO::Socket::SSL->new(
3027 PeerAddr => $server,
3033 $SH = IO::Socket::INET->new(
3034 PeerAddr => $server,
3043 die "$0: cannot connect $server:$port - $@\n";
3046 print "TCPCONNECT to $server:$port\n" if $opt_v;
3051 eval "use IO::Socket::SSL";
3052 die "$0: cannot load IO::Socket::SSL\n" if $@;
3053 eval '$SSL{SSL_verify_mode} = 0 if Net::SSLeay::SSLeay() <= 9470143';
3055 foreach my $v (keys %SSL) {
3056 printf "%s => %s\n",$v,$SSL{$v};
3067 push @head,"Host: $sp";
3069 foreach $head (@head) {
3070 print "--> $head\n" if $opt_v;
3071 print {$SH} $head,"\r\n";
3073 print "-->\n" if $opt_v;
3079 local $SIG{PIPE} = sub { $sigpipe = "@_" };
3083 die "$0: internal error: no active network handle\n" unless $SH;
3084 die "$0: remote host has closed the link\n" unless $SH->connected;
3086 foreach my $line (@_) {
3087 print {$SH} $line,"\r\n";
3098 # from MIME::Base64::Perl
3105 $res = join '',map(pack('u',$_)=~ /^.(\S*)/, ($_[0]=~/(.{1,45})/gs));
3106 $res =~ tr|` -_|AA-Za-z0-9+/|;
3107 $padding = (3-length($_[0])%3)%3;
3108 $res =~ s/.{$padding}$/'=' x $padding/e if $padding;