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 'md5_hex'; # encrypted ID / SID
23 use Time::HiRes '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,$macos,$useragent,$editor,$nomail);
35 our ($anonymous,$public);
36 our ($tpid,$frecipient);
37 our ($FEXID,$FEXXX,$HOME);
40 our $version = 20160919;
42 our $DEBUG = $ENV{DEBUG};
44 my %SSL = (SSL_version => 'TLSv1');
47 if ($Config{osname} =~ /^mswin/i) {
48 # http://slu.livejournal.com/17395.html
49 $windoof = $Config{osname};
50 $HOME = $ENV{USERPROFILE};
51 $fexhome = $ENV{FEXHOME} || $HOME.'\fex';
52 $tmpdir = $ENV{FEXTMP} || $ENV{TEMP} || "$fexhome\\tmp";
53 $idf = "$fexhome\\id";
54 $editor = $ENV{EDITOR} || 'notepad.exe';
55 $useragent = sprintf("fexsend-$version (%s %s)",
56 $Config{osname},$Config{archname});
57 $SSL{SSL_verify_mode} = 0;
58 } elsif ($Config{osname} =~ /^darwin/i or $ENV{MACOS}) {
59 # http://stackoverflow.com/questions/989349/running-a-command-in-a-new-mac-os-x-terminal-window
60 $macos = $Config{osname};
61 $HOME = (getpwuid($<))[7]||$ENV{HOME};
62 $fexhome = $HOME.'/.fex';
63 $tmpdir = $ENV{FEXTMP} || $ENV{TMPDIR} || "$fexhome/tmp";
67 $editor = $ENV{EDITOR} || 'open -W -n -e';
68 $_ = `sw_vers -productVersion 2>/dev/null`||'';
70 $useragent = "fexsend-$version (MacOS $_)";
73 $HOME = (getpwuid($<))[7]||$ENV{HOME};
74 $fexhome = $HOME.'/.fex';
75 $tmpdir = $ENV{FEXTMP} || "$fexhome/tmp";
78 $editor = $ENV{EDITOR} || 'vi';
79 $_ = `(lsb_release -d||uname -a)2>/dev/null`||'';
82 $useragent = "fexsend-$version ($_)";
85 if (-f ($_ = '/etc/fex/config.pl')) {
86 eval { require } or warn $@;
94 my $atype = ''; # archive type
95 my $fexcgi; # F*EX CGI URL
96 my @files; # files to send
97 my %AB = (); # server based address book
98 my ($server,$port,$sid,$https);
100 my $proxy_prefix = '';
102 my $timeout = 30; # server timeout
103 my $fexlist = "$tmpdir/fexlist";
105 my $xx = $0 =~ /\bxx$/;
108 $usage = "usage: send file(s): xx [:slot] file...\n".
109 " or: send STDIN: xx [:slot] -\n".
110 " or: send pipe: ... | xx [:slot] \n".
111 " or: get file(s) or STDIN: xx [:slot] \n".
112 " or: get file(s) no-questions: xx [:slot] --\n".
113 "examples: dmesg | xx\n".
116 " xx :conf /etc /boot\n";
119 usage: $0 [options] file(s) [@] recipient(s)
120 or: $0 [special options]
121 or: $0 -l [recipient-regexp]
122 or: $0 -f \# recipient(s)
123 or: $0 -x \# [-C -k -D -K -S]
124 options: -v verbose mode
125 -d delete file on fex server
126 -c compress file with gzip
127 -g encrypt file with gpg
128 -m limit limit throughput (kB/s)
129 -i account use ID data [account] from ID file
130 -C comment add comment to notification e-mail
131 -k max keep file max days on fex server
132 -D delay auto-delete after download
133 -K no auto-delete after download
134 -M MIME-file (to be displayed in recipient\'s webbrowser)
135 -o overwrite mode, do not resume
136 -a archive put files in archive (.zip .7z .tar .tgz)
137 -s stream read data from pipe and upload it with stream name
138 special options: -I initialize ID file or show ID
139 -I account add alternate ID data (secondary logins) to ID file
140 -l list sent files numbers (# needed for -f -x -d -N)
141 -f \# forward already uploaded file to another recipient
142 -x \# use -C -k -D -K for already uploaded file
143 -d \# delete file on fex server
144 -N \# resend notification e-mail
146 -T up:down test internet speed with up and down MBs
147 -A edit server address book (aliases)
148 -S show server/user settings and auth-ID
149 -H show hints, examples and more options
150 -V show version and ask for upgrade
151 (# is a file number, see output from $0 -l)
152 examples: $0 visualization.mpg framstag\@rus.uni-stuttgart.de
153 $0 -a images.zip *.jpg webmaster\@flupp.org,metoo
154 lshw | $0 -s hardware.list admin\@flupp.org
156 # or: $0 -R FEX-URL e-mail
157 # -R FEX mail self-register your e-mail address at FEX server
160 $0 hints and more options:
162 usage: $0 [options] file recipient(s)
164 Recipient can be a comma separated address list. Example:
165 $0 big.file framstag\@rus.uni-stuttgart.de,webmaster\@flupp.org
167 Recipient can be an alias from your server address book
168 (use "$0 -A" to edit it). Example:
171 Recipient can be a SKEY URL, which you have received from a regular F*EX user.
172 When using this URL you are a subuser of this full user and the file will be
173 sent to him. Example:
174 $0 big.file http://fex.rus.uni-stuttgart.de/fup?skey=4285f8cdd881626524fba686d5f0a83a
176 Recipient can be a GKEY URL, which you have received from a regular F*EX user.
177 Using this URL you are a member of his group and the file will be sent to all
178 members of this group. Example:
179 $0 big.file http://fex.rus.uni-stuttgart.de/fup?gkey=50d26547b1e8c1110beb8748fc1d9444
181 When you use "FEX-URL/anonymous" as recipient and your F*EX administrator has
182 allowed anonymous upload for your IP address then no auth-ID is needed.
184 "." as recipient means fex to yourself and show immediately the download URL
185 (no notification e-mail will be sent). Example:
188 "//" as recipient means fex to yourself and create extra short download URL.
192 If you want a Bcc of the notification e-mail then add '!bcc!' to the comment:
193 fexsend -C '!bcc! for me and you' ...
195 Additional special options:
197 -. sends a short instead of a detailed notification e-mail
198 -/ does not upload the file, but tells the server to link it
199 -= uses an alias name as file name
200 -# excludes files (# is list separator) from archive -a
201 -n sends no notification e-mail, but shows the download URL immediately
203 -r ADDRESS sets e-mail Reply-To ADDRESS
204 -F activates female mode
205 -U show authorized URL
206 -+ is an undocumented feature - test it :-)
208 To manage your subuser and groups or forward or redirect files, use a
209 webbrowser with the URL from "$0 -U", e.g.: firefox \$($0 -U)
211 If you want to copy-forward an already uploaded file to another recipient,
212 then you first have to query the file number with:
214 and then copy-forward it with:
215 $0 -b # other\@address
216 Where # is the file number.
218 You can list an uploaded file in more detail with
220 Where # is the file number.
222 If you want to modify the keep time, comment or auto-delete behaviour of an
223 already uploaded file then you first have to query the file number with:
225 and then for example set the keep time to 30 days with:
227 Where # is the file number.
229 With option -a you can send several files or whole directories within a single
230 archive file. The archive types tar and tgz are build on-the-fly (streaming)
231 whereas archive types zip and 7z need a temporary archive file on local disk.
233 With option -s you can send any data coming from a pipe (STDIN) as a file
234 without wasting local disc space.
236 With option -X you can specify any URL parameter, e.g.:
237 fexsend -X autodelete=yes ...
238 fexsend -X 'autodelete=no&locale=german' ...
240 For HTTPS you can set the environment variables:
241 SSLVERIFY=1 # activate server identity verification
242 SSLVERSION=TLSv1 # this is the default
243 SSLCAPATH=/etc/ssl/certs # path to trusted (root) certificates
244 SSLCAFILE=/etc/ssl/cert.pem # file with trusted (root) certificates
245 SSLCIPHERLIST=HIGH:!3DES # see http://www.openssl.org/docs/apps/ciphers.html
247 Partner program xx is an internet clipboard. See: xx -h
249 Partner program fexget is for downloading. See: fexget -h
251 fexsend stores the login data (server, user and auth-ID) in the file
253 The format of this file is ([data] is optional):
255 server-URL[!proxy[:port[:chunk-size]]
259 For temporary usage of a HTTP proxy use:
260 $0 -P your_proxy:port:chunksize_in_MB file recipient
262 $0 -P wwwproxy.uni-stuttgart.de.de:8080:1024 4GB.tar .
264 For temporary usage of an alternative F*EX server or user use:
265 FEXID="FEXSERVER USER AUTHID" $0 file recipient
267 FEXID="fex.flupp.org gaga\@flupp.org blubb" $0 big.file framstag\@rus.uni-stuttgart.de
269 You can define aliases (and optional fexsend options) in \$HOME/.fex/config.pl:
271 'alias1' => 'user1\@domain1.org',
272 'alias2' => 'user2\@domain2.org',
273 'both' => 'user1\@domain1.org,user2\@domain2.org',
274 'extra' => 'extra\@special.net:-i other -K -k 30',
277 fexsend also respects aliases in $HOME/.mutt/aliases
278 The alias priority is (descending):
279 \$HOME/.fex/config.pl
281 fexserver address book
283 In \$HOME/.fex/config.pl you can also set the SSL* environment variables and the
284 \$opt_* variables, e.g.:
286 \$ENV{SSLVERSION} = 'TLSv1';
299 "
\e[A \\\\/\\\\/ \n",
300 "
\e[A //\\\\//\\\\\n"
310 "
\e[A \\\\/\\\\/ \n",
311 "
\e[A //\\\\//\\\\\n"
317 if ($windoof and not @ARGV and not $ENV{PROMPT}) {
318 # restart with cmd.exe to have mouse cut+paste
319 exec qw'cmd /k',$0,'-W';
323 unless (-d $fexhome) {
324 mkdir $fexhome,0700 or die "$0: cannot create FEXHOME $fexhome - $!\n";
327 unless (-d $tmpdir) {
328 mkdir $tmpdir,0700 or die "$0: cannot create tmpdir $tmpdir - $!\n";
331 my @_ARGV = @ARGV; # save arguments
333 our ($opt_q,$opt_h,$opt_H,$opt_v,$opt_m,$opt_c,$opt_k,$opt_d,$opt_l,$opt_I,
334 $opt_K,$opt_D,$opt_u,$opt_f,$opt_a,$opt_C,$opt_R,$opt_M,$opt_L,$opt_Q,
335 $opt_A,$opt_i,$opt_z,$opt_Z,$opt_b,$opt_P,$opt_x,$opt_X,$opt_V,$opt_U,
336 $opt_s,$opt_o,$opt_g,$opt_F,$opt_n,$opt_r,$opt_S,$opt_N,$opt_T);
339 $opt_q = 1 if @ARGV and $ARGV[-1] eq '--' and pop @ARGV or not -t STDOUT;
340 $opt_h = $opt_v = $opt_m = $opt_I = 0;
342 $_ = "$fexhome/config.pl"; require if -f;
343 getopts('hvIm:') or die $usage;
345 if ($macos and not @ARGV) {
348 $opt_h = $opt_v = $opt_m = $opt_c = $opt_k = $opt_d = $opt_l = $opt_I = 0;
349 $opt_H = $opt_K = $opt_D = $opt_R = $opt_M = $opt_L = $opt_Q = $opt_A = 0;
350 $opt_x = $opt_o = $opt_g = $opt_V = $opt_U = $opt_F = $opt_n = $opt_q = 0;
352 ${'opt_@'} = ${'opt_!'} = ${'opt_+'} = ${'opt_.'} = ${'opt_/'} = 0;
353 ${'opt_='} = ${'opt_#'} = '';
354 $opt_u = $opt_f = $opt_a = $opt_C = $opt_i = $opt_b = $opt_P = $opt_X = '';
355 $opt_s = $opt_r = $opt_T = '';
356 $_ = "$fexhome/config.pl"; require if -f;
357 getopts('hHvcdognVDKlILUARWMFzZqQS@!+./r:m:k:u:f:a:s:C:i:b:P:x:X:N:T:=:#:')
366 print "Version: $version\n";
368 print "Upgrade fexsend? ";
371 my $new = `wget -nv -O- http://fex.belwue.de/download/fexsend`;
372 my $newversion = $1 if $new =~ /version = (\d+)/;
373 if ($new !~ /upgrade fexsend/ or not $newversion) {
374 die "$0: bad update\n";
376 if ($newversion <= $version) {
377 die "$0: no newer version\n";
380 system qw'rsync -a',$_0,$_0.'_old';
382 open $_0,'>',$_0 or die "$0: cannot write $_0. - $!\n";
389 exit if "@ARGV" eq '.';
392 if ($opt_K and $opt_D) {
393 die "$0: you cannot use both options -D and -K\n";
396 if ($opt_a and $opt_c) {
397 die "$0: you cannot use both options -a and -c\n";
400 if ($opt_a and $opt_s) {
401 die "$0: you cannot use both options -a and -s\n";
404 if ($opt_g and $opt_c) {
409 if ($opt_f and $opt_f !~ /^\d+$/) {
410 die "$0: option -f needs a number, see $0 -l\n";
413 if ($opt_I and $opt_R) {
414 die "$0: you cannot use both options -I and -R\n";
417 # $opt_C is COMMENT command in F*EX protocol
420 ($opt_l or $opt_L) ? 'LIST':
421 ($opt_Q) ? 'CHECKQUOTA':
422 ($opt_S) ? 'LISTSETTINGS':
423 ($opt_Z) ? 'RECEIVEDLOG':
424 ($opt_z) ? 'SENDLOG':
425 (${'opt_!'}) ? 'FOPLOG':
437 female_mode("show help?") if $opt_F;
449 die $usage if $opt_m and $opt_m !~ /^\d+/;
452 if ($opt_P =~ /^([\w.-]+:\d+)(:(\d+))?/) {
454 $chunksize = $3 || 0;
456 die "$0: proxy must be: SERVER:PORT\n";
460 if ($FEXID = $ENV{FEXID}) {
461 $FEXID = decode_b64($FEXID) if $FEXID !~ /\s/;
462 ($fexcgi,$from,$id) = split(/\s+/,$FEXID);
464 if ($windoof and not -f $idf) { &init_id }
465 if (open $idf,$idf) {
472 # convert old idxx file
473 if ($idf and open $idf,$idf.'xx') {
476 if (open $idf,'>>',$idf) {
477 print {$idf} "\n[xx]\n",
487 if ($FEXXX = $ENV{FEXXX}) {
488 $FEXXX = decode_b64($FEXXX) if $FEXXX !~ /\s/;
489 ($fexcgi,$from,$id) = split(/\s+/,$FEXXX);
490 } elsif (open $idf,$idf) {
493 $proxy = $proxy_prefix = '';
505 $proxy = $proxy_prefix = '';
506 open $idf,$idf or die "$0: cannot open $idf - $!\n";
514 die "$0: no [$opt_i] in $idf\n" unless $_;
519 if ($xx) { &show_id }
527 $usage = "usage: $0 -T MB_up[:MB_down] [fexserver]\n";
528 if ($opt_T =~ /^(\d+)$/) {
530 } elsif ($opt_T =~ /^(\d+):(\d+)$/) {
538 nettest($ARGV[0],$up,$down);
540 nettest($fexcgi,$up,$down);
542 nettest('fex.belwue.de',$up,$down);
547 if (@ARGV > 1 and $ARGV[-1] =~ /(^|\/)anonymous/) {
548 $fexcgi = $1 if $ARGV[-1] =~ s:(.+)/::;
549 die "usage: $0 [options] file FEXSERVER/anonymous\n" unless $fexcgi;
550 $anonymous = $from = 'anonymous';
551 $sid = $id = 'ANONYMOUS';
552 } elsif (@ARGV > 1 and $id eq 'PUBLIC') {
553 $public = $sid = $id;
554 } elsif (@ARGV > 1 and $ARGV[-1] =~ m{^(https?://[\w.-]+(:\d+)?/fup\?[sg]key=\w+)}) {
556 $skey = $1 if $fexcgi =~ /skey=(\w+)/;
557 $gkey = $1 if $fexcgi =~ /gkey=(\w+)/;
560 $fexcgi = $opt_u if $opt_u;
562 if (not -e $idf and not ($fexcgi and $from and $id)) {
563 die "$0: no ID file $idf found, use \"fexsend -I\" to create it\n";
567 die "$0: no FEX URL found, use \"$0 -u URL\" or \"$0 -I\"\n";
570 unless ($from and $id) {
571 die "$0: no sender found, use \"$0 -f FROM:ID\" or \"$0 -I\"\n";
574 if ($fexcgi !~ /^http/) {
575 if ($fexcgi =~ /:443/) { $fexcgi = "https://$fexcgi" }
576 else { $fexcgi = "http://$fexcgi" }
584 $port = 443 if $server =~ s{https://}{};
585 $port = $1 if $server =~ s/:(\d+)//;
588 # $opt_s and die "$0: cannot use -s with https due to stunnel bug\n";
589 # $opt_g and die "$0: cannot use -g with https due to stunnel bug\n";
593 $server =~ s{http://}{};
596 # $chunksize = 4*k unless $chunksize;
600 if ($port == 80) { $proxy_prefix = "http://$server" }
601 elsif ($port != 443) { $proxy_prefix = "http://$server:$port" }
604 # xx: special file exchange between own accounts
606 my $transferfile = "$tmpdir/STDFEX";
609 $transferfile = "$tmpdir/xx:xxx";
610 } elsif (@ARGV and $ARGV[0] =~ /^:([\w.=+-]+)$/) {
611 $transferfile = "$tmpdir/xx:$1";
614 open my $lock,'>>',$transferfile
615 or die "$0: cannot write $transferfile - $!\n";
616 flock($lock,LOCK_EX|LOCK_NB)
617 or die "$0: $transferfile is locked by another process\n";
618 truncate $transferfile,0;
619 if (not @ARGV and -t) {
620 &get_xx($transferfile);
622 &send_xx($transferfile);
629 &inquire if $windoof and not @ARGV and not
630 ($opt_l or $opt_L or $opt_Q or $opt_A or $opt_U or $opt_I or
631 $opt_f or $opt_x or $opt_N);
634 $opt_C = "!SHORTMAIL! $opt_C";
637 if ($opt_n or $opt_C =~ /NOMAIL|!#!/) {
641 unless ($skey or $gkey or $anonymous) {
643 $opt_f||$opt_x||$opt_Q||$opt_l||$opt_L||$opt_U||$opt_z||$opt_Z||$opt_A
644 ||$opt_d||${'opt_!'}||${'opt_@'})
645 ) { warn "Server/User: $fexcgi/$from\n" }
648 if ($opt_V and not @ARGV) { exit }
649 if ($opt_f) { &forward }
650 elsif ($opt_x) { &modify }
651 elsif ($opt_N) { &renotify }
652 elsif ($opt_Q) { &query_quotas }
653 elsif ($opt_S) { &query_settings }
654 elsif ($opt_l or $opt_L) { &list }
655 elsif ($opt_U) { &show_URL }
656 elsif ($opt_z or $opt_Z or ${'opt_!'}) { &get_log }
657 elsif ($opt_A) { edit_address_book($from) }
658 elsif (${'opt_@'}) { &show_address_book }
659 elsif ($opt_d and $anonymous) { &purge }
660 elsif ($opt_d and $ARGV[-1] =~ /^\d+$/) { &delete_file_number }
666 # initialize ID file or show ID
676 $fexcgi = $from = $id = '';
678 unless (-d $fexhome) {
679 mkdir $fexhome,0700 or die "$0: cannot create FEXHOME $fexhome - $!\n";
683 if (not $tag and open $idf,$idf) {
686 last if /^\[$opt_i\]/;
694 chomp($fexcgi,$from,$id);
695 $FEXID = encode_b64("$fexcgi $from $id");
697 print "# hint: to edit the ID file $idf use \"$0 -I .\" #\n";
698 print "export FEXID=$FEXID\n";
699 print "history -d \$((HISTCMD-1));history -d \$((HISTCMD-1))\n";
701 print "FEXID=$FEXID\n";
705 die "$0: no ID data found\n";
709 if ($tag and $tag eq '.') { exec $ENV{EDITOR}||'vi',$idf }
711 if ($tag) { print "F*EX server URL for [$tag]: " }
712 else { print "F*EX server URL: " }
714 $fexcgi =~ s/[\s\n]//g;
715 die "you MUST provide a FEX-URL!\n" unless $fexcgi;
716 if ($fexcgi =~ /\?/) {
717 $from = $1 if $fexcgi =~ /\bfrom=(.+?)(&|$)/i;
718 $id = $1 if $fexcgi =~ /\bid=(.+?)(&|$)/i;
719 # $skey = $1 if $fexcgi =~ /\bskey=(.+?)(&|$)/i;
720 # $gkey = $1 if $fexcgi =~ /\bgkey=(.+?)(&|$)/i;
721 die "$0: cannot use GKEY URL in ID file\n" if $fexcgi =~ /gkey=/i;
722 die "$0: cannot use SKEY URL in ID file\n" if $fexcgi =~ /skey=/i;
725 unless ($fexcgi =~ /^[_:=\w\-\.\/\@\%]+$/) {
726 die "\"$fexcgi\" is not a legal FEX-URL!\n";
728 $fexcgi =~ s:/fup/*$::;
729 print "proxy address (hostname:port or empty if none): ";
731 $proxy =~ s/[\s\n]//g;
732 if ($proxy =~ /^[\w.-]+:\d+$/) {
734 } elsif ($proxy =~ /\S/) {
735 die "wrong proxy address format\n";
740 print "proxy POST limit in MB (use 2048 if unknown): ";
750 $from = 'GROUPMEMBER';
754 print "Your e-mail address as registered at $fexcgi: ";
756 $from =~ s/[\s\n]//g;
757 die "you MUST provide your e-mail address!\n" unless $from;
759 unless ($from =~ /^[_:=\w\-\.\/\@\%\+]+$/) {
760 die "\"$from\" is not a legal e-mail address!\n";
763 print "Your auth-ID for $from at $fexcgi: ";
766 die "you MUST provide your ID!\n" unless $id;
769 if (open $idf,'>>',$idf) {
770 print {$idf} "\n[$tag]\n" if $tag and -s $idf;
771 print {$idf} "$fexcgi$proxy\n",
775 print "data written to $idf\n";
777 die "$0: cannot write to $idf - $!\n";
783 my ($fexcgi,$from,$id);
784 if (open $idf,$idf) {
786 # $fexcgi = <$idf> if $fexcgi =~ /^\[.+\]/;
797 die "$0: too few data in $idf" unless defined $id;
801 $FEXXX = encode_b64("$fexcgi $from $id");
803 print "export FEXXX=$FEXXX\n";
804 print "history -d \$((HISTCMD-1));history -d \$((HISTCMD-1))\n";
806 print "FEXXX=$FEXXX\n";
809 die "$0: cannot read $idf - $!\n";
815 my $fs = shift @ARGV or die $usage;
816 my $mail = shift @ARGV or die $usage;
818 my ($server,$user,$id);
820 die "$0: $idf does already exist\n" if -e $idf;
822 if ($fs =~ /^https/) {
823 die "$0: cannot handle https at this time\n";
826 $fs =~ s{^http://}{};
828 if ($fs =~ s/:(\d+)//) { $port = $1 }
831 tcpconnect($fs,$port);
832 sendheader("$fs:$port","GET $proxy_prefix/fur?user=$mail&verify=no HTTP/1.1");
838 printf "<-- $_"if $opt_v;
844 printf "<-- $_"if $opt_v;
845 if (m{http://(.*)/fup\?from=(.+)&ID=(.+)}) {
850 if (open F,">$idf") {
856 print "user data written to $idf\n";
857 print "you can now fex!\n";
860 die "$0: cannot write to $idf - $!\n";
865 die "$0: no account data received from F*EX server\n";
870 # menu for MacOS users
878 print "fexsend-$version\n";
881 if (open $idf,$idf) {
882 $fexcgi = getline($idf) and
883 $from = getline($idf) and
892 print "$from on $fexcgi\n";
897 print "[s] send a file or directory\n";
898 print "[u] update fexsend\n";
899 print "[l] change login data (user, server, auth-ID)\n";
903 print "your choice: ";
908 print "Type [Cmd]W to close this window.\n";
915 "With fexsend you can send files of any size to any e-mail address.\n".
917 "At the recipient or file prompt [RETURN] brings you to this option menu.\n".
919 "To send more than one file:\n".
920 "When you enter * at the file prompt, you will be first asked for an archive name\n".
921 "and then you can drag+drop multiple files.\n".
923 "Do not forget to terminate each input line with [RETURN].\n".
925 "See http://fex.rus.uni-stuttgart.de/ for more information.\n";
930 if ($0 =~ m:(^/client/|/sw/):) {
932 print "use swupdate to update fexsend!\n";
936 system "curl http://fex.belwue.de/download/fexsend>".quote($new);
938 system qw'perl -c',$new;
944 print "cannot install new fexsend\n";
954 if ($key eq 's' or $key eq "\n") {
966 my ($file,$comment,$recipient,$archive,$size,$cmd,$key);
973 &set_ID unless -s $idf;
976 print "Enter [RETURN] after each input line.\n";
980 print "Recipient(s): ";
981 $recipient = <STDIN>;
983 $recipient =~ s/^\s+//;
984 $recipient =~ s/\s+$//;
985 $recipient =~ s/[\s;,]+/,/g;
986 &menu unless $recipient;
987 last if $recipient =~ /\w/ or $recipient eq '.';
992 print "Drag a file into this window or hit [RETURN] ";
993 print $archive ? "to continue.\n" : "for menu options.\n";
994 print "File to send: ";
998 $file =~ s/ $// if $file !~ /\\ $/;
999 &menu unless $file or $archive;
1001 print "Archive name: ";
1002 $archive = <STDIN>||'';
1004 next unless $archive;
1005 $archive =~ s/^\s+//g;
1006 $archive =~ s/\s+$//g;
1007 $archive =~ s/[^\w=.+-]/_/g;
1012 $file =~ s/\\\\/\000/g;
1014 $file =~ s/\000/\\/g;
1017 print "\"$file\" is not readable\n";
1020 my $qf = quote($file);
1021 if (`du -ms $qf` =~ /^(\d+)/) {
1023 printf "%d MB\n",$1;
1032 $qfiles = join(' ',map(quote($_),@files));
1041 $comment = <STDIN>||'';
1044 if ($comment =~ s/^:\s*-/-/) {
1045 $cmd = quote($0)." $comment ";
1047 $cmd .= '-a '.quote($archive).' '.$qfiles;
1049 $cmd .= quote($file);
1051 $cmd .= ' '.quote($recipient);
1055 print quote($0)." -C '$comment' ";
1057 printf "-a %s %s %s\n",quote($archive),$qfiles,$recipient;
1058 system $0,'-C',$comment,'-a',$archive,@files,$recipient;
1060 printf "%s %s\n",quote($file),$recipient;
1061 system $0,'-C',$comment,$file,$recipient;
1065 print "[s] send another file to $recipient\n";
1066 print "[n] send another file to another recipient\n";
1069 print "your choice: ";
1072 &ask_file if $key eq 'n';
1073 if ($key eq 's' or $key eq "\n") {
1082 $file = $comment = $archive = '';
1089 my ($server,$port,$user,$logo);
1094 print "F*EX server URL: ";
1096 $server =~ s/[\s\n]//g;
1097 if ($server =~ s:/fup/(\w+)$::) {
1098 $_ = decode_b64($1);
1099 if (/(from|user)=(.+)&id=(.+)/) {
1104 $server =~ s:/fup.*::;
1106 next if $server !~ /\w/;
1107 if ($server =~ s/^https:..// or $server =~ /:443/) {
1110 eval "use IO::Socket::SSL";
1112 print "\nno perl SSL modules installed - cannot use https\n\n";
1115 $SH = IO::Socket::SSL->new(
1116 PeerAddr => $server,
1122 $server =~ s:^http.//::;
1123 if ($server =~ s/:(\d+)//) {
1128 $SH = IO::Socket::INET->new(
1129 PeerAddr => $server,
1135 print "\ncannot connect to $server:$port - $!\n\n";
1140 "GET /logo.jpg HTTP/1.0",
1141 "Connection: close",
1144 unless (/HTTP.1.1 200/) {
1145 print "\nbad server reply: $_\n";
1148 while (<$SH>) { last if /^\s*$/ }
1152 if (length $logo < 9999) {
1153 print "\n$server is not a F*EX server!\n\n";
1156 open $logo,">$tmpdir/fex.jpg";
1157 print {$logo} $logo;
1164 print "Your login (e-mail address): ";
1166 $user =~ s/[\s\n]//g;
1167 if ($user !~ /.@[\w.-]+$/) {
1168 print "\"$user\" is not a valid e-mail address!\n";
1175 print "Your auth-ID for this account: ";
1180 open $idf,'>',$idf or die "$0: cannot write to $idf - $!\n";
1181 print {$idf} "$server\n",
1186 print "Login data written to $idf\n\n";
1187 print "fexing test file to $user:\n\n";
1188 system "$0 -o -M -C test $tmpdir/fex.jpg $user";
1191 print "fexsend failed, login data is invalid, try again\n";
1194 print "fexsend test succeeded!\n";
1206 my ($length,$t0,$t1,$t2,$tt,$tb,$tc,$B,$kBs,$bt);
1208 my $nettest = $sid = 'nettest';
1211 if ($url =~ s:^https.//::) {
1212 $https = $port = 443;
1214 $url =~ s:^http.//::;
1215 $port = $1 if $url =~ s/:(\d+)//;
1217 $url =~ s/[\/:].*//;
1221 serverconnect($server,$port);
1222 checkrecipient($nettest,$nettest);
1223 warn "$0: send to $server:$port\n";
1230 comment => 'NOSTORE',
1235 serverconnect($server,$port);
1236 warn "$0: receive from $server:$port\n";
1237 sendheader("$server:$port","GET $proxy_prefix/ddd/$down HTTP/1.0");
1239 die "$0: no response from fex server $server\n" unless $_;
1242 if (/^HTTP\/[\d.]+ 2/) {
1243 warn "<-- $_" if $opt_v;
1246 print "<-- $_" if $opt_v;
1248 $length = $1 if /^Content-Length:\s*(\d+)/i;
1251 s/HTTP\/[\d.]+ \d+ //;
1252 die "$0: bad server reply: $_";
1256 die "$0: no Content-Length header in server reply\n";
1265 $t0 = $t1 = $t2 = int(time);
1267 while ($B < $length) {
1268 $b = read $SH,$_,$bs or die "$0: cannot read after $B bytes - $!\n";
1269 # defined($_ = <$SH>) or die "$0: cannot read after $B bytes - $!\n";
1274 if (${'opt_+'} and int($t2*10)>$tc) {
1275 print $rrcamel[$tc%2+1];
1278 if (int($t2) > $t1) {
1279 $kBs = int($bt/k/($t2-$t1));
1282 printf STDERR "nettest: %d MB (%d%%) %d kB/s \r",
1283 int($B/M),int(100*$B/$length),$kBs;
1289 $kBs = int($B/k/($tt||1));
1294 printf STDERR "nettest: %d MB in %d s = %d kB/s \n",
1300 # read one key from terminal in raw mode
1303 local $SIG{INT} = sub { stty('reset'); exit };
1306 # loop necessary for ESXi support
1307 while (not defined $key) {
1316 if (shift eq 'raw') {
1317 system qw'stty -echo -icanon eol',"\001";
1319 system qw'stty echo icanon eol',"\000";
1325 my $transferfile = shift;
1329 $SIG{PIPE} = $SIG{INT} = sub {
1330 unlink $transferfile;
1334 if ($0 eq 'xxx') { @tar = qw'tar -cv' }
1335 else { @tar = qw'tar -cvz' }
1338 if ("@ARGV" eq '-') {
1339 # store STDIN to transfer file
1340 shelldo("cat >> $transferfile");
1342 print "making tar transfer file $transferfile :\n";
1343 # single file? then add this directly
1344 if (scalar @ARGV == 1) {
1345 # strip path if not ending with /
1346 if ($ARGV[0] =~ m:(.+)/(.+): and $2 !~ m:/$:) {
1347 ($dir,$file) = ($1,$2);
1348 chdir $dir or die "$0: $dir - $!\n";
1353 shelldo(@tar,qw'--dereference -f',$transferfile,$file);
1355 shelldo(@tar,'-f',$transferfile,$file);
1358 shelldo(@tar,'-f',$transferfile,@ARGV);
1361 unlink $transferfile;
1363 die "$0: interrupted making tar transfer file\n";
1365 die "$0: error while making tar transfer file\n";
1370 # write input from pipe to transfer file
1371 shelldo("cat >> $transferfile");
1374 die "$0: no transfer file\n" unless -s $transferfile;
1376 serverconnect($server,$port);
1377 query_sid($server,$port);
1383 file => $transferfile,
1384 comment => 'NOMAIL',
1385 autodelete => $transferfile =~ /STDFEX/ ? 'NO' : 'DELAY',
1388 # open P,'|w3m -T text/html -dump' or die "$0: w3m - $!\n";
1391 if ($transferfile =~ /:/ and $0 ne 'xxx') {
1392 if ("@r" =~ /\s(X-)?Location: (http.*)\s/) {
1393 print "wget -O- $2 | tar xvzf -\n";
1397 unlink $transferfile;
1405 female_mode("query quotas?") if $opt_F;
1413 die "$0: no response from fex server $server\n" unless @r;
1415 unless (/^HTTP.* 2/) {
1417 die "$0: server response: $_\n";
1419 if (($_) = grep(/^X-Sender-Quota/,@r) and /(\d+)\s+(\d+)/) {
1420 print "sender quota (used): $1 ($2) MB\n";
1422 print "sender quota: unlimited\n";
1424 if (($_) = grep(/^X-Recipient-Quota/,@r) and /(\d+)\s+(\d+)/) {
1425 print "recipient quota (used): $1 ($2) MB\n";
1427 print "recipient quota: unlimited\n";
1432 sub query_settings {
1436 female_mode("query settings?") if $opt_F;
1439 print "ID data from \$FEXID\n";
1441 print "ID data from $idf\n";
1443 die "$0: found no ID\n";
1445 print "server: $fexcgi\n";
1446 print "user: $from\n";
1447 print "auth-ID: $id\n";
1448 print "login URL: ";
1457 die "$0: no response from fex server $server\n" unless @r;
1459 unless (/^HTTP.* 2/) {
1461 die "$0: server response: $_\n";
1463 if (($_) = grep(/^X-Autodelete/,@r) and /:\s+(\w+)/) {
1464 print "autodelete: $1\n";
1466 if (($_) = grep(/^X-Default-Keep/,@r) and /(\d+)/) {
1467 print "default keep: $1 days\n";
1469 if (($_) = grep(/^X-Default-Locale/,@r) and /:\s+(\w+)/) {
1470 print "default locale: $1\n";
1472 if (($_) = grep(/^X-MIME/,@r) and /:\s+(\w+)/) {
1473 print "display file with browser: $1\n";
1475 if (($_) = grep(/^X-Sender-Quota/,@r) and /(\d+)\s+(\d+)/) {
1476 print "sender quota (used): $1 ($2) MB\n";
1478 print "sender quota: unlimited\n";
1480 if (($_) = grep(/^X-Recipient-Quota/,@r) and /(\d+)\s+(\d+)/) {
1481 print "recipient quota (used): $1 ($2) MB\n";
1483 print "recipient quota: unlimited\n";
1494 my $a = shift @ARGV || '.';
1497 female_mode("list spooled files?") if $opt_F;
1500 if ($a =~ /^\d+$/) {
1501 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
1502 while (<$fexlist>) {
1503 if (/^\s*(\d+)\) (\w+) (.+)/ and $1 eq $a) {
1504 serverconnect($server,$port) unless $SH;
1507 "GET $proxy_prefix/fop/$2/$2?LIST HTTP/1.1",
1511 print "<-- $_" if $opt_v;
1512 if (/^HTTP.* 200/) {
1513 print "<-- $_" if $opt_v;
1521 } elsif (s:HTTP/[\d\. ]+::) {
1522 die "$0: server response: $_";
1524 die "$0: no response from fex server $server\n";
1529 die "$0: file \#$a not found in fexlist\n";
1535 to => $opt_l ? '*' : $from,
1538 die "$0: no response from fex server $server\n" unless @r;
1540 unless (/^HTTP.* 200/) {
1542 die "$0: server response: $_\n";
1547 open $fexlist,">$fexlist" or die "$0: cannot write $fexlist - $!\n";
1549 next unless /<pre>/ or $data;
1552 if (/<a href=".*dkey=(\w+).*?">/) { $dkey = $1 }
1554 # $_ = encode_utf8($_);
1559 if (/^(to (.+) :)/) {
1561 print "\n$_\n" if $s;
1562 print {$fexlist} "\n$_\n";
1563 } elsif (m/(\d+) MB (.+)/) {
1565 printf "%4s) %8d MB %s\n","#$n",$1,$2 if $s;
1566 printf {$fexlist} "%3d) %s %s\n",$n,$dkey,$2;
1572 # list received files
1575 next unless /<pre>/ or $data;
1579 if (/(from .* :)/) {
1582 if (m{(\d+) (MB.*)<a href="(https?://.*/fop/\w+/.+)">(.+)</a>( ".*")?}) {
1583 printf "%8d %s%s%s\n",$1,$2,$3,($5||'');
1591 printf "%s/fup/%s\n",$fexcgi,encode_b64("from=$from&id=$id");
1605 die "$0: no response from fex server $server\n" unless @r;
1607 unless (/^HTTP.* 200/) {
1609 die "$0: server response: $_\n";
1612 foreach (@r) { print "$_\n" }
1616 sub show_address_book {
1621 %AB = query_address_book($server,$port,$from);
1622 foreach $alias (sort keys %AB) {
1623 next if $alias eq 'ADDRESS_BOOK';
1624 $_ = sprintf "%s = %s (%s) # %s\n",
1627 $AB{$alias}->{options},
1628 $AB{$alias}->{comment};
1637 die "$0: not yet implemented\n";
1641 sub delete_file_number {
1645 $opt_d = shift @ARGV;
1646 die "usage: $0 -d #\n" if $opt_d !~ /^\d+$/;
1648 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
1649 while (<$fexlist>) {
1650 if (/^to (.+\@.+) :/) {
1652 } elsif (/^\s*(\d+)\) (\w+) (.+)/ and $1 eq $opt_d) {
1653 serverconnect($server,$port) unless $SH;
1656 "GET $proxy_prefix/fop/$2/$2?DELETE HTTP/1.1",
1660 print "<-- $_" if $opt_v;
1661 if (/^HTTP.* 200/) {
1664 last if /^\n/; # ignore HTML output
1665 print "<-- $_" if $opt_v;
1666 if (/^X-File:.*\/(.+)/) {
1667 printf "%s deleted\n",decode_utf8(urldecode($1));
1671 } elsif (s:HTTP/[\d\. ]+::) {
1672 die "$0: server response: $_";
1674 die "$0: no response from fex server $server\n";
1680 sleep 1; # do not overrun server
1688 my ($from,$to,$file) = @_;
1692 serverconnect($server,$port);
1693 query_sid($server,$port) unless $anonymous;
1696 $file = urlencode($file);
1699 "GET $proxy_prefix/fop/$to/$from/$file?id=$sid&DELETE HTTP/1.1",
1704 printf "<-- $_"if $opt_v;
1712 s/([^_=:,;<>()+.\w\-])/'%'.uc(unpack("H2",$1))/ge;
1721 my ($data,$aname,$alias);
1728 if ($from =~ /^SUBUSER|GROUPMEMBER$/) {
1731 # look for single @ in arguments
1732 for (my $i=1; $i<$#ARGV; $i++) {
1733 if ($ARGV[$i] eq '@') {
1734 $ARGV[$i] = join(',',@ARGV[$i+1 .. $#ARGV]);
1739 $to = pop @ARGV or die $usage;
1742 $nomail = $opt_C ||= 'NOMAIL';
1746 $nomail = $opt_C ||= 'NOMAIL';
1748 if ($opt_g and $to =~ /,/) {
1749 die "$0: encryption is supported to only one recipient\n";
1751 if ($to =~ m{^https?://.*/fup\?skey=(\w+)}) {
1756 if ($to =~ m{^https?://.*/fup\?gkey=(\w+)}) {
1757 $from = 'GROUPMEMBER';
1762 @to = split(',',lc($to));
1764 die $usage unless @ARGV or $opt_a or $opt_s;
1765 die $usage if $opt_s and @ARGV;
1767 # early serverconnect necessary for X-Features info
1768 serverconnect($server,$port);
1772 sendheader("$server:$port","OPTIONS /FEX HTTP/1.1");
1775 die "$0: no response from fex server $server\n" unless $_;
1776 print "<-- $_" if $opt_v;
1777 if (/^HTTP.* 201/) {
1780 print "<-- $_" if $opt_v;
1782 $aok = $_ if /X-Features:.*ANONYMOUS/;
1784 die "$0: no anonymous support on server $server\n" unless $aok;
1786 die "$0: bad response from server $server : $_\n";
1791 query_sid($server,$port);
1793 if ($from eq 'SUBUSER') {
1795 # die "skey=$skey\nid=$id\nsid=$sid\n";
1798 if ($from eq 'GROUPMEMBER') {
1804 $opt_C ||= 'NOMAIL';
1805 } elsif ($to =~ m:^(//.*):) {
1807 if ($features =~ /XKEY/) {
1811 die "$0: server does not support XKEY\n";
1813 } elsif (grep /^[^@]*$/,@to and not $skey and not $gkey) {
1814 %AB = query_address_book($server,$port,$from);
1816 serverconnect($server,$port);
1817 query_sid($server,$port);
1820 # alias in local config?
1822 if ($alias{$to} =~ /(.+?):(.+)/) {
1827 # special extra upload
1828 system $0,split(/\s/,$opt),@argv,$ato;
1834 # alias in server address book?
1836 # do not substitute alias with expanded addresses because then
1837 # keep and autodelete options from address book will get lost
1840 # look for mutt aliases
1841 elsif ($to !~ /@/ and $to ne $from) {
1842 $to = get_mutt_alias($to);
1847 $to = join(',',grep /./,@to) or exit;
1848 # warn "Server/User: $fexcgi/$from\n" unless $opt_q;
1851 not $skey and not $gkey
1853 and $features =~ /CHECKRECIPIENT/
1854 and $opt_C !~ /^(DELETE|LIST|RECEIVEDLOG|SENDLOG|FOPLOG)$/
1856 checkrecipient($from,$to);
1858 serverconnect($server,$port);
1859 query_sid($server,$port);
1864 if (@ARGV > 1 and not ($opt_a or $opt_s or $opt_d)) {
1865 print "Archive name (name.tar, name.tgz or name.zip) or [RETURN] to send file for file:\n";
1872 if ($macos and not $opt_a and -d "@ARGV") {
1874 my $qdir = quote($dir);
1875 if (`du -s $qdir` =~ /^(\d+)/ and $1 < 2**21) {
1876 $opt_a = "$dir.zip";
1878 $opt_a = "$dir.tar";
1885 $opt_s =~ s/[^\w_.+-]/_/g;
1890 $opt_a =~ s/[^\w_.+-]/_/g;
1891 if ($opt_a =~ /(.+)\.(zip|tar|tgz|7z)$/) {
1895 die "$0: archive name must be one of ".
1896 "$opt_a.tar $opt_a.tgz $opt_a.zip\n";
1898 # no file argument left?
1900 # use file name as archive name
1905 foreach my $file (@ARGV) {
1906 die "$0: cannot read \"$file\"\n" unless -l $file or -r $file;
1908 $opt_a .= ".$atype" if $opt_a !~ /\.$atype$/;
1909 $transferfile = "$tmpdir/$opt_a";
1910 unlink $transferfile;
1911 print "Making fex archive ($opt_a):\n";
1912 if ($atype eq 'zip') {
1914 # if ($opt_c) { system(qw'7z a -tzip',$transferfile,@ARGV) }
1915 # else { system(qw'7z a -tzip -mm=copy',$transferfile,@ARGV) }
1916 system(qw'7z a -tzip',$transferfile,@ARGV);
1917 @files = ($transferfile);
1918 } elsif ($macos and scalar(@ARGV) == 1) {
1919 ## ditto-zip is now handled by formdatapost()
1923 # zip archives must be < 2 GB, so split as necessary
1924 @files = zipsplit($transferfile,@ARGV);
1925 if (scalar(@files) == 1) {
1926 $transferfile = $files[0];
1927 $transferfile =~ s/_1.zip$/.zip/;
1928 rename $files[0],$transferfile;
1929 @files = ($transferfile);
1932 @transferfiles = @files;
1933 } elsif ($atype eq '7z') {
1934 # http://www.7-zip.org/
1935 my @X = (); # exclude list
1937 foreach my $x (split('#',${'opt_#'})) {
1941 if ($opt_c) { system(qw'7z a',@X,$transferfile,@ARGV) }
1942 else { system(qw'7z a -t7z -mx0',@X,$transferfile,@ARGV) }
1943 @transferfiles = @files = ($transferfile);
1944 } elsif ($atype eq 'tar') {
1946 system(qw'7z a -ttar',$transferfile,@ARGV);
1947 @transferfiles = @files = ($transferfile);
1949 ## tar is now handled by formdatapost()
1950 # system(qw'tar cvf',$transferfile,@ARGV);
1954 } elsif ($atype eq 'tgz') {
1956 die "$0: archive type tgz not available, use tar, zip or 7z\n";
1958 ## tgz is now handled by formdatapost()
1959 # system(qw'tar cvzf',$transferfile,@ARGV);
1963 die "$0: unknown archive format \"$atype\"\n";
1966 if (@transferfiles) {
1968 # error in making transfer archive?
1970 unlink @transferfiles;
1971 die "$0: $! - aborting upload\n";
1974 # maybe timeout, so make new connect
1975 if (time-$t0 >= $timeout) {
1976 serverconnect($server,$port);
1977 query_sid($server,$port) unless $anonymous;
1997 die "$0: \"$file\" is not a regular file, try option -a\n"
1999 die "$0: \"$file\" does not exist\n";
2002 die "$0: cannot read \"$file\"\n" unless -r $file;
2009 foreach my $file (@files) {
2010 my @s = stat($file);
2011 unless (@s and ($s[2] & S_IROTH) and -r $file) {
2012 die "$0: \"$file\" is not world readable\n";
2017 foreach my $file (@files) {
2018 sleep 1; # do not overrun server!
2019 unless (-s $file or $opt_d or $opt_a or $opt_s) {
2020 die "$0: cannot send empty file \"$file\"\n";
2022 female_mode("send file $file?") if $opt_F;
2031 autodelete => $opt_D,
2034 if (not @r or not grep /\w/,@r) {
2035 die "$0: no response from server\n";
2037 next if "@r" eq '0'; # already transfered
2038 if (($r) = grep /^ERROR:/,@r) {
2039 if ($anonymous and $r =~ /purge it/) {
2040 die "$0: file is already on server for $to - use another anonymous recipent\n";
2041 } elsif ($r =~ /timeout/i) {
2047 die "$0: server error: $r\n";
2051 if (scalar(@r) == 1) {
2052 die "$0: server error: @r\n";
2054 if ($r[0] !~ /HTTP.1.. 2/) {
2055 if ($r[0] =~ /HTTP.[\s\d.]+(.+)/) {
2056 die "$0: server error: $1\n";
2058 die "$0: server error:\n".join("\n",@r)."\n";
2063 if (($r) = grep /<h3>\Q$file/,@r) {
2067 if ($opt_a !~ /^afex_\d+\.tar$/ and $file !~ /afex_\d+\.tar$/) {
2068 # print grep({s/^(X-Recipient:.*\((.+)\))/Parameters: $2\n/i} @r);
2073 if (/^(X-)?(Recipient.*)/i) {
2075 if (/notification=no/i) { $nonot = 1 }
2078 if (/^(X-)?(Location.*)/i) {
2082 if ($from eq $to or $from =~ /^\Q$to\E@/i
2083 or $nomail or $anonymous or $nonot)
2085 print "$recipient\n" if $recipient;
2086 print "$location\n" if $location;
2091 # delete transfer tmp file
2092 unlink $transferfile if $transferfile;
2098 my ($to,$n,$dkey,$file,$req);
2102 # look for single @ in arguments
2103 for (my $i=1; $i<$#ARGV; $i++) {
2104 if ($ARGV[$i] eq '@') {
2105 $ARGV[$i] = join(',',@ARGV[$i+1 .. $#ARGV]);
2111 # if ($windoof and not @ARGV) { &inquire }
2112 $to = pop @ARGV or die $usage;
2113 $to = $from if $to eq '.';
2114 if ($to !~ /@/ and $to ne $from) {
2115 $to = get_mutt_alias($to);
2118 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
2119 while (<$fexlist>) {
2120 if (/^\s*(\d+)\) (\w+) .\s*\d+ d. ([+-] )?(.+)/ and $1 eq $opt_f) {
2124 if ($file =~ s/ "(.*)"$//) {
2125 $opt_C ||= $1 if $1 ne 'NOMAIL';
2133 die "$0: file #$opt_f not found in fexlist\n";
2136 female_mode("forward file #$opt_f?") if $opt_F;
2138 serverconnect($server,$port);
2139 query_sid($server,$port);
2141 $req = "GET $proxy_prefix/fup?"
2142 ."from=$from&ID=$sid&to=$to&dkey=$dkey&command=FORWARD";
2143 $req .= "&comment=$opt_C" if $opt_C;
2144 $req .= "&keep=$opt_k" if $opt_k;
2145 $req .= "&autodelete=$opt_D" if $opt_D;
2146 $req .= "&$opt_X" if $opt_X;
2147 $req .= " HTTP/1.1";
2148 sendheader("$server:$port",$req);
2151 $fp =~ s/[^\w_.-]/.+/g; # because of UTF8 filename
2154 $status = 0 if /"$fp"/;
2155 print if $opt_v or /"$fp"/;
2159 die "$0: server failed, rerun command with option -v\n";
2167 my ($to,$n,$dkey,$file,$req,$recipient);
2170 die $usage if @ARGV;
2172 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
2173 while (<$fexlist>) {
2174 if (/^\s*(\d+)\) (\w+) .\s*\d+ d. (.+)/ and $1 eq $opt_N) {
2183 die "$0: file #$opt_N not found in fexlist\n";
2186 female_mode("resend notification for file #$opt_N?") if $opt_F;
2188 serverconnect($server,$port);
2189 query_sid($server,$port);
2191 $req = "GET $proxy_prefix/fup?"
2192 ."from=$from&ID=$sid&dkey=$dkey&command=RENOTIFY"
2194 sendheader("$server:$port",$req);
2198 print "<-- $_" if $opt_v;
2200 if (/^X-Notify: (.+)\/(.+)\/(.+)/) {
2207 print "notification e-mail for $file has been resent to $recipient\n";
2210 die "$0: server failed\n";
2212 die "$0: server failed, rerun command with option -v\n";
2222 my ($n,$dkey,$file,$req);
2225 die $usage if @ARGV;
2226 die $usage unless $opt_C or $opt_k or $opt_D;
2228 open $fexlist,$fexlist or die "$0: $fexlist - $!\n";
2229 while (<$fexlist>) {
2230 if (/^\s*(\d+)\) (\w+) .\s*\d+ d. (.+)/ and $1 eq $opt_x) {
2234 $file =~ s/ "(.*)"$//;
2241 die "$0: file #$opt_x not found in fexlist\n";
2244 female_mode("modify file #$opt_x?") if $opt_F;
2246 serverconnect($server,$port);
2247 query_sid($server,$port);
2249 $req = "GET $proxy_prefix/fup?"
2250 ."from=$from&ID=$sid&dkey=$dkey&command=MODIFY";
2251 $req .= "&comment=$opt_C" if $opt_C;
2252 $req .= "&keep=$opt_k" if $opt_k;
2253 $req .= "&autodelete=$opt_D" if $opt_D;
2254 $req .= " HTTP/1.1";
2255 sendheader("$server:$port",$req);
2270 my $transferfile = shift;
2274 # get transfer file from FEX server
2276 serverconnect($server,$port);
2277 query_sid($server,$port);
2280 xxget($from,$sid,$transferfile);
2283 unless (-s $transferfile) {
2284 unlink $transferfile;
2288 # no further processing if delivering to pipe
2289 exec 'cat',$transferfile unless -t STDOUT;
2291 if ($ft = `file $transferfile 2>/dev/null`) {
2292 if ($ft =~ /compressed/) {
2293 rename $transferfile,"$transferfile.gz";
2294 shelldo(ws("gunzip $transferfile.gz"));
2296 $ft = `file $transferfile`;
2298 # file command failed, so we look ourself into the file...
2299 elsif (open $transferfile,$transferfile) {
2300 read $transferfile,$_,4;
2301 close $transferfile;
2303 if (/\x1F\x8B\x08\x00/) {
2304 rename $transferfile,"$transferfile.gz";
2305 shelldo(ws("gunzip $transferfile.gz"));
2307 $ft = 'tar archive';
2310 if ($ft =~ /tar archive/) {
2311 rename $transferfile,"$transferfile.tar";
2312 $transferfile .= '.tar';
2316 print "Files in transfer-container:\n\n";
2317 shelldo(ws("tar tvf $transferfile"));
2318 print "\nExtract these files? [Yn] ";
2322 print "keeping $transferfile\n";
2324 my $untar = "tar xvf";
2325 # if ($> == 0 and `tar --help 2>&1` =~ /gnu/) {
2326 # $untar = "tar --no-same-owner -xvf";
2328 system("$untar $transferfile && rm $transferfile");
2329 die "$0: error while untaring, see $transferfile\n" if -f $transferfile;
2332 exec 'cat',$transferfile;
2340 my ($boundary,$filename,$length,$buf,$file,$fpsize,$resume,$seek,$nettest);
2342 my (@hh,@hb,@r,@pv,$to);
2343 my ($bytes,$b,$t,$bt);
2344 my ($t0,$t1,$t2,$tt,$tc);
2345 my $bs = 2**16; # blocksize for reading and sending file
2346 my $fileid = int(time);
2349 my $connection = '';
2352 my ($tar,$ditto,$aname,$atype,$list,$error,$location,$transferfile);
2355 if (defined($file = $P{file})) {
2357 $to = $AB{$P{to}} || $P{to}; # for gpg
2359 # special file: stream from STDIN
2361 $filename = encode_utf8($file);
2369 $if =~ s/([^_\w\.\-])/\\$1/g;
2370 $transferfile = $tmpdir . '/' . basename($file) . '.gz';
2371 $of = $transferfile;
2372 $of =~ s/([^_\w\.\-])/\\$1/g;
2373 shelldo("gzip <$if>$of");
2374 $filesize = -s $transferfile;
2375 die "$0: cannot gzip \"$file\"\n" unless $filesize;
2376 $file = $transferfile;
2379 # special file: tar-on-the-fly
2380 if (not $windoof and $opt_a and $file =~ /(.+)\.(tar|tgz)$/) {
2383 $list = "$tmpdir/$aname.list";
2384 $error = "$tmpdir/$aname.error";
2386 $tar .= 'z' if $atype eq 'tgz';
2387 if (`tar --help 2>/dev/null` =~ /--index-file/) {
2388 $tar .= " --index-file=$list -f-";
2393 foreach my $x (split('#',${'opt_#'})) {
2394 $tar .= " --exclude=$x";
2398 $tar .= ' '.quote($_);
2400 # print "calculating archive size... ";
2401 open $tar,"$tar 2>$error|" or die "$0: cannot run tar - $!\n";
2402 $t0 = int(time) if -t STDOUT;
2403 while ($b = read $tar,$_,$bs) {
2408 printf "Archive size: %d MB\r",int($filesize/M);
2413 printf "Archive size: %d MB\n",int($filesize/M) if -t STDOUT;
2414 unless (close $tar) {
2416 if (open $error,$error) {
2421 unlink $list,$error;
2422 die "$0: tar error:\n$_";
2424 $file = "$aname.$atype";
2425 $filename = encode_utf8($file);
2426 undef $SH; # force reconnect (timeout!)
2429 # special file: ditto-zip-on-the-fly
2430 # ditto: Can't archive multiple sources
2431 elsif ($macos and $opt_a and $file =~ /(.+)\.(zip)$/ and scalar(@ARGV) == 1) {
2434 $list = "$tmpdir/$aname.list";
2435 $error = "$tmpdir/$aname.error";
2436 $ditto = 'ditto -c -k --sequesterRsrc --keepParent';
2437 if (-d "@ARGV" and "@ARGV" =~ m:^(.+)/(.+):) {
2440 $file =~ s/([^\w\-\@\#%,.=+_:])/\\$1/g;
2441 $ditto .= ' '.$file;
2445 $file =~ s/([^\w\-\@\#%,.=+_:])/\\$1/g;
2446 $ditto .= ' '.$file;
2449 # print "calculating archive size... ";
2450 debug("cd $dittodir;$ditto -");
2451 open $ditto,"cd $dittodir;$ditto - 2>$error|"
2452 or die "$0: cannot run ditto - $!\n";
2453 $t0 = int(time) if -t STDOUT;
2454 while ($b = read $ditto,$_,$bs) {
2459 printf "Archive size: %d MB\r",int($filesize/M);
2464 printf "Archive size: %d MB\n",int($filesize/M) if -t STDOUT;
2465 unless (close $ditto) {
2467 if (-s $error and open $error,$error) {
2472 unlink $list,$error;
2473 die "$0: ditto-zip error:\n$_";
2475 unlink $list,$error;
2476 $file = "$aname.$atype";
2477 $filename = encode_utf8($file);
2478 undef $SH; # force reconnect (timeout!)
2481 elsif ($P{to} eq 'nettest') {
2482 $filename = $nettest = 'nettest';
2483 $filesize = $P{size};
2489 $filename = encode_utf8(${'opt_='} || $file);
2492 $filename =~ s/^[a-z]://;
2493 $filename =~ s/.*\\//;
2495 $filename =~ s:.*/::;
2496 $filename =~ s:[\r\n]+: :g;
2499 } elsif (not $opt_g and not $opt_s) {
2500 $filesize = -s $file or die "$0: \"$file\" is empty or not readable\n";
2504 $filename .= '.gpg' if $opt_g;
2506 unless ($opt_d or $nettest) {
2509 $fileid = int(time);
2512 $fileid = md5_hex(fmd(@ARGV));
2514 $fileid = fileid($file);
2520 $file = $filename = '';
2526 @hh = (); # HTTP header
2527 @hb = (); # HTTP body
2534 serverconnect($server,$port);
2535 query_sid($server,$port) unless $anonymous or $nettest;
2538 $P{id} = $sid; # ugly hack!
2540 $filename =~ s/\\/_/g; # \ is a illegal character for fexsrv
2542 # ask server if this file has been already sent
2543 if ($file and not $xx and not $nettest) {
2544 if (not $opt_d and $opt_o) {
2545 # delete before overwrite
2546 delete_file($from,$to,$filename);
2547 serverconnect($server,$port);
2548 query_sid($server,$port) unless $anonymous;
2549 $P{id} = $sid; # ugly hack!
2550 } elsif (not($opt_s or $opt_g or $opt_d or $opt_l or $opt_L or ${'opt_/'})) {
2551 ($seek,$location) = query_file($server,$port,
2552 $frecipient||$P{to},$P{from},$P{id},$filename,$fileid);
2553 if ($filesize == $seek) {
2554 print "Location: $location\n" if $location and $nomail;
2555 warn "$0: $file has been already transferred\n";
2557 } elsif ($seek and $seek < $filesize) {
2558 $resume = " (resuming at byte $seek)";
2559 } elsif ($filesize <= $seek) {
2564 sleep 1; # do not overrun proxy
2565 serverconnect($server,$port);
2570 if ($chunksize and $proxy and $port != 443
2571 and $filesize - $seek > $chunksize - $bs) {
2572 if ($features !~ /MULTIPOST/) {
2573 die sprintf("$0: server does not support chunked multi-POST needed for"
2574 ." files > %d MB via proxy\n",$chunksize/M);
2576 $opt_o = 0; # no overwriting mode for next chunks
2577 $fpsize = $chunksize - $bs;
2579 $fpsize = $filesize - $seek;
2582 $boundary = randstring(48);
2585 $P{filesize} = $filesize;
2587 # send HTTP POST variables
2590 @pv = qw'from to skey keep autodelete comment seek filesize';
2593 @pv = qw'from to gkey keep autodelete comment seek filesize';
2595 @pv = qw'from to id replyto keep autodelete comment command seek filesize';
2597 foreach my $v (@pv) {
2600 push @hb,"--$boundary";
2601 push @hb,"Content-Disposition: form-data; name=\"$name\"";
2603 # push @hb,encode_utf8($P{$v});
2608 # at last, POST the file
2610 push @hb,"--$boundary";
2611 push @hb,"Content-Disposition: form-data; name=\"FILE\"; filename=\"$filename\"";
2613 if ($opt_M) { push @hb,"Content-Type: application/x-mime" }
2614 else { push @hb,"Content-Type: application/octet-stream" }
2616 $flink = abs_path($file);
2617 push @hb,"Content-Location: $flink";
2619 # push @hb,"Content-Length: " . ((-s $file||0) - $seek); # optional header!
2620 push @hb,"Content-Length: $fpsize"; # optional header! NOT filesize!
2621 push @hb,"X-File-ID: $fileid";
2626 # prevent proxy chunked mode reply
2627 $connection = "close";
2630 push @hb,"--$boundary--";
2635 $length = length(join('',@hb)) + scalar(@hb)*2 + $fpsize;
2638 if ($file and not $opt_d) {
2639 if ($flink) { $hb[-2] = $flink }
2640 else { $hb[-2] = '(file content)' }
2642 # any other extra URL arguments
2644 $opt_X = "?$::opt_X" if $::opt_X and $file;
2647 push @hh,"POST $proxy_prefix/fup$opt_X HTTP/1.1";
2648 push @hh,"Host: $server:$port";
2649 push @hh,"User-Agent: $useragent";
2650 push @hh,"Content-Length: $length";
2651 push @hh,"Content-Type: multipart/form-data; boundary=$boundary";
2652 push @hh,"Connection: $connection" if $connection;
2656 print "--> $_\n" foreach (@hh,@hb);
2659 $SIG{PIPE} = \&sigpipehandler;
2660 # foreach $sig (keys %SIG) {
2661 # eval '$SIG{$sig} = sub { print "\n!!! SIGNAL '.$sig.' !!!\n"; exit; }';
2666 pop @hb unless $flink;
2667 nvtsend(@hh,@hb) or do {
2668 warn "$0: server has closed the connection, reconnecting...\n";
2670 goto FORMDATAPOST; # necessary: new $sid ==> new @hh
2673 unless ($opt_d or $flink) {
2675 $t0 = $t2 = int(time);
2682 open $file,"gpg -e -r $to|" or die "$0: cannot run gpg - $!\n";
2684 open $file,'>&=STDIN' or die "$0: cannot open STDIN - $!\n";
2688 open $file,"$tar|gpg -e -r $to|" or die "$0: cannot run tar&gpg - $!\n";
2690 open $file,"$tar|" or die "$0: cannot run tar - $!\n";
2694 if (defined $tpid and $tpid == 0) {
2696 if (open $list,$list) {
2697 # print "\n$tar|\n"; system "ls -l $list";
2700 print ' 'x(length($file)+40),"\r",$_;
2707 $SIG{CHLD} = 'IGNORE';
2710 print "Fast forward to byte $seek (resuming)\n";
2711 readahead($file,$seek);
2714 $ditto =~ s/ditto/ditto -V/;
2715 open $file,"cd $dittodir;$ditto -|" or die "$0: cannot run ditto - $!\n";
2717 print "Fast forward to byte $seek (resuming)\n";
2718 readahead($file,$seek);
2720 } elsif ($nettest) {
2724 my $fileq = quote($file);
2725 open $file,"gpg -e -r $to <$fileq|" or die "$0: cannot run gpg - $!\n";
2727 open $file,$file or die "$0: cannot read \"$file\" - $!\n";
2736 print $rcamel[0] if ${'opt_+'};
2738 $buf = '#' x $bs if $nettest;
2740 $SIG{ALRM} = sub { retry("timed out") };
2742 while ($bytes < $fpsize) {
2746 $b = read $file,$buf,$bs;
2751 print {$SH} $buf or &sigpipehandler;
2753 syswrite $SH,$buf or &sigpipehandler;
2757 if (not $nettest and $filesize > 0 and $bytes+$seek > $filesize) {
2762 die "$0: \"$file\" filesize has grown while uploading\n";
2766 if (${'opt_+'} and int($t2*10)>$tc) {
2767 print $rcamel[$tc%2+1];
2770 if (not $opt_q and -t STDOUT and int($t2)>$t1) {
2771 &sigpipehandler unless $SH->connected;
2772 # smaller block size is better on slow links
2773 $bs = 4096 if $t1 and $bs>4096 and $bytes/($t2-$t0)<65536;
2774 if ($filesize > 0) {
2775 $pct = sprintf "(%d%%)",int(($bytes+$seek)/$filesize*100);
2777 if ($bytes>2*M and $bs>4096) {
2778 printf STDERR "%s: %d MB of %d MB %s %d kB/s \r",
2779 $opt_s||$opt_a||$file,
2780 int(($bytes+$seek)/M),
2783 int($bt/k/($t2-$tt));
2785 printf STDERR "%s: %d kB of %d MB %s %d kB/s \r",
2786 $opt_s||$opt_a||$file,
2787 int(($bytes+$seek)/k),
2790 int($bt/k/($t2-$tt));
2793 # time window for transfer rate calculation
2799 last if $filesize > 0 and $bytes >= $fpsize;
2800 sleep 1 while ($opt_m and $bytes/k/(time-$t0||1) > $opt_m);
2803 close $file unless $nettest;
2807 print $rcamel[2] if ${'opt_+'};
2809 # terminate tar verbose output job
2816 if ($fileid =~ /[a-z]/ and not ($opt_s or $opt_g)) {
2818 if ($fileid ne md5_hex(fmd(@ARGV))) {
2819 print "\n" unless $opt_q;
2820 die "$0: files have been modified while uploading\n";
2823 if ($fileid ne fileid($file)) {
2824 print "\n" unless $opt_q;
2825 die "$0: file has been modified while uploading\n";
2831 if (not $chunksize and $bytes+$seek < $filesize) {
2832 die "$0: \"$file\" filesize has shrunk while uploading\n";
2835 if ($seek or $chunksize and $chunksize < $filesize) {
2837 printf STDERR "%s: %d MB in %d s = %d kB/s",
2838 $opt_s||$opt_a||$file,
2842 if ($bytes+$seek == $filesize) {
2843 printf STDERR ", total %d MB\n",int($filesize/M);
2845 printf STDERR ", chunk #%d : %d MB\n",
2846 $chunk,int(($bytes+$seek)/M);
2849 printf STDERR "%s: %d kB in %d s = %d kB/s",
2850 $opt_s||$opt_a||$file,
2854 if ($bytes+$seek == $filesize) {
2855 printf STDERR ", total %d kB\n",int($filesize/k);
2857 printf STDERR ", chunk #%d : %d kB\n",
2858 $chunk,int(($bytes+$seek)/k);
2863 printf STDERR "%s: %d MB in %d s = %d kB/s \n",
2864 $opt_s||$opt_a||$file,
2869 printf STDERR "%s: %d kB in %d s = %d kB/s \n",
2870 $opt_s||$opt_a||$file,
2877 if (-t STDOUT and not ($opt_s or $opt_g or $nettest)) {
2878 print STDERR "waiting for server ok..."
2884 print {$SH} "\r\n--$boundary--\r\n";
2885 # return if $nettest;
2887 # special handling of streaming file because of stunnel tcp shutdown bug
2888 if ($opt_s or $opt_g) {
2891 serverconnect($server,$port);
2892 query_sid($server,$port) unless $anonymous;
2893 ($seek,$location) = query_file($server,$port,$P{to},$P{from},$sid,
2895 if ($seek != $bytes) {
2896 die "$0: streamed $bytes bytes but server received $seek bytes\n";
2898 return "X-Location: $location\n";
2904 printf STDERR "%s: %d MB\n",$flink,int($bytes/M);
2906 printf STDERR "%s: %d kB\n",$flink,int($bytes/k);
2914 # SuSe: Can't locate object method "BINMODE" via package "IO::Socket::SSL::SSL_HANDLE"
2915 # binmode $SH,':utf8';
2917 if (not $opt_q and $file and -t STDOUT) {
2918 print STDERR "\r \r";
2922 print "<-- $_\n" if $opt_v;
2923 last if @r and $r[0] =~ / 204 / and /^$/ or /<\/html>/i;
2924 push @r,decode_utf8($_);
2930 if ($proxy and $fpsize+$seek < $filesize) {
2941 my @rc = ('A'..'Z','a'..'z',0..9 );
2945 for (1..$n) { $rs .= $rc[int(rand($rn))] };
2951 my $zipbase = shift;
2955 my ($zsize,$size,$n);
2957 $zipbase =~ s/\.zip$//;
2958 map { s/([^_\w\+\-\.])/\\$1/g } @files;
2960 open my $ff,"find @files|" or die "$0: cannot search for @_ - $!\n";
2967 die "$0: too many zip-archives\n";
2970 while ($file = <$ff>) {
2972 # next if -l $file or not -f $file;
2973 next unless -f $file;
2975 if ($size > 2147480000) {
2977 die "$0: \"$file\" too big for zip\n";
2979 if ($zsize + $size > 2147000000) {
2980 push @zipfiles,zip($zipbase.'_'.$n.'.zip',@files);
2991 push @zipfiles,zip($zipbase.'_'.$n.'.zip',@files);
3003 # if ($opt_c) { $cmd = "zip -@ $zip" }
3004 # else { $cmd = "zip -0 -@ $zip" }
3005 $cmd = "zip -@ $zip";
3007 ${'opt_#'} =~ s/#/ /g;
3008 $cmd .= " -x ".${'opt_#'};
3010 print $cmd,"\n" if $opt_v;
3011 open $cmd,"|$cmd" or die "$0: cannot create $zip - $!\n";
3013 print {$cmd} $_."\n";
3014 print " $_\n" if $opt_v;
3016 close $cmd or die "$0: zip failed - $!\n";
3032 return $_ if length($_);
3039 my ($server,$port,$to,$from,$id,$filename,$fileid) = @_;
3042 my ($head,$location);
3043 my ($response,$fexsrv,$cc);
3047 $to = $AB{$to} if $AB{$to};
3048 $filename =~ s/([^_=:,;<>()+.\w\-])/'%'.uc(unpack("H2",$1))/ge; # urlencode
3050 $head = "HEAD $proxy_prefix/fop/$to/$from/$filename??SKEY=$id HTTP/1.1";
3052 $head = "HEAD $proxy_prefix/fop/$to/$from/$filename??GKEY=$id HTTP/1.1";
3054 $head = "HEAD $proxy_prefix/fop/$to/$from/$filename??ID=$id HTTP/1.1";
3056 sendheader("$server:$port",$head);
3058 unless (defined $_ and /\w/) {
3059 die "$0: no response from server\n";
3062 print "<-- $_" if $opt_v;
3063 unless (/^HTTP.* 200/) {
3068 print "<-- $_" if $opt_v;
3069 $fexsrv = $_ if /^(Server: fexsrv|X-Features:)/;
3072 die "$0: no fexserver at $server:$port\n" unless $fexsrv;
3073 die "$0: server response: $response";
3077 print "<-- $_" if $opt_v;
3079 if (/^Content-Length:\s+(\d+)/) { $seek = $1 }
3080 if (/^X-File-ID:\s+(.+)/) { $qfileid = $1 }
3081 if (/^X-Features:\s+(.+)/) { $features = $1 }
3082 if (/^X-Location:\s+(.+)/) { $location = $1 }
3083 if (/^Connection: close/) { $cc = $_ }
3086 # return true seek only if file is identified
3087 $seek = 0 if $qfileid and $qfileid ne $fileid;
3090 serverconnect($server,$port);
3094 return ($seek,$location);
3098 sub edit_address_book {
3101 my $ab = "$fexhome/ADDRESS_BOOK";
3105 die "$0: address book not available for subusers\n" if $skey;
3106 die "$0: address book not available for group members\n" if $gkey;
3108 female_mode("edit your address book?") if $opt_F;
3110 %AB = query_address_book($server,$port,$user);
3111 if ($AB{ADDRESS_BOOK} !~ /\w/) {
3113 "# Format: alias e-mail-address # Comment\n".
3115 "framstag framstag\@rus.uni-stuttgart.de\n";
3117 open $ab,">$ab" or die "$0: cannot write to $ab - $!\n";
3118 print {$ab} $AB{ADDRESS_BOOK};
3121 system "$editor $ab";
3126 serverconnect($server,$port);
3127 query_sid($server,$port);
3140 sub query_address_book {
3141 my ($server,$port,$user) = @_;
3142 my ($req,$alias,$address,$options,$comment,$cl,$ab,$b);
3147 serverconnect($server,$port);
3148 query_sid($server,$port);
3151 $req = "GET $proxy_prefix/fop/$user/$user/ADDRESS_BOOK?ID=$sid HTTP/1.1";
3152 sendheader("$server:$port",$req);
3154 unless (defined $_ and /\w/) {
3155 die "$0: no response from server\n";
3158 print "<-- $_" if $opt_v;
3159 unless (/^HTTP.* 200/) {
3160 if (/^HTTP.* 404/) {
3161 while (<$SH>) { last if /^\r?\n/ }
3164 # s:HTTP/[\d\. ]+::;
3165 # die "$0: server response: $_";
3173 print "<-- $_" if $opt_v;
3175 $cl = $1 if /^Content-Length: (\d+)/;
3185 print "<-- $_\n" if $opt_v;
3189 ($alias,$address,$options) = split;
3191 if ($options) { $options =~ s/[()]//g }
3192 else { $options = '' }
3193 $AB{$alias} = $address;
3194 $AB{$alias}->{options} = $options||'';
3195 $AB{$alias}->{comment} = $comment||'';
3196 if ($options and $options =~ /keep=(\d+)/i) {
3197 $AB{$alias}->{keep} = $1;
3199 if ($options and $options =~ /autodelete=(\w+)/i) {
3200 $AB{$alias}->{autodelete} = $1;
3208 $AB{ADDRESS_BOOK} = $ab;
3214 # sets global $sid $features $timeout # ugly hack! :-}
3216 my ($server,$port) = @_;
3222 if ($port eq 443 or $proxy) {
3224 return if $features; # early return if we know enough
3225 $req = "OPTIONS /FEX HTTP/1.1"; # does not work with (some) proxies
3226 $req = "GET /SID HTTP/1.1"; # needed as FEATURES query
3228 $req = "GET /SID HTTP/1.1";
3231 sendheader("$server:$port",$req);
3233 unless (defined $_ and /\w/) {
3234 print "\n" if $opt_v;
3235 die "$0: no response from server\n";
3238 print "<-- $_" if $opt_v;
3240 if ($req =~ /OPTIONS/ and /^HTTP.* 502 /) {
3241 # (reverse) proxy error
3243 serverconnect($server,$port);
3244 $req = "GET /SID HTTP/1.0";
3245 sendheader("$server:$port",$req);
3247 unless (defined $_ and /\w/) {
3248 print "\n" if $opt_v;
3249 die "$0: no response from server\n";
3252 print "<-- $_" if $opt_v;
3255 print "<-- $_" if $opt_v;
3256 $features = $1 if /^X-Features: (.+)/;
3257 $timeout = $1 if /^X-Timeout: (\d+)/;
3261 serverconnect($server,$port);
3262 } elsif (/^HTTP.* [25]0[01] /) {
3263 if (not $proxy and $port ne 443 and /^HTTP.* 201 (.+)/) {
3264 $sid = 'MD5H:'.md5_hex($id.$1);
3269 print "<-- $_" if $opt_v;
3270 $features = $1 if /^X-Features: (.+)/;
3271 $timeout = $1 if /^X-Timeout: (\d+)/;
3272 $cc = $_ if /^Connection: close/;
3276 serverconnect($server,$port);
3279 } elsif (/^HTTP.* 301 /) {
3280 while (<$SH>) { last if /Location/ }
3281 die "$0: cannot use $server:$port because server has a redirection to\n".$_;
3283 # no SID support - perhaps transparent web proxy?
3286 print "<-- $_" if $opt_v;
3287 $fexsrv = $_ if /^(Server: fexsrv|X-Features:)/;
3290 die "$0: no fexserver at $server:$port\n" unless $fexsrv;
3291 serverconnect($server,$port);
3295 # warn "proxy: $proxy\n";
3297 serverconnect($server,$port);
3305 my ($from,$id,$save) = @_;
3308 my ($url,$B,$b,$t0,$t1,$cl);
3313 $url = "$proxy_prefix/fop/$from/$from/$xx?ID=$id";
3315 sendheader("$server:$port","GET $url HTTP/1.0");
3319 print "<-- $_" if $opt_v;
3320 $cl = $1 if /^Content-Length:\s(\d+)/;
3321 # $ft = $1 if /^X-File-Type:\s(.+)/;
3325 die "$0: no Content-Length in server-reply\n" unless $cl;
3327 open $save,">$save" or die "$0: cannot write to $save - $!\n";
3330 $t0 = $t1 = int(time);
3333 while ($b = read($SH,$_,$bs)) {
3336 if (int(time) > $t1) {
3340 print STDERR $ts,"\r";
3344 sleep 1 while ($opt_m and $B/k/(time-$t0||1) > $opt_m);
3347 print STDERR ts($B,$cl),"\n";
3355 return sprintf("transferred: %d MB (%d%%)",int($b/M),int($b/$tb*100));
3359 sub sigpipehandler {
3365 local $SIG{ALRM} = sub { };
3371 kill 9,$tpid if $tpid;
3372 if (@r and $opt_v) {
3373 die "\n$0: ($$) server error: @r\n";
3375 if (@r and $r[0] =~ /^HTTP.* \d+ (.*)/) {
3376 die "\n$0: server error: $1\n";
3380 warn "\n$0: connection to $server $reason\n";
3381 warn "retrying after $timeout seconds...\n";
3383 if ($windoof) { exec $^X,$0,@_ARGV }
3384 else { exec $_0,@_ARGV }
3389 sub checkrecipient {
3390 my ($from,$to) = @_;
3398 command => 'CHECKRECIPIENT',
3401 $_ = shift @r or die "$0: no reply from server\n";
3404 return if $to eq 'nettest';
3407 if (s/X-(Recipient: .+)/$1\n/) {
3408 s/autodelete=\w+/autodelete=$opt_D/ if $opt_D;
3409 s/keep=\d+/keep=$opt_k/ if $opt_k;
3411 $frecipient ||= (split)[1];
3415 http_response($_,@r);
3420 # get ID data from ID file
3424 $fexcgi = getline($idf) || die "$0: no FEX-URL in $idf\n";
3425 $from = getline($idf) || die "$0: no FROM in $idf\n";
3426 $id = getline($idf) || die "$0: no ID in $idf\n";
3427 if ($fexcgi =~ s/!([\w.-]+:\d+)(:(\d+))?//) {
3429 $chunksize = $3 || 0;
3431 unless ($fexcgi =~ /^[_:=\w\-\.\/\@\%]+$/) {
3432 die "$0: illegal FEX-URL \"$fexcgi\" in $idf\n";
3434 unless ($from =~ /^[_:=\w\-\.\/\@\%\+]+$/) {
3435 die "$0: illegal FROM \"$from\" in $idf\n";
3445 print "file to send: ";
3446 chomp($file = <STDIN>);
3450 warn "$file does not exist\n";
3452 print "recipient (e-mail address): ";
3453 chomp($to = <STDIN>);
3454 die $usage unless $to;
3457 chomp($opt_C = <STDIN>);
3459 @ARGV = ($file,$to);
3464 if (system(@_) < 0) { die "failed: @_\n" }
3468 # emulate seek on a pipe
3470 my $fh = shift; # filehandle
3471 my $ba = shift; # bytes ahead
3479 $n = $bs if $n > $bs;
3480 $s += read $fh,$_,$n;
3487 my $dirmode = shift;
3488 my @s = $dirmode ? lstat($file) : stat($file);
3491 return md5_hex($file.$s[0].$s[1].$s[7].$s[9]);
3493 warn "$0: $file - $!\n";
3499 sub get_mutt_alias {
3501 my $ma = $HOME.'/.mutt/aliases';
3502 my ($alias,$options);
3505 $to =~ s/(:.+)// and $options = $1;
3506 open $ma,$ma or return $to;
3508 if (/^alias \Q$to\E\s/i) {
3516 warn "$0: ignoring mutt multi-alias $to = $_\n";
3521 warn "$0: found mutt alias $to = $alias\n";
3522 $alias .= $options if $options;
3528 $to = "$to:$options" if $options;
3529 return ($alias||$to);
3533 # collect (hashed) file meta data
3539 foreach $file (@files) {
3540 if (not -l $file and -d $file) {
3542 if (opendir $dir,$dir) {
3543 while (defined ($file = readdir($dir))) {
3544 next if $file eq '..';
3546 $fmd .= fileid($dir);
3547 } elsif (-l "$dir/$file") {
3548 # hack for dangling symlinks: do not raise an error
3549 $fmd .= fileid("$dir/$file",'dirmode');
3551 $fmd .= fmd("$dir/$file");
3557 $fmd .= fileid($file);
3565 # from MIME::Base64::Perl
3571 tr|A-Za-z0-9+=/||cd;
3573 tr|A-Za-z0-9+/| -_|;
3574 return "" unless length;
3577 for ($i = 0; $i <= $l; $i += 60) {
3578 $uu .= "M" . substr($_,$i,60);
3582 $uu .= chr(32+(length)*3/4) . $_;
3584 return unpack("u",$uu);
3590 if (open my $tty,'/dev/tty') {
3594 " [p] perhaps - don't know\n",
3598 if (/^y/i) { return }
3600 if (/^p/i) { int(rand(2)) ? return : exit }
3607 local $_ = shift || <$SH>;
3611 $_ = <$SH> unless $_;
3612 unless (defined $_ and /\w/) {
3613 die "$0: no response from server\n";
3616 print "<-- $_\n" if $opt_v;
3617 # CGI fatalsToBrowser
3618 if (/^HTTP.* 500/) {
3619 @r = <$SH> unless @r;
3621 die "$0: server error: $_\n@r\n";
3623 unless (/^HTTP.* 200/) {
3625 $error =~ s/HTTP.[\s\d.]+//;
3626 @r = <$SH> unless @r;
3630 $error .= "\n".$_ if /^Location/;
3631 print "<-- $_\n" if $opt_v;
3633 die "$0: server error: $error\n";
3647 my $cfb = '### common functions ###';
3652 open $0,$0 or die "cannot read $0 - $!\n";
3655 $cfc =~ s/.*\n$cfb\n//s;
3657 foreach my $p (qw'fexget sexsend') {
3658 open $p,$p or die "cannot read $p - $!\n";
3661 s/\n$cfb.*/\n$cfb\n$cfc/s;
3663 open $p,'>',$p or die "cannot write $p - $!\n";
3668 exec "l fexsend fexget sexsend";
3672 ### common functions ###
3676 my @d = localtime((stat shift)[9]);
3677 return sprintf('%d%02d%02d',$d[5]+1900,$d[4]+1,$d[3]);
3683 s/\%([a-f\d]{2})/chr(hex($1))/ige;
3689 # set SSL/TLS options
3690 $SSL{SSL_verify_mode} = $ENV{SSLVERIFY} if defined($ENV{SSLVERIFY});
3691 foreach my $opt (qw(
3700 $SSL{$opt} = $ENV{$env} if defined($ENV{$env});
3703 if ($SSL{SSL_verify_mode}) {
3705 unless ($SSL{SSL_ca_path} or $SSL{SSL_ca_file}) {
3706 die "$0: \$SSLVERIFYMODE, but not valid \$SSLCAPATH or \$SSLCAFILE\n";
3708 } elsif (defined($SSL{SSL_verify_mode})) {
3709 # user has set SSLVERIFY=0 !
3712 $SSL{SSL_verify_mode} = 1 if $SSL{SSL_ca_path} or $SSL{SSL_ca_file};
3718 return if $SSL{SSL_ca_file} or $SSL{SSL_ca_path};
3719 foreach (qw(/etc/ssl/certs/ca-certificates.crt)) {
3721 $SSL{SSL_ca_file} = $_;
3725 foreach (qw(/etc/ssl/certs /etc/pki/tls/certs)) {
3727 $SSL{SSL_ca_path} = $_;
3735 my ($server,$port) = @_;
3736 my $connect = "CONNECT $server:$port HTTP/1.1";
3740 tcpconnect(split(':',$proxy));
3742 printf "--> %s\n",$connect if $opt_v;
3743 nvtsend($connect,"");
3746 printf "<-- $_"if $opt_v;
3747 unless (/^HTTP.1.. 200/) {
3748 die "$0: proxy error : $_";
3751 $SH = IO::Socket::SSL->start_SSL($SH,%SSL);
3754 tcpconnect($server,$port);
3756 # if ($https and $opt_v) {
3757 # printf "%s\n",$SH->get_cipher();
3762 # set up tcp/ip connection
3764 my ($server,$port) = @_;
3772 # eval "use IO::Socket::SSL qw(debug3)";
3774 $SH = IO::Socket::SSL->new(
3775 PeerAddr => $server,
3781 $SH = IO::Socket::INET->new(
3782 PeerAddr => $server,
3792 die "$0: cannot connect $server:$port - $@\n";
3795 print "TCPCONNECT to $server:$port\n" if $opt_v;
3800 eval "use IO::Socket::SSL";
3801 die "$0: cannot load IO::Socket::SSL\n" if $@;
3802 eval '$SSL{SSL_verify_mode} = 0 if Net::SSLeay::SSLeay() <= 9470143';
3804 foreach my $v (keys %SSL) {
3805 printf "%s => %s\n",$v,$SSL{$v};
3816 push @head,"Host: $sp";
3817 push @head,"User-Agent: $useragent";
3819 foreach $head (@head) {
3821 print "--> $head\n" if $opt_v;
3822 print {$SH} $head,"\r\n";
3824 print "-->\n" if $opt_v;
3830 local $SIG{PIPE} = sub { $sigpipe = "@_" };
3834 die "$0: internal error: no active network handle\n" unless $SH;
3835 die "$0: remote host has closed the link\n" unless $SH->connected;
3837 foreach my $line (@_) {
3838 print {$SH} $line,"\r\n";
3851 s/([^\w\@\/%^,.=+_:+-])/\\$1/g;
3857 print "## DEBUG: @_\n" if $DEBUG;
3861 # from MIME::Base64::Perl
3868 $res = join '',map(pack('u',$_)=~ /^.(\S*)/, ($_[0]=~/(.{1,45})/gs));
3869 $res =~ tr|` -_|AA-Za-z0-9+/|;
3870 $padding = (3-length($_[0])%3)%3;
3871 $res =~ s/.{$padding}$/'=' x $padding/e if $padding;