#!/usr/bin/perl -w -T # -*- perl -*- # @configure_input@ use strict; use Getopt::Long; use LWP::UserAgent; use Socket; use IO::Socket; use Sys::Hostname; use Sys::Syslog qw(:DEFAULT setlogsock); my $help; my $verbose; my $config_filename = "@CONFDIR@/client.conf"; my $spooldir = "@SPOOLDIR@"; my $client_version = "@VERSION@"; GetOptions ("help|?" => \$help, "verbose" => \$verbose, "config=s" => \$config_filename, "spooldir=s" => \$spooldir); if ($help) { print STDERR <) { s/[\n\r]*$//; next if /^\s*\#/; next if /^\s*$/; if (/^\s*server_url\s+(\S+)\s*$/) { $server_url = $1; print "server_url=$server_url\n" if $verbose; } elsif (/^\s*server_zone\s+(.*)\s*$/) { @server_zones = split /\s+/, $1; print "server_zones=", join (" ", @server_zones), "\n" if $verbose; } elsif (/^\s*server\s+(.*)\s*$/) { @servers = split /\s+/, $1; print "servers=", join (" ", @servers), "\n" if $verbose; } elsif (/^\s*max_cells_upload_per_pass\s+([1-9][0-9]*)\s*$/) { $max_cells_upload_per_pass = $1; } elsif (/^\s*max_cells_download_per_pass\s+([1-9][0-9]*)\s*$/) { $max_cells_download_per_pass = $1; } else { die "unknown configuration option: $_"; } } close CONFIG; # Load @server_zones into a hash for rapid searching. my %server_zones; foreach (@server_zones) { $server_zones{$_} = 1; } # Server URL set or @servers not empty? unless ($server_url || @servers) { die "neither server_url and servers was set: cannot do anything"; } # Go to spool directory. chdir $spooldir or die "$spooldir: $!"; # Find the list of cells to upload and arrange into a random order. my @all_upload_cells = randomize_list (glob_outgoing ()); unless (@all_upload_cells) { syslog ("info", "no cells to upload (is the dlife_soup process running?)"); exit; } # Only upload the first few cells. my @upload_cells = @all_upload_cells; splice @upload_cells, $max_cells_upload_per_pass; # If server URL set, then go and download the webpage. Load appropriate # server names into @servers. if ($server_url) { syslog ("info", "contacting $server_url"); print "Contacting $server_url\n" if $verbose; my $ua = LWP::UserAgent->new; my $req = HTTP::Request->new ("GET", $server_url); my $rep = $ua->request ($req); if ($rep->is_success) { print "Fetched page. Parsing page ...\n" if $verbose; # Parse the page. my @lines = split /[\n\r]+/, $rep->content; foreach (@lines) { if (/^\s*server\s+(\S*)\s+(.*)\s*$/) { my $server = $1; my @zones = split /\s+/, $2; # Is this server in one of our zones? if (@server_zones) { foreach (@zones) { if (exists $server_zones{$_}) { push @servers, $server; last; } } } else { push @servers, $server; } } } print "Finished parsing page. \@servers = ", join (" ", @servers), "\n" if $verbose; } else { syslog ("error", "could not contact $server_url"); print "Could not contact server.\n" if $verbose; } } # Sort the server list into a random order. @servers = randomize_list (@servers); # Contact each server in turn until we succeed with one of them. my $server; foreach $server (@servers) { alarm 0; print "Attempting to connect to $server port 5904\n" if $verbose; syslog ("info", "exchanging cells with $server"); my $socket = new IO::Socket::INET (PeerAddr => $server, PeerPort => "5904", Proto => "tcp"); unless ($socket) { syslog ("error", "could not connect to $server"); print "Failed to connect.\n" if $verbose; next; } print "Connected.\n" if $verbose; # Read server and protocol version. alarm 60; $_ = $socket->getline; unless (/^DLIFE SERVER\s+([0-9]+\.[0-9]+)\s+([0-9]+\.[0-9]+)/) { print "Unrecognized server greeting.\n" if $verbose; next; } my $server_version = $1; my $protocol_version = $2; # The only protocol we recognize right now is version 1.x. These # x (minor) revisions will be backwards compatible. If an # incompatibility is introduced in the future, then that will # become version 2.x, 3.x, etc. unless ($protocol_version =~ /^1\./) { print "Unsupported protocol version.\n" if $verbose; next; } # Send our client greeting string. $socket->print ("HELO - $client_version\r\n"); # Wait for OK response. alarm 60; $_ = $socket->getline; unless (/^2[0-9][0-9]\s/) { print "Error response from server during HELO.\n" if $verbose; next; } # Check for cells to upload, and upload the first few. my $cell_filename; foreach $cell_filename (@upload_cells) { print "Uploading $cell_filename ...\n" if $verbose; $socket->print ("STOR\r\n"); # Wait for OK to send response. alarm 60; $_ = $socket->getline; unless (/^1[0-9][0-9]\s/) { print "Error response from server during STOR.\n" if $verbose; next; } # Send the cell. open CELL, "<$cell_filename" or die "$cell_filename: $!"; while () { s/[\n\r]*$//; $socket->print ($_, "\r\n"); } close CELL; $socket->print (".\r\n"); # Wait for OK response from server. alarm 60; $_ = $socket->getline; unless (/^2[0-9][0-9]\s/) { print "Error response from server after STOR.\n" if $verbose; next; } } # Remove all upload cells in the queue. foreach $cell_filename (@all_upload_cells) { unlink $cell_filename; } # Check for cells to download. for (my $i = 0; $i < $max_cells_download_per_pass; ++$i) { print "Downloading ...\n" if $verbose; $socket->print ("RETR\r\n"); # Wait for OK to retrieve response. alarm 60; $_ = $socket->getline; unless (/^1[0-9][0-9]\s/) { print "Error response from server during RETR.\n" if $verbose; next; } # Retrieve the cell. my $cell = ""; while (length ($cell) < 8192) { alarm 10; $_ = $socket->getline; alarm 0; s/[\n\r]*$//; last if $_ eq "."; $cell .= $_ . "\n"; } # Wait for OK response. alarm 60; $_ = $socket->getline; unless (/^2[0-9][0-9]\s/) { print "Error response from server after RETR.\n" if $verbose; next; } # Save the cell to a file. my $rand = int (rand 1000000000); open CELL, ">incoming/$rand.dlo" or die "incoming/$rand.dlo: $!"; print CELL $cell; close CELL; } print "Disconnecting ...\n" if $verbose; # Say goodbye. $socket->print ("QUIT\r\n"); # Wait for OK response from server. alarm 60; $_ = $socket->getline; unless (/^2[0-9][0-9]\s/) { print "Error response from server during QUIT.\n" if $verbose; next; } # Close socket. $socket->close; alarm 0; last; } syslog ("info", "exit"); exit; sub randomize_list { for (my $i = 0; $i < @_; ++$i) { my $r = int (rand (@_ - $i)); if ($r > 0) { # Swap elements $i and $i+$r. my $t = $_[$i+$r]; $_[$i+$r] = $_[$i]; $_[$i] = $t; } } return @_; } # This function is equivalent to glob ("outgoing/*.dlo"), except that # the glob function doesn't work when tainting is enabled, alas. sub glob_outgoing { opendir DIR, "outgoing" or die "outgoing: $!"; my @names = map { untaint_string ($_) } map { "outgoing/$_" } grep { /\.dlo$/ } readdir DIR; closedir DIR; return @names; } sub untaint_string { my $s = shift; $s =~ /^(.*)$/; return $1; }