#!/usr/bin/perl -w -T # -*- perl -*- # @configure_input@ BEGIN { # Close stderr, else inetd sends this back to the client. close STDERR; }; use strict; use Socket; use IO::Socket; use Sys::Hostname; use Sys::Syslog qw(:DEFAULT setlogsock); $ENV{PATH} = "/usr/bin:/bin"; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; my $spooldir = "@SPOOLDIR@"; my $user = "dlife"; my $server_version = "@VERSION@"; my $protocol_version = "1.0"; my $cells_uploaded = 0; my $cells_downloaded = 0; my $max_cells_upload_per_pass = 8; my $max_cells_download_per_pass = 8; my $hostname = hostname (); # Open a connection to syslog. setlogsock ("unix"); openlog ("dlife_server", "pid,ndelay", "user"); # Chroot into the spool directory and change our UID/GID so we are safe. my ($login, $pass, $uid, $gid) = getpwnam ($user) or die "$user: user not found in password file"; chroot $spooldir or die "chroot $spooldir: $!"; $) = $gid; $> = $uid; die "could not change UID successfully: $!" unless $> == $uid && $) == $gid; chdir "/store" or die "$spooldir/store: $!"; # Pull out connection information. my $peername = getpeername STDIN; my ($peerport, $peeraddr) = unpack_sockaddr_in ($peername); my $peeraddrstring = inet_ntoa ($peeraddr); my $peerhostname; my $revhostname = gethostbyaddr ($peeraddr, AF_INET); if ($revhostname) { my $ipaddr = gethostbyname ($revhostname); if ($ipaddr && inet_ntoa ($ipaddr) eq $peeraddrstring) { $peerhostname = $revhostname; } } # Log connection information. syslog ("info", "received connection from $peeraddrstring:$peerport" . ($peerhostname ? " ($peerhostname)" : "")); $| = 1; # Send greeting to client. print "DLIFE SERVER $server_version $protocol_version\r\n"; # Loop, waiting for commands from the client. for (;;) { alarm 60; $_ = ; # Remove trailing \r\n. s/[\r\n]+$//; if (/^HELO\s+(.*)\s+(.*)$/) { do_HELO_command ($1, $2); } elsif (/^STOR$/) { do_STOR_command (); } elsif (/^RETR$/) { do_RETR_command (); } elsif (/^QUIT$/) { print "200 Goodbye.\r\n"; last; # Exit the loop. } else { # Unknown command. Return an error message. print "500 Unknown command.\r\n"; } } exit 0; sub do_HELO_command { my $remote_hostname = shift; my $remote_client_version = shift; syslog ("info", "client software version: $remote_client_version"); # Do nothing with this information for now. print "200 Hello.\r\n"; } sub do_STOR_command { if ($cells_uploaded > $max_cells_upload_per_pass) { print "500 Too many cells uploaded in this pass.\r\n"; return; } print "100 OK. Send the cell, terminated by . on a line of its own.\r\n"; # Read in the cell. my $cell = ""; while (length ($cell) < 8192) { alarm 10; $_ = ; alarm 0; s/[\n\r]*$//; last if $_ eq "."; $cell .= $_ . "\n"; } # Save the cell to a file. my $rand = int (rand 1000000000); open CELL, ">$rand.dlo" or die "$rand.dlo: $!"; print CELL $cell; close CELL; syslog ("info", "received cell $rand.dlo"); $cells_uploaded ++; print "200 Cell uploaded OK.\r\n"; } sub do_RETR_command { if ($cells_downloaded > $max_cells_download_per_pass) { print "500 Too many cells downloaded in this pass.\r\n"; return; } # Find a cell at random. my @cells = glob_cells (); if (@cells == 0) { print "400 I have no cells to send you. Try again later.\r\n"; return; } my $r = rand @cells; my $cell_filename = $cells[$r]; unless (open CELL, "<$cell_filename") { print "400 Another process grabbed that cell before I could send it. Try again.\r\n"; return; } # Send it. print "100 OK. Sending you a cell now.\r\n"; syslog ("info", "sending cell $cell_filename"); while () { s/[\n\r]+$//; print $_, "\r\n"; } close CELL; unlink $cell_filename; print ".\r\n"; $cells_downloaded ++; print "200 Finished sending the cell.\r\n"; } # This function is equivalent to glob ("*.dlo"), except that # the glob function doesn't work when tainting is enabled, alas. sub glob_cells { opendir DIR, "." or die "$spooldir/store: $!"; my @names = map { untaint_string ($_) } grep { /\.dlo$/ } readdir DIR; closedir DIR; return @names; } sub untaint_string { my $s = shift; $s =~ /^(.*)$/; return $1; }