3 # Interchange::Link -- mod_perl 1.99/2.0 module for linking to Interchange
5 # Copyright (C) 2002-2009 Interchange Development Group
6 # Copyright (C) 1996-2002 Red Hat, Inc.
8 # This program is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License as
10 # published by the Free Software Foundation; either version 2 of the
11 # License, or (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 package Interchange::Link;
26 use ModPerl::Registry;
28 use Apache2::Const -compile => qw(DECLINED OK NOT_FOUND FORBIDDEN REDIRECT HTTP_MOVED_PERMANENTLY);
29 use Apache2::ServerRec ();
30 require Apache2::Connection;
31 require Apache2::RequestRec;
32 require Apache2::RequestIO;
33 require Apache2::RequestUtil;
36 $ENV{PATH} = "/bin:/usr/bin";
39 ## This is what is returned when the environment returns undef
44 Interchange::Link -- mod_perl 1.99/2.0 module for linking to Interchange
52 <Location /foundation>
53 SetHandler perl-script
54 PerlResponseHandler Interchange::Link
55 PerlOptions +GlobalRequest
56 PerlSetVar InterchangeServer /var/run/interchange/socket
57 PerlSetVar OrdinaryFileList "/foundation/images/ /foundation/dl/"
62 Interchange::Link is designed to replace the vlink and tlink programs
63 that come with Interchange. The Interchange link protocol has been
64 implemented via an Apache mod_perl modules which saves us the (small) overhead
65 of the execution of a CGI program.
67 In addition, it will deliver downloadable files in a streaming fashion
68 without keeping Interchange open, which cuts overhead dramatically for
69 large downloadable files. See L<FileDeliveryBase>.
71 Note that this module is not compatible with Apache 1.
75 You must have mod_perl 1.99 or higher installed on your Apache. On a
76 Red Hat-style Linux system, it is as simple as:
78 rpm -i mod_perl-1.99.XX-X.rpm
81 Installation of mod_perl will vary from system to system. Consult the
82 mod_perl documentation. Sometimes it is as easy as
84 perl -MCPAN -e 'install ModPerl::Registry'
88 Usually you can download the package from http://perl.apache.org/ and
89 follow those instructions.
93 You must specify that Apache use mod_perl, and you must tell it where
94 to find the Perl modules you want to use.
96 On a Red Hat Linux system you might copy this file to /usr/lib/httpd/perl/
99 mkdir -p /usr/lib/httpd/perl/Interchange
100 cp Link.pm /usr/lib/httpd/perl/Interchange
102 If you have mod_perl2 1.999_21 or earlier, you should instead do:
104 mkdir -p /usr/lib/httpd/perl/Interchange
105 cp Link.pm.mod_perl-1.999_21_and_before /usr/lib/httpd/perl/Interchange
107 Then you provide a startup script that tells mod_perl where its
110 cd /usr/lib/httpd/perl
111 echo "use lib qw(/usr/lib/httpd/perl);1;" > startup.pl
113 Then you can put in your /etc/httpd/conf/httpd.conf:
116 PerlRequire /usr/lib/httpd/perl/startup.pl
118 Finally, you specify a location like:
120 <Location /foundation>
121 SetHandler perl-script
122 PerlResponseHandler Interchange::Link
123 PerlOptions +GlobalRequest
124 # Make sure you set SocketPerms to 0666 (or 0660 with appropriate
125 # setgid group ownership of the directory)
126 PerlSetVar InterchangeServer /var/run/interchange/socket
127 PerlSetVar OrdinaryFileList "/foundation/images/ /foundation/dl/"
130 Note: The Apache <Location> path should not contain a dot (.) or any
131 other characters except A-Z, a-z, 0-9 or a hyphen (-), so:
133 <Location /shop.name> is invalid, whereas:
134 <Location /shop-name> is valid.
136 The specifics of the configuration are discussed in the next section.
140 The module understands directives set via the mod_perl C<PerlSetVar>
145 =item InterchangeServer
147 This specifies the way to contact the
148 primary and possibly additionial Interchange servers. The InterchangeServer
149 directive takes either a pathname to the Interchange UNIX socket or a
150 host:port specification if you want to use INET mode.
152 Normally this takes the form of:
154 PerlSetVar InterchangeServer /var/run/interchange/socket
156 Note that your file permissions for the socket file need to allow the
157 Apache User uid to read and write it. This usually means "SocketPerms
158 0666" or in interchange.cfg. You can also do "SocketPerms 0660" if you
159 set the group of the containing directory to the Apache Group value,
160 and change the directory permissions to enable the setgid bit. (That is
161 accomplished with "chmod g+s <directory>".)
163 If you want to specify more than one so that a backup server can provide
164 request support in case of failure:
166 PerlSetVar InterchangeServer "/var/run/interchange/socket 10.1.1.1:7786"
168 The optional InterchangeServerBackup directive takes the same arguments,
169 but should obviously point to a different Interchange server than the
170 primary. The InterchangeServerBackup directive is only of any use if
171 you have multiple Interchange servers configured in a clustered environment.
173 If you want to randomly select from a series of clustered servers, do:
175 PerlSetVar InterchangeServer "10.1.1.1:7786 10.1.1.2:7786 10.1.1.3:7786"
176 PerlSetVar RandomServer 1
178 Note: The Apache <Location> path should not contain a dot (.) or any
179 other characters except A-Z, a-z, 0-9 or a hyphen (-), so:
181 <Location /shop.name> is invalid, whereas:
182 <Location /shop-name> is valid.
184 Example of a UNIX mode local connection:
187 SetHandler perl-script
188 PerlResponseHandler Interchange::Link
189 PerlSetVar InterchangeServer /opt/interchange/etc/socket
192 Example of INET mode local connection:
195 SetHandler perl-script
196 PerlResponseHandler Interchange::Link
197 PerlSetVar InterchangeServer localhost:7786
200 UNIX mode local primary connection and INET mode remote backup connection:
203 SetHandler perl-script
204 PerlResponseHandler Interchange::Link
205 PerlSetVar InterchangeServer /opt/interchange/etc/socket
206 PerlSetVar InterchangeServerBackup another.server.com:7786
209 The default if not set is C<127.0.0.1:7786>.
211 =item ConnectTries and ConnectRetryDelay
213 The ConnectTries parameter specifies the number of connection attempts to
214 make before giving up. ConnectRetryDelay specifies the delay, in seconds,
215 between each retry attempt.
217 The ConnectTries default is 10 and the ConnectRetryDelay default is 2 seconds.
221 SetHandler perl-script
222 PerlResponseHandler Interchange::Link
223 PerlSetVar ConnectTries 10
224 PerlSetVar ConnectRetryDelay 1
227 =item DropRequestList
229 The DropRequestList allows a list of space-separated URI components
230 to be specified. If one of the list entries is found anywhere in the
231 requested URI, the request will be dropped with a 404 (not found) error,
232 without the request being passed to Interchange. This parameter is useful
233 for blocking known Microsoft IIS attacks like "Code Red", so that we don't
234 waste any more time processing the (bogus) requests than we have to.
237 SetHandler perl-script
238 PerlResponseHandler Interchange::Link
239 PerlSetVar DropRequestList "/default.ida /x.ida /cmd.exe /root.exe"
242 =item OrdinaryFileList
244 The OrdinaryFileList allows a list of space-separated URI path
245 components to be specified. If one of the list entries is found at the
246 start of any request then that request will not be passed to Interchange.
247 Instead, the file will be directly served by Apache. For example:
250 SetHandler perl-script
251 PerlResponseHandler Interchange::Link
252 PerlSetVar OrdinaryFileList "/foundation/ /interchange-5/ /robots.txt"
255 This will result in the following:
257 www.example.com/index.html (handled by Interchange)
258 www.example.com/ord/basket.html (handled by Interchange)
259 www.example.com/foundation/images/somefile.gif (served by Apache)
260 www.example.com/robots.txt (served by Apache)
262 You should add a trailing slash to directory names to prevent, for instance,
263 "/images/foo.gif" from being confused with the likes of "/images.html".
264 If OrdinaryFileList was set to "/images" then both of those requests would
265 be handled by Apache. If OrdinaryFileList was set to "/images/" then
266 "/images/foo.gif" would be handled by Apache and "/images.html" would be
267 handled by Interchange.
269 If you're using "<Location />" then you will need a dummy "index.html" file
270 in your VirtualHost's DocumentRoot directory to avoid permission problems
271 assocated with the Apache directory index creation code.
273 =item InterchangeScript
275 The InterchangeScript parameter allows the SCRIPT_NAME to be different from
276 the <Location>. For example:
282 The above will set the SCRIPT_NAME to "/shop".
286 PerlSetVar InterchangeScript /foo
289 The above will set the SCRIPT_NAME to "/foo", instead of "/shop" before
290 passing the request to Interchange.
292 The appropriate SCRIPT_NAME must be configured into the "Catalog"
293 directive in your interchange.cfg file.
295 =item FileDeliveryBase
297 Interchange::Link can deliver files without needing to keep Interchange
298 open. To do this, you set the HTTP Status: header to C<httpd_deliver>.
299 In Interchange 5.0 or higher you can do this by putting in a page:
303 location=directory/file.ext
304 type=application/octet-stream
307 The C<FileDeliveryBase> setting determines where the file will be
308 relative to. While you can set it to C</>, that is not recommended
309 as files like C</etc/passwd> could be delivered.
311 The default is the document root of the Apache server. To protect
312 files from being served directly by Apache, you can either put them
313 under a directory at the Interchange location, or you can use normal
318 Set C<NoBlankLines> to 1 to remove blank lines from the outputted source
325 Send bug reports and suggestions to the Interchange users list,
326 <interchange-users@icdevgroup.org>.
328 =head1 COPYRIGHT AND LICENSE
330 Copyright (C) 2002-2009 Interchange Development Group
331 Copyright (C) 1996-2002 Red Hat, Inc.
333 This program is free software. You can redistribute it and/or modify
334 it under the terms of the GNU General Public License as published by
335 the Free Software Foundation. You may refer to either version 2 of the
336 License or (at your option) any later version.
338 This program is distributed in the hope that it will be useful, but
339 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
340 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
350 my $location = $r->location;
352 return $config{$location} if $config{$location} && !($s->is_virtual());
354 #warn "Getting location $location\n";
356 my $c = $config{$location} = {};
358 $c->{InterchangeScript} = $r->dir_config('InterchangeScript') || $location;
360 my @OrdinaryFileList;
362 my $ordstring = $r->dir_config('OrdinaryFileList')
364 @OrdinaryFileList = grep /\S/, split /\s+/, $ordstring;
365 for (@OrdinaryFileList) {
372 my $dropstring = $r->dir_config('DropRequestList')
374 @DropRequestList = grep /\S/, split /\s+/, $dropstring;
375 for (@DropRequestList) {
381 my $serverpool = $r->dir_config('ServerPool');
383 $serverpool = $r->dir_config('InterchangeServer');
384 if($serverpool and $r->dir_config('InterchangeServerBackup')) {
385 $serverpool .= " " . $r->dir_config('InterchangeServerBackup');
389 $serverpool ||= '127.0.0.1:7786';
390 $serverpool =~ s/^\s+//;
391 $serverpool =~ s/\s+$//;
393 my @servpool = split /\s+/, $serverpool;
394 $c->{ServerPool} = \@servpool;
397 my $base = $r->dir_config('FileDeliveryBase') || $r->document_root;
398 $base =~ s:/*$:/: unless $base eq '/';
400 $c->{FileDeliveryBase} = $base;
402 $c->{RandomServer} = $r->dir_config('RandomServer');
404 $c->{ConnectTries} = $r->dir_config('ConnectTries') || 10;
405 $c->{ConnectRetryDelay} = $r->dir_config('ConnectRetryDelay') || 2;
407 $c->{DropRequestList} = \@DropRequestList if @DropRequestList;
408 $c->{OrdinaryFileList} = \@OrdinaryFileList if @OrdinaryFileList;
418 sub server_not_running {
423 warn "ALERT: Interchange server not running for $ENV{SCRIPT_NAME}\n";
425 $r->content_type ("text/html");
427 <html><head><title>Service unavailable</title></head>
430 We are temporarily out of service or may be experiencing high system demand.
431 Please try again soon.
443 # Read the entity from stdin if present.
448 my $val = "arg $count\n";
456 sub send_environment {
458 my $c = $r->connection;
460 #warn("Connection=$c");
469 my $location = $r->location;
470 my $cfg = setup_location($r);
472 if(my $ord = $cfg->{OrdinaryFileList}) {
474 #warn "checking for OrdinaryFile $_\n";
475 next unless $uri =~ $_;
476 $global_status = Apache2::Const::DECLINED;
481 if(my $drop = $cfg->{DropRequestList}) {
483 #warn "checking for DropRequest $_\n";
484 next unless $uri =~ $_;
485 $r->headers_out->{Status} = '404 Not found';
486 $r->content_type('text/html');
487 #warn "dropping request for $uri\n";
488 $global_status = Apache2::Const::NOT_FOUND;
493 my $method = $r->method;
494 #warn "method=$method\n";
496 $uri =~ s{^$location}{} unless $location eq '/';
498 my $query = $r->args;
500 my $script = $cfg->{InterchangeScript};
503 AUTHORIZATION_TYPE AUTH_TYPE
504 AUTHORIZATION AUTHORIZATION
506 CLIENT_HOSTNAME REMOTE_HOST
507 CLIENT_IP_ADDRESS REMOTE_ADDR
508 CLIENT_IDENT REMOTE_IDENT
509 CONTENT_LENGTH CONTENT_LENGTH
510 CONTENT_TYPE CONTENT_TYPE
515 METHOD REQUEST_METHOD
517 PATH_TRANSLATED PATH_TRANSLATED
520 RECONFIGURE RECONFIGURE_MINIVEND
523 SERVER_HOST SERVER_NAME
524 SERVER_PORT SERVER_PORT
525 USER_AGENT HTTP_USER_AGENT
526 CONTENT_ENCODING HTTP_CONTENT_ENCODING
527 CONTENT_LANGUAGE HTTP_CONTENT_LANGUAGE
528 CONTENT_TRANSFER_ENCODING HTTP_CONTENT_TRANSFER_ENCODING
532 for(keys %{$r->headers_in}) {
533 my $val = $r->headers_in->{$_};
536 $k = $header_map{$k} || "HTTP_$k";
538 #warn "header $_/$k=$val\n";
542 SCRIPT_NAME => $script,
543 REQUEST_METHOD => $r->method,
546 QUERY_STRING => $query,
547 REMOTE_ADDR => $c->remote_ip,
555 my $n = shift @pairs;
556 my $v = shift @pairs;
561 $val .= length($str);
564 $val = "env $count\n$val";
570 my $len = $r->headers_in->{'Content-Length'};
571 return '' unless $len > 0;
572 return "entity\n$len ";
578 for ($i = @$array; --$i; ) {
579 my $j = int rand ($i+1);
581 @$array[$i,$j] = @$array[$j,$i];
587 #warn "current location=" . $r->location . "\n";
590 #warn "Entering handler.\n";
591 $arg = send_arguments($r);
592 #warn "Got arguments.\n";
593 $env = send_environment($r)
594 or return $global_status;
595 #warn "Got environment.\n";
596 $ent = check_entity($r);
597 #warn "Got entity.\n";
599 my $cfg = setup_location($r);
601 $SIG{PIPE} = 'IGNORE';
602 $SIG{ALRM} = sub { server_not_running($r); exit 1; };
604 my ($remote, $port, $iaddr, $paddr, $proto, $line);
610 my $socklist = $cfg->{ServerPool};
612 if($cfg->{RandomServer}) {
613 $socklist = [ @$socklist ];
618 while ($tries++ < $cfg->{ConnectTries}) {
619 for my $sockname (@$socklist) {
620 #warn "InterchangeServer=$sockname\n";
621 if($sockname =~ m{/}) {
622 socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!\n";
623 $paddr = sockaddr_un($sockname);
624 #warn "vlink $sockname RandomServer=$cfg->{RandomServer}\n";
627 ($remote, $port) = split /:/, $sockname, 2;
628 #warn "tlink remote=$remote port=$port RandomServer=$cfg->{RandomServer}\n";
631 if ($port =~ /\D/) { $port = getservbyname($port, 'tcp'); }
633 $iaddr = inet_aton($remote);
634 $paddr = sockaddr_in($port,$iaddr);
636 $proto = getprotobyname('tcp');
638 socket(SOCK, PF_INET, SOCK_STREAM, $proto) or die "socket: $!\n";
642 #warn "Ready to connect.\n";
644 $ok = connect(SOCK, $paddr);
645 } while ( ! defined $ok and $! =~ /interrupt/i);
647 my $def = defined $ok;
648 warn "ok=$ok defined=$def sockname=$sockname: $!\n" if ! $ok;
651 #warn "Connected.\n";
654 sleep($cfg->{ConnectRetryDelay});
658 server_not_running($r);
659 return Apache2::Const::OK;
662 my $former = select SOCK;
671 #warn "there is an entity=$ent";
683 #warn "reading from SOCK\n";
685 #warn "GOT header read from SOCK: $_\n";
695 next unless /^[-\w]+:/;
697 my ($k, $v) = split /:\s*/, $_, 2;
699 if($lc eq 'content-type') {
703 elsif($lc eq 'status') {
706 elsif($lc eq 'mod-perl-deliver') {
707 $deliver_object = $v;
709 #warn "Setting header=$k to '$v'\n";
710 $r->headers_out->{$k} = $v;
713 $set_content ||= 'text/html';
716 if($set_status =~ /^30[21]/) {
717 #warn "Doing redirect\n";
718 $r->content_type($set_content);
719 close (SOCK) or die "close: $!\n";
720 return Apache2::Const::HTTP_MOVED_PERMANENTLY if $set_status == 301;
721 return Apache2::Const::REDIRECT;
723 elsif($set_status =~ /^404/) {
724 #warn "404 not found status\n";
725 close (SOCK) or die "close: $!\n";
726 return Apache2::Const::NOT_FOUND;
728 elsif($set_status eq 'httpd_deliver') {
729 $deliver_object = $set_status;
733 if($deliver_object) {
734 my $fn = $r->headers_out->{Location}
735 or die "No location for delivery.\n";
737 $fn =~ s:^/*:$cfg->{FileDeliveryBase}:;
739 close SOCK or die "close: $!\n";
740 unless (open IN, "< $fn") {
741 warn "cannot open mod_perl_deliver $fn: $!\n";
742 $r->content_type('text/html');
744 $r->headers_out->{Status} = '404 Not found';
745 return Apache2::Const::NOT_FOUND;
748 $r->headers_out->{Status} = '403 Permission denied';
749 return Apache2::Const::FORBIDDEN;
753 $r->headers_out->{Status} = '200 OK';
754 $r->headers_out->{'Content-Length'} = -s $fn;
755 $r->content_type($set_content);
759 or die "recipient write for $deliver_object failed: $!\n";
762 or die "cannot close $deliver_object: $!\n";
766 $r->content_type($set_content);
767 my $no_blank_lines = $r->dir_config('NoBlankLines');
769 push @out, $_ unless $no_blank_lines and ! /\S/;
771 close (SOCK) or die "close: $!\n";
775 #warn "Returning OK\n";
776 return Apache2::Const::OK;