3 # fexsrv : web server for F*EX service
5 # Author: Ulli Horlacher <framstag@rus.uni-stuttgart.de>
11 use Fcntl qw':flock :seek';
14 BEGIN { $SIG{CHLD} = "DEFAULT" }
17 # setrlimit(RLIMIT_CPU,999,999) or die "$0: $!\n";
19 # SSL remote address provided by stunnel
20 if (@ARGV and $ARGV[0] eq 'stunnel' and $ENV{REMOTE_HOST} =~ /(.+)/) {
24 # KEEP_ALIVE <== callback from CGI
25 if ($ENV{KEEP_ALIVE}) {
26 $keep_alive = $ENV{KEEP_ALIVE};
28 %ENV = (); # clean environment
31 $ENV{HOME} = (getpwuid($<))[7] or die "$0: no HOME\n";
33 # fexsrv MUST be run with full path!
34 if ($0 =~ m:^(/.+)/bin/fexsrv:) {
38 $ENV{FEXHOME} = $FEXHOME;
44 '/usr/local/share/fex',
47 $ENV{FEXLIB} = $FEXLIB = $lib and last if -f "$lib/fex.pp";
48 $ENV{FEXLIB} = $FEXLIB = "$lib/lib" and last if -f "$lib/lib/fex.pp";
52 our ($hostname,$debug,$timeout,$max_error,$max_error_handler);
53 our ($spooldir,$logdir,$docdir,$xkeydir,$lockdir);
54 our ($force_https,$default_locale,$bs,$adlm);
57 # load common code (local config: $FEXHOME/lib/fex.ph)
58 require "$FEXLIB/fex.pp" or die "$0: cannot load $FEXLIB/fex.pp - $!\n";
60 chdir $spooldir or http_die("$0: $spooldir - $!\n");
62 our $log = "$logdir/fexsrv.log";
63 our $error = 'F*EX ERROR';
65 our $hid = ''; # header ID
70 $ENV{GATEWAY_INTERFACE} = 'CGI/1.1';
71 $ENV{SERVER_NAME} = $hostname;
72 $ENV{QUERY_STRING} = '';
73 $ENV{HTTP_COOKIE} = '';
75 $ENV{RANDOM} = randstring(8);
76 $ENV{FEATURES} = join(',',qw(
77 SID CHECKRECIPIENT GROUPS QUOTA FILEID MULTIPOST XKEY FILEQUERY FILESTREAM
78 JUP NOSTORE AXEL FEXMAIL FILELINK
85 if ($ENV{HTTP_HOST} =~ /(.+):(.+)/) {
89 $hostname = $ENV{HTTP_HOST};
90 if ($ENV{PROTO} eq 'https') { $port = 443 }
93 $ra = $ENV{REMOTE_ADDR};
94 $rh = $ENV{REMOTE_HOST};
103 $ENV{PROTO} = 'https';
104 $ENV{REMOTE_ADDR} = $ra = $ssl_ra;
105 if ($ssl_ra =~ /\w:\w/) {
106 # ($rh) = `host $ssl_ra 2>/dev/null` =~ /name pointer (.+)\.$/;
107 $^W = 0; eval 'use Socket6'; $^W = 1;
108 http_error(503) if $@;
109 $iaddr = inet_pton(AF_INET6,$ssl_ra) and
110 $rh = gethostbyaddr($iaddr,AF_INET6);
112 $rh = gethostbyaddr(inet_aton($ra),AF_INET);
116 # print {$log} "X-SSL-Remote-Host: $ssl_ra\n";
121 $ENV{PROTO} = 'http';
122 my $sa = getpeername(STDIN) or die "$0: no network stream on STDIN\n";
123 if (sockaddr_family($sa) == AF_INET) {
124 ($ENV{REMOTE_PORT},$iaddr) = sockaddr_in($sa);
125 $ENV{REMOTE_ADDR} = $ra = inet_ntoa($iaddr);
126 $rh = gethostbyaddr($iaddr,AF_INET);
127 ($port) = sockaddr_in(getsockname(STDIN));
128 } elsif (sockaddr_family($sa) == AF_INET6) {
129 $^W = 0; eval 'use Socket6'; $^W = 1;
130 http_error(503) if $@;
131 ($ENV{REMOTE_PORT},$iaddr) = unpack_sockaddr_in6($sa);
132 $ENV{REMOTE_ADDR} = $ra = inet_ntop(AF_INET6, $iaddr);
133 $rh = gethostbyaddr($iaddr,AF_INET6);
134 ($port) = unpack_sockaddr_in6(getsockname(STDIN));
136 die "$0: unknown IP version\n";
138 $port = 80 unless $port;
141 $ENV{REMOTE_HOST} = $rh || '';
143 $ENV{HTTP_HOST} = ($port == 80 or $port == 443)
144 ? $hostname : "$hostname:$port";
148 if ($reverse_proxy_ip and $reverse_proxy_ip eq $ra) {
149 $ENV{FEATURES} =~ s/SID,//;
152 if (@anonymous_upload and ipin($ra,@anonymous_upload)) {
153 $ENV{FEATURES} .= ',ANONYMOUS';
158 $SIG{CHLD} = "DEFAULT"; # stunnel workaround
161 # printf {$log} "\nTIMEOUT %s %s\n",isodate(time),$connect;
163 debuglog('TIMEOUT',isodate(time));
164 fexlog($connect,@log,"TIMEOUT");
169 REQUEST: while (*STDIN) {
171 if (defined $ENV{REQUESTCOUNT}) { $ENV{REQUESTCOUNT}++ }
172 else { $ENV{REQUESTCOUNT} = 0 }
174 $connect = sprintf "%s:%s %s %s %s [%s_%s]",
175 $keep_alive ? 'CONTINUE' : 'CONNECT',
180 $$,$ENV{REQUESTCOUNT};
181 $hid = sprintf("%s %s\n",$rh||'-',$ra);
186 # read complete HTTP header
187 while (defined ($_ = &getaline)) {
192 # URL-encode non-printable chars
193 s/([\x00-\x08\x0E-\x1F\x7F-\x9F])/sprintf "%%%02X",ord($1)/ge;
195 if (@header and s/^\s+/ /) {
199 $header{$1} = $2 if /(.+)\s*:\s*(.+)/;
203 if (/^(GET \/|X-Forwarded-For|User-Agent)/i) {
208 if ($reverse_proxy_ip and $reverse_proxy_ip eq $ra and
209 /^X-Forwarded-For: ([\d.]+)/
211 $ENV{REMOTE_ADDR} = $ra = $1;
212 $ENV{REMOTE_HOST} = $rh = gethostbyaddr(inet_aton($ra),AF_INET) || '';
213 $ENV{HTTP_HOST} = $hostname;
214 if ($ENV{PROTO} eq 'https') { $port = 443 }
220 exit if $header =~ /^\s*$/;
222 $ENV{HTTP_HEADER} = $header;
224 # http_die("<pre>$header</pre>");
226 $ENV{'HTTP_HEADER_LENGTH'} = $hl;
227 $ENV{REQUEST_URI} = $uri = '';
230 # is it a HTTP-request at all?
231 $request = shift @header;
232 if ($request !~ /^(GET|HEAD|POST|OPTIONS).*HTTP\/\d\.\d$/i) {
233 fexlog($connect,$request,"DISCONNECT: no HTTP request");
234 badlog("no HTTP request: $request");
238 if ($force_https and $port != 443
239 and $request =~ /^(GET|HEAD|POST)\s+(.+)\s+(HTTP\/[\d\.]+$)/i) {
242 "HTTP/1.1 301 Moved Permanently",
243 "Location: https://$hostname$request",
247 fexlog($connect,@log);
251 $request =~ s{^(GET|HEAD|POST) https?://$hostname(:\d+)?}{$1 }i;
253 if ($request =~ m"^(GET|HEAD) /fop/\w+/") {
254 # no header inquisition on regular fop request
257 &$header_hook($connect,$request,$ra) if $header_hook;
260 unless ($keep_alive) {
261 if ($request =~ m:(HTTP/1.(\d)): and $2) {
262 $ENV{KEEP_ALIVE} = $keep_alive = $ra
264 $ENV{KEEP_ALIVE} = $keep_alive = '';
268 if ($request =~ /^OPTIONS FEX HTTP\/[\d\.]+$/i) {
269 fexlog($connect,@log);
272 "X-Features: $ENV{FEATURES}",
273 "X-Timeout: $timeout",
276 next REQUEST if $keep_alive;
280 if ($request =~ m:^GET /?SID HTTP/[\d\.]+$:i) {
281 if ($ENV{FEATURES} !~ /\bSID\b/) {
282 fexlog($connect,@log);
284 "HTTP/1.1 501 Not Available",
286 "X-Features: ".$ENV{FEATURES},
287 "X-Timeout: ".$timeout,
292 $ENV{SID} = randstring(8);
293 fexlog($connect,@log);
295 "HTTP/1.1 201 ".$ENV{SID},
297 "X-Features: ".$ENV{FEATURES},
299 "X-Timeout: ".$timeout,
304 next REQUEST if $keep_alive;
308 if ($request =~ /^(GET|HEAD|POST)\s+(.+)\s+(HTTP\/[\d\.]+$)/i) {
309 $ENV{REQUEST_METHOD} = uc($1);
310 $ENV{REQUEST_URI} = $uri = $cgi = $2;
311 $ENV{HTTP_VERSION} = $protocol = $3;
312 $ENV{QUERY_STRING} = $1 if $cgi =~ s/\?(.*)//;
313 $ENV{PATH_INFO} = $1 if $cgi =~ m:/.+?(/.+?)(\?|$):;
314 $ENV{KEEP_ALIVE} = $keep_alive = '' if $protocol =~ /1\.0/;
315 $ENV{REQUEST_URL} = "$ENV{PROTO}://$ENV{HTTP_HOST}$ENV{REQUEST_URI}";
316 if ($uri =~ /<|%3c/i) { badchar("<") }
317 if ($uri =~ />|%3e/i) { badchar(">") }
318 if ($uri =~ /\||%7c/i) { badchar("|") }
319 if ($uri =~ /\\|%5c/i) { badchar("\\") }
322 while ($_ = shift @header) {
324 # header inquisition!
325 &$header_hook($connect,$_,$ra) if $header_hook;
327 # mega stupid "Download Manager" FlashGet
328 if ($uri =~ m{^/fop/} and m{^Referer: https?://.*\Q$uri$}) {
329 fexlog($connect,@log,"NULL: FlashGet");
330 debuglog("NULL: FlashGet");
331 exec qw'cat /dev/zero' or sleep 30;
335 if ($header =~ /\nRange:/ and /^User-Agent: (FDM)/) {
336 disconnect($1,"499 Download Manager $1 Not Supported",30);
339 if (/^User-Agent: (Java\/[\d\.]+)/) {
340 disconnect($1,"499 User-Agent $1 Not Supported",30);
344 disconnect("Range a,b","416 Requested Range Not Satisfiable",30);
346 if (/^Range:.*(\d+)-(\d+)/) {
348 disconnect("Range a>b","416 Requested Range Not Satisfiable",0);
350 if (($header{'User-Agent'}||'') !~ /$adlm/ ) {
351 disconnect("Range a-b","416 Requested Range Not Satisfiable",30);
355 if (/^Range:.*\d+-$/ and $hid) {
356 my $lock = untaint($lockdir.'/'.md5_hex($hid));
357 if (open $lock,'+>>',$lock) {
358 if (flock($lock,LOCK_EX|LOCK_NB)) {
364 "multiple Range request",
365 "400 Multiple Requests Not Allowed",
372 # client signed int bug
373 if (/^Range:.*-\d+-/) {
374 disconnect("Range -a-","416 Requested Range Not Satisfiable",0);
377 # if (/^Range:/ and $protocol =~ /1\.0/) {
378 # &$header_hook($connect,$_,$ra) while ($header_hook and $_ = shift @header);
379 # fexlog($connect,@log,"DISCONNECT: Range + HTTP/1.0");
380 # debuglog("DISCONNECT: Range + HTTP/1.0");
385 if (/^Connection:\s*close/i) {
386 $ENV{KEEP_ALIVE} = $keep_alive = '';
389 # HTTP header ==> environment variables
390 if (/^([\w\-]+):\s*(.+)/s) {
393 $http_var =~ s/-/_/g;
394 $http_var = uc($http_var);
395 $http_val =~ s/^\s+//;
396 $http_val =~ s/\s+$//;
397 if ($http_var =~ /^X_(FEX_\w+|CONTENT_LENGTH)$/) {
400 $http_val =~ s/\s+/ /g;
401 if ($http_var =~ /^HTTP_(HOST|VERSION)$/) {
402 $http_var = 'X-'.$http_var;
403 } elsif ($http_var !~ /^CONTENT_/) {
404 $http_var = 'HTTP_'.$http_var;
407 $ENV{$http_var} = $http_val;
411 # multiline header inquisition
412 &$header_hook($connect,$header,$ra) if $header_hook;
416 # extra download request? (request http://fexserver//xkey)
417 if ($cgi =~ m{^//([^/]+)$}) {
420 if ($xkey =~ /^afex_\d/) {
421 $dkey = readlink "$xkeydir/$xkey" and $dkey =~ s/^\.\.\///;
423 $dkey = readlink "$xkeydir/$xkey/dkey" and $dkey .= "/$xkey";
426 # xkey downloads are only one time possible - besides afex
427 if ($xkey !~ /^afex_\d/) {
428 unlink "$xkeydir/$xkey/xkey";
429 unlink "$xkeydir/$xkey";
432 "HTTP/1.1 301 Moved Permanently",
433 "Location: $ENV{PROTO}://$ENV{HTTP_HOST}/fop/$dkey",
437 fexlog($connect,@log);
440 fexlog($connect,@log);
446 if (($ENV{QUERY_STRING} =~ /.*locale=([\w-]+)/ or
447 $ENV{HTTP_COOKIE} =~ /.*locale=([\w-]+)/)
448 and -d "$FEXHOME/locale/$1") {
449 $ENV{LOCALE} = $locale = $1;
451 $ENV{LOCALE} = $locale = $default_locale;
454 # check for name based virtual host
455 $vhost = vhost($ENV{'HTTP_HOST'});
459 foreach $var (sort keys %ENV) {
460 debuglog(sprintf " %s = >%s<\n",$var,$ENV{$var});
465 # locale definitions in fex.ph?
468 $locale = $locales[0];
469 } elsif (not grep /^$locale$/,@locales) {
470 $locale = $default_locale;
474 # prepare document file name
475 if ($ENV{REQUEST_METHOD} =~ /^GET|HEAD$/) {
476 $doc = untaint($uri);
477 $doc =~ s/%([\dA-F]{2})/unpack("a",pack("H2",$1))/ge;
478 $doc =~ m:/\.\./: and http_error(403);
481 if ($locale and -e "$docdir/locale/$locale/$doc") {
482 $doc = "$docdir/locale/$locale/$doc";
484 $doc = "$docdir/$doc";
488 # CGI or document request?
489 if ($cgi =~ s:^/+::) {
492 my $login = "$FEXHOME/cgi-bin/login";
494 $cgi = untaint(readlink($login) || $login);
499 $ENV{SCRIPT_NAME} = $cgi;
501 # locale CGIs? (vhost comes already with own FEXLIB)
502 if ($locale and $locale ne 'english'
503 and -f "$FEXHOME/locale/$locale/cgi-bin/$cgi") {
504 $ENV{SCRIPT_FILENAME} = $cgi = "$FEXHOME/locale/$locale/cgi-bin/$cgi";
505 $ENV{FEXLIB} = $FEXLIB = "$FEXHOME/locale/$locale/lib" unless $vhost;
507 $ENV{SCRIPT_FILENAME} = $cgi = "$FEXHOME/cgi-bin/$cgi";
508 $ENV{FEXLIB} = $FEXLIB = "$FEXHOME/lib" unless $vhost;
512 if (-x $cgi and -f $cgi) {
513 if (@forbidden_hosts and ipin($ra,@forbidden_hosts)) {
514 fexlog($connect,@log,"FORBIDDEN");
517 unlink "$logdir/.error/$ra";
518 # push @log,"DEBUG: locale=$locale locales=(@locales)";
519 fexlog($connect,@log,"EXEC $cgi");
520 eval { local $^W = 0; exec $cgi };
521 $status = "$! or bad interpreter";
522 fexlog($connect,@log,"FAILED to exec $cgi : $status");
525 if (-f "$doc/.htindex") {
526 require "$FEXLIB/dop";
527 fexlog($connect,@log);
531 next REQUEST if $keep_alive;
534 if (-f "$doc/index.html") {
535 # force redirect if trailing / is missing
536 # this is mandatory for processing further HTTP request!
539 "HTTP/1.1 301 Moved Permanently",
540 "Location: $ENV{REQUEST_URL}/",
544 fexlog($connect,@log);
545 next REQUEST if $keep_alive;
548 $doc .= '/index.html';
551 $doc =~ s/#.*//; # ignore HTML anchors (stupid msnbot)
553 # special request for F*EX UNIX clients
554 if ($ENV{SCRIPT_NAME} eq 'xx.tar') {
555 bintar(qw'fexget fexsend xx zz ezz');
557 if ($ENV{SCRIPT_NAME} eq 'sex.tar') {
558 bintar(qw'sexsend sexget sexxx');
560 if ($ENV{SCRIPT_NAME} eq 'afex.tar') {
561 bintar(qw'afex asex fexget fexsend sexsend sexget');
563 if ($ENV{SCRIPT_NAME} eq 'afs.tar') {
564 bintar(qw'afex asex fexget fexsend xx sexsend sexget sexxx zz ezz');
566 # URL ends with ".html!" or ".html?!"
567 if ($doc =~ s/(\.html)!$/$1/ or
568 $doc =~ /\.html$/ and $ENV{'QUERY_STRING'} eq '!')
569 { $htmlsource = $doc } else { $htmlsource = '' }
572 or $doc =~ /(.+)\.(tar|tgz|zip)$/ and lstat("$1.stream")
573 or $doc =~ /(.+)\.tgz$/ and -f "$1.tar"
574 or $doc =~ /(.+)\.gz$/ and -f $1)
576 unlink "$logdir/.error/$ra";
577 delete $ENV{SCRIPT_FILENAME};
578 $ENV{DOCUMENT_FILENAME} = $doc;
579 require "$FEXLIB/dop";
580 fexlog($connect,@log);
583 next REQUEST if $keep_alive;
585 } elsif ($uri eq '/bunny') {
586 fexlog($connect,@log);
590 "Content-Type: text/plain",
595 } elsif ($uri eq '/camel') {
596 fexlog($connect,@log);
600 "Content-Type: text/plain",
604 print unpack('u',<DATA>);
607 $status = 'not executable';
614 # neither document nor CGI ==> error
617 fexlog($connect,@log,"FAILED to exec $cgi : $status");
620 fexlog($connect,@log,"UNKNOWN URL");
628 # read one text line unbuffered from STDIN
635 # must use sysread to avoid perl line buffering
636 # (later exec would destroy line buffer)
637 while (sysread STDIN,$c,1) {
650 if (open $log,">>$log") {
652 seek $log,0,SEEK_END;
653 print {$log} "\n",join("\n",@log),"\n";
656 http_die("$0: cannot write to $log - $!\n");
665 fexlog($connect,@log,"DISCONNECT: bad characters in URL");
666 debuglog("DISCONNECT: bad characters in URL $uri");
668 http_die("\"$bc\" is not allowed in URL");
673 my $tmpdir = "$FEXHOME/tmp";
674 my $fs = "$ENV{PROTO}://$ENV{HTTP_HOST}";
676 if (chdir "$FEXHOME/bin") {
677 fexlog($connect,@log);
678 chdir $fstb if $fstb;
681 copy($f,"$tmpdir/$f","s#fexserver = ''#fexserver = '$fs'#");
682 chmod 0755,"$tmpdir/$f";
684 chdir $tmpdir or http_die("internal error: $tmpdir - $!");
685 my $tar = `tar cf - @_ 2>/dev/null`;
690 "Content-Length: ".length($tar),
691 "Content-Type: application/x-tar",
702 my $URL = $ENV{REQUEST_URL}||'';
703 my $URI = $ENV{REQUEST_URI}||'';
706 http_error_header("400 Bad Request");
707 nvt_print("Your request $URL is not acceptable.");
708 } elsif ($error eq 403) {
709 http_error_header("403 Forbidden");
710 nvt_print("You have no permission to request $URL");
711 } elsif ($error eq 404) {
712 http_error_header("404 Not Found");
713 nvt_print("The requested URI $URI was not found on this server.");
714 } elsif ($error eq 416) {
715 http_error_header("416 Requested Range Not Satisfiable");
716 } elsif ($error eq 503) {
717 http_error_header("503 Service Unavailable");
718 # nvt_print("No Perl ipv6 support on this server.");
720 http_error_header("555 Unknown Error");
721 nvt_print("The requested URL $URL produced an internal error.");
725 "<address>fexsrv at <a href=\"/index.html\">$hostname:$port</a></address>",
737 # &$header_hook($connect,$_,$ra) while ($header_hook and $_ = shift @header);
738 fexlog($connect,@log,"DISCONNECT: $info");
739 debuglog("DISCONNECT: $info");
740 errorlog("$ENV{REQUEST_URI} ==> $error");
741 badlog("$ENV{REQUEST_URI} ==> $error ($info)");
744 nvt_print("HTTP/1.0 $error");
749 sub http_error_header {
751 my $uri = $ENV{REQUEST_URI};
753 errorlog("$uri ==> $error") if $uri;
757 "Content-Type: text/html; charset=iso-8859-1",
759 '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">',
761 "<head><title>$error</title></head>",
771 my $ed = "$spooldir/.error";
775 foreach (@ignore_error) {
776 return if $request =~ /$_/;
780 if ($ra and $max_error and $max_error_handler) {
781 mkdir($ed) unless -d $ed;
783 if (open $ra,"+>>$ed/$ra") {
787 printf {$ra} "%s %s\n",isodate(time),$request;
789 &$max_error_handler($ra,@n) if scalar(@n) > $max_error;
796 M("`@("`@("`@("`@("`@("`@("`@("`@("`@("`@("`@("PM)R(G+5P*("`@
797 M("`@("`@("`@("`@("`@("`@("`@("]@8&`M+B`@(&!<("`@("Q=+B`@("`@
798 M("`@("`@("`@("`@("`@("`L+BY?"B`@("`@("`@("`@("`@("`@("`@("`@
799 M+&`@("`@(&`B+B`@72X@("`@(&`N("`@("`@("`@("`@("`@7U]?+BY>(&!?
800 M)V`B(B(*("`@("`@("`@("`@("`@("`@("`@("`I("`@("`@("`G+"TG+2T@
801 M+B`@("!@+B`@("`@(%]?7RPN+2<@("XN("T@("`G("TP("Y?"B`@("`@("`@
802 M("`@+"X@("`@("`@("`@?%]?+"Y?("!?("`N+5\@("!<("`\("Y@(B<G)V`L
803 M7R`@("`@("`@+2!?("TN("`@("`@("<M+BX*("`@("`@("`@('P@(&8M+2TM
804 M+2TM+2<O("`@("!@)RTM+BXL("`@("`M("`@("`@("`@("T@("`@("`@("`@
805 M("`@("`G("XL7U\N7%\N)PH@("`@("`@("`@8"TM)R<G)R<G8"TM7"`@("`@
806 M("`@("`@("!@)RTM+B`@("`@("`@("`@("`@("`@("`@7RPN)RTM)R<*("`@
807 M("`@("`@("`@("`@("`@("`@("\@("`@("`@("`@("`@("`@("!@8#T@+2`@
808 M+2`@("!?+2`@7RXM)PH@("`@("`@("`@("`@("`@("`@("!\("`@("`@("`@
809 M("`@("`@("`@("`@("!?)V`@("`G)U\M+2<*("`@("`@("`@("`@("`@("`@
810 M("`@?"`@("`@("`@("`@("`@("`@("`O("`@?"T]+BXM+2<*("`@("`@("`@
811 M("`@("`@(%\@("`@("<N("`@("`@("`N7R`@(%\N+2=@)R`@(&!?7PH@("`@
812 M("`@("`@("`@("PG("`N("PG("TL3%]?+WP@(%P@8"<G+&\N("`@+5\@("`@
813 M)V`M+2X*("`@("`@("`@("`@("`@+BPG(BT@("`O("`@("`@("`@?"`@8'8@
814 M+R`N("PG8"TM+BXG7R`G+@H@("`@("`@("`@("`@("`@("TN+E\L)R`@("`@
815 M('P@("!\("`@)RTM+E]@("!?+R`@('P@("!\"B`@("`@("`@("`@("`@("`@
816 M("`@("`@("`@("=@8"<@6R`@("`@("`@("=@)R`@("`L+RXN+PH@("`@("`@
817 M("`@("`@("`@("`@("`@("`@('P@("`M+B<@("`@("`@("`@("`@("Y;("`@
818 M+PH@("`@("`@("`@("`@("`@("`@(%]?7U]?7RX@("`L.E]?7U]?7U]?7U]?
819 M7U]?7RQ@("PG"B`@("TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2T@+2(M+2TM
820 M+2TM+2TM+2TM+2TM+2T]+0H@+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM
821 6+2TM+2TM+2TM+2TM+2TM+2TM+2TM"@``