#!/usr/bin/perl -w $PROGNAME="PSX"; # Perl Scour eXchange $VERSION="0.9"; # Copyright (C) 2000 Vince Busam # This program is provided under the terms of the GPL # If you don't know what those terms are, see http://www.gnu.org/ # Authors: # Vince Busam # Naveen Nalam #Import these use Getopt::Long; use IO::Socket; use IO::Select; use MD5; use MIME::Base64; require 'sys/ioctl.ph'; # Needed for FIONREAD #Forward declarations sub login; sub newlogin; sub checklogin; sub get; sub recvfile; sub search; sub searchresults; sub connecttoserver; sub readconfig; sub getstats; sub getcommand; sub selectloop; sub handlehelo; sub getuserstatus; sub handleinput; sub acceptconn; sub bindport; sub sendfile; sub adddir; sub getfirewall; sub usage; sub sendqueue; sub recvqueue; sub getrecvqueue; sub prompt; sub showtran; sub addhotlist; sub remhotlist; sub showuserstatus; sub readhotlist; sub writehotlist; sub killtrans; sub piperr; sub md5file; sub reconnect; sub results; sub sigint; sub cancelsearch; sub resume; sub writemd5cache; sub readmd5cache; sub sendmessage; sub recvmessage; sub failmessage; #CONSTANTS #In results array my $RESULTS = 0; my $NAME = 1; my $IP = 2; my $PORT = 3; my $SERVER = 4; my $SIZE = 5; my $HEIGHT = 6; my $WIDTH = 7; my $BITRATE = 8; my $FREQ = 9; my $DURATION = 10; my $FPS = 11; my $MD5 = 12; #In Transfer queue array my $FH = 0; my $SOCK = 3; my $SENT = 4; my $USER = 6; my $AGENT = 7; my $RESINDEX = 8; #Download block size my $BSIZE = 1024; # Block size to send my $MD5SIZE = 300*1024; # MD5 first x bytes of file my $MAXADD = 200; # Only add this many files per ADD #Connect timeout my $TO = 5; #bandwidth identifier $bwhash{0} = "Unknown"; $bwhash{1} = "14.4"; $bwhash{2} = "28.8"; $bwhash{3} = "33.6"; $bwhash{4} = "56.7"; $bwhash{5} = "64K ISDN"; $bwhash{6} = "128K ISDN"; $bwhash{7} = "Cable"; $bwhash{8} = "DSL"; $bwhash{9} = "T1"; $bwhash{10} = "T3"; #GLOBALS my $username = ""; # Username, related info my $pass = MD5->hexhash("pass"); my $first = ""; my $last = ""; my $email = ""; my $useragent = "$PROGNAME/$VERSION"; my $downloaddir = "/tmp"; my $serverconn = STDOUT; # Socket to server my $myport = 10000; # Local port for serving my $myip = "127.0.0.1"; # Local IP for serving my $myspeed = 0; # Bandwidth identifier my $searchid = 0; # Unique ID for each search my $maxresults = 22; # Max resuls (to fit on one screen) my $serveraddr = "stp.scour.net"; # Server address/port my $serverport = "80"; my @res = (); # Search results my $totalusers = 0; # Stats on connected users my $totalfiles = 0; my $totalsize = 0; my $localsock = 0; # Local server socket my %opts = (); # Command line opts my $login = 0; # Login status my $words = (); # Last search query my $next = 0; # Offset of next search my $shareddirs = ""; # regex of shared dirs my @sendfiles = (); # Files being sent / Info stored for time slicing my @recvfiles = (); # Files being downloaded my $fhq = 0; # File handle queue, uniqe id for each FH my %hotlist = (); # hotlist of connected users my $hotlistfile = ""; # save hotlist here my $type = "all"; # search type my $sharetype = ""; # stuff to share my $pipe = 0; # Broken pipe my $fallback = 2; # Fallback time to reconnect to server my $blockstr = ""; # Where we'd be blocking right now my $sigints = 0; # How many times we've Cntrl-C'd my $noreconnect = 0; # don't try to reconnect my %reshash = (); # Store filename we want to recv for firewall transfer my $getadd = 0; # Add this to the number given to get to current index in @res my %md5cache = (); # Store MD5 sums of files my $md5cachefile = ""; # File to store MD5 sums in my $numadded = 0; # Number of files we've added my $msgseq = 0; # Message sequence #Initialize $res[$RESULTS] = 0; $| = 1; $SIG{PIPE} = \&piperr; $SIG{INT} = \&sigint; #MAIN #Read in config file, command line options Getopt::Long::Configure("no_ignore_case"); GetOptions(\%opts, 'new|n', 'server|s=s', 'port|p=i', 'conf|c=s', 'user|u=s', 'pass|P=s', 'version|v', 'help|h'); usage if $opts{help}; die "$PROGNAME/$VERSION\n" if $opts{version}; if ($opts{conf}) { readconfig $opts{conf}; } else { readconfig("$ENV{'HOME'}/.psxrc") || readconfig("/etc/psxrc") || readconfig; } $serverport = $opts{port} if ($opts{port}); $serveraddr = $opts{server} if ($opts{server}); $username = $opts{user} if ($opts{user}); $pass = MD5->hexhash($opts{pass}) if ($opts{pass}); #Open connection to server, bind local server $localsock = bindport || die "Unable to bind socket\n"; $myport = $localsock->sockport(); print "Connecting to Server...\n"; $serverconn = connecttoserver || die "Can't connect to server\n"; $myip = $serverconn->sockhost(); print "Bound to $myip:$myport\n"; #Login if ($opts{new}) { print "Registering New User\n"; newlogin; } else { login; } #Handle HELO and 100 Authorized #We're going to select, and answer the HELO if we get it #otherwise, just check if authorized #If we get the authorization first, the main loop will handle the HELO my $sel = IO::Select->new(); $sel->add($serverconn); $sel->add($localsock); @ready = $sel->can_read; foreach $fh (@ready) { if ($fh == $localsock) { acceptconn; } if ($fh == $serverconn) { checklogin; } } checklogin if (!$login); #If we got a HELO, run here readmd5cache; foreach $dir (split /\|/, $shareddirs) { adddir $dir; } readhotlist; #main loop while (getcommand()) {} #close up shop writehotlist; writemd5cache; $serverconn->close() if ($serverconn); $localsock->close(); exit(0); #END MAIN #SUBROUTINES #Send the login packet sub login { $blockstr = "LOGIN"; print $serverconn "STP/1.0 LOGIN\r\n"; print $serverconn "User-Agent: $useragent\r\n"; print $serverconn "Username: $username\r\n"; print $serverconn "Password: $pass\r\n"; print $serverconn "IP: $myip\r\n"; print $serverconn "Port: $myport\r\n"; print $serverconn "Speed: $myspeed\r\n"; print $serverconn "\r\n"; return 1; } #Register a user sub newlogin { $blockstr = "NEWLOGIN"; print $serverconn "STP/1.0 NEWLOGIN\r\n"; print $serverconn "User-Agent: $useragent\r\n"; print $serverconn "Username: $username\r\n"; print $serverconn "First: $first\r\n"; print $serverconn "Last: $last\r\n"; print $serverconn "Email: $email\r\n"; print $serverconn "Password: $pass\r\n"; print $serverconn "IP: $myip\r\n"; print $serverconn "Port: $myport\r\n"; print $serverconn "Speed: $myspeed\r\n"; print $serverconn "\r\n"; return 1; } #Connect to remote server, send file request, call recvfile to get it sub get { my $num = shift; my $range = shift; print "Connecting to $res[$IP][$num]:$res[$PORT][$num]...\n"; $blockstr = "Connecting to $res[$IP][$num]:$res[$PORT][$num]"; my $remoteconn = IO::Socket::INET->new (Proto => "tcp", PeerAddr => $res[$IP][$num], PeerPort => $res[$PORT][$num], Timeout => $TO) || return 0; print "Connected, requesting " . $res[$NAME][$num] . "\n"; $res[$NAME][$num] =~ /([^\/\\]+)$/; my $localfilename = $downloaddir . "/" . $1; $blockstr = "Sending GET to client"; print $remoteconn "STP/1.0 GET\r\n"; print $remoteconn "User-Agent: $useragent\r\n"; print $remoteconn "Username: $username\r\n"; print $remoteconn "Filename: " . $res[$NAME][$num] . "\r\n"; print $remoteconn "Range: bytes=" . $range . "\r\n" if ($range); print $remoteconn "\r\n"; #$remoteconn->autoflush(0); recvfile $remoteconn, $localfilename, $res[$SERVER][$num], $num; return 1; } sub getfirewall { my $num = shift; my $range = shift; $blockstr = "Sending GET to server"; print $serverconn "STP/1.0 GET\r\n"; print $serverconn "User-Agent: $useragent\r\n"; print $serverconn "Servername: $res[$SERVER][$num]\r\n"; print $serverconn "Filename: " . $res[$NAME][$num] . "\r\n"; print $serverconn "Range: bytes=" . $range . "\r\n" if ($range); print $serverconn "\r\n"; print "Requested transfer from firewalled client\n"; $reshash{$res[$NAME][$num]} = $num; return 1; } #Send a search request sub search { my $start = shift; my $qstr = shift; $blockstr = "Sending SEARCH"; print $serverconn "STP/1.0 SEARCH\r\n"; print $serverconn "Search-ID: " . $searchid++ . "\r\n"; print $serverconn "Num-Results: $maxresults\r\n"; print $serverconn "Offset: $start\r\n"; print $serverconn "Type: $type\r\n"; if ($qstr =~ /^USER/) { $qstr =~ s/^USER//; print $serverconn "Username: $qstr\r\n"; } else { print $serverconn "Query: $qstr\r\n"; } print $serverconn "\r\n"; return 1; } #Open a socket to server sub connecttoserver { $blockstr = "Connecting to $serveraddr:$serverport"; my $sock = IO::Socket::INET->new (Proto => "tcp", PeerAddr => $serveraddr, PeerPort => $serverport, Timeout => $TO); return $sock; } #Bind a socket sub bindport { my $blockstr = "binding"; my $sock = new IO::Socket::INET(Listen => 5, Timeout => $TO); return $sock; } #Make sure we logged in correctly. sub checklogin { $blockstr = "Waiting for login response"; my $response = <$serverconn>; if (defined($response) && ($response =~ /STP\/1.0 (\d+) (.*)$/)) { my $status = $1; my $message = $2; if ($status == 100) { $login = 1; $response = <$serverconn>; if ($response =~ /Firewall/) { $response = <$serverconn>; } return 1; } else { print "Server response: " . $response; $response = <$serverconn>; $serverconn->close() if ($serverconn); $serverconn = 0; $login = 0; $fallback *= 2; } } else { print "Login Error: "; print chomp $response if defined($response); print " Server disconnected on login, will retry login\n"; $login = 0; $serverconn->close() if ($serverconn); $serverconn = 0; $fallback *= 2; } return 1; } #Parse search results into 2 dimensional array sub searchresults { my @names = (); my @ip = (); my @username = (); my @port = (); my @speed = (); my @md5 = (); my @size = (); my @width = (); my @height = (); my @bitrate = (); my @duration = (); my @freq = (); my @fps = (); my $count = -1; $blockstr = "Reading SEARCH results"; $result = <$serverconn>; while ($result =~ /^(Num-Results|Search-ID|Offset|Type):/) { if ($result =~ /^Num-Results:\s+(\d+)\r/) { print "\r$1 Results for $words \n"; } $result = <$serverconn>; } while ($result =~ /^[A-Z]/) { if ($result =~ /^Filename: (.*)\r/) { $names[++$count] = $1; $speed[$count] = 0; $ip[$count] = ""; $username[$count] = ""; $port[$count] = 0; $size[$count] = 0; $md5[$count] = ""; $height[$count] = 0; $width[$count] = 0; $bitrate[$count] = 0; $duration[$count] = 0; $freq[$count] = 0; $fps[$count] = 0; } if ($result =~ /^IP: (.*)\r/) { $ip[$count] = $1; } if ($result =~ /^Username: (.*)\r/) { $username[$count] = $1; } if ($result =~ /^Port: (.*)\r/) { $port[$count] = $1; } if ($result =~ /^Speed: (.*)\r/) { $speed[$count] = $1; } if ($result =~ /^Size: (.*)\r/) { $size[$count] = $1; } if ($result =~ /^MD5: (.*)\r/) { $md5[$count] = $1; } if ($result =~ /^Height: (.*)\r/) { $height[$count] = $1; } if ($result =~ /^Width: (.*)\r/) { $width[$count] = $1; } if ($result =~ /^Bitrate: (.*)\r/) { $bitrate[$count] = $1; } if ($result =~ /^Duration: (.*)\r/) { $duration[$count] = $1; } if ($result =~ /^Freq: (.*)\r/) { $freq[$count] = $1; } if ($result =~ /^Fps: (.*)\r/) { $fps[$count] = $1; } $result = <$serverconn>; } for (my $i=0; $i < @names; $i++) { my $name = $names[$i]; $name =~ s/^.*[\/\\](.*)$/$1/; print "$i: "; print "$name "; print "$username[$i] "; print "$bwhash{$speed[$i]} " if defined($bwhash{$speed[$i]}); print "$size[$i]\n"; } $getadd = $res[$RESULTS]; $res[$RESULTS] += $count + 1; # Count starts at -1 push @{$res[$NAME]}, @names; push @{$res[$IP]}, @ip; push @{$res[$PORT]}, @port; push @{$res[$SERVER]}, @username; push @{$res[$HEIGHT]}, @height; push @{$res[$WIDTH]}, @width; push @{$res[$BITRATE]}, @bitrate; push @{$res[$DURATION]}, @duration; push @{$res[$FREQ]}, @freq; push @{$res[$FPS]}, @fps; push @{$res[$SIZE]}, @size; push @{$res[$MD5]}, @md5; prompt; return 1; } #Read in config file sub readconfig { my $filename = shift; $filename = "psx.cfg" if (!$filename); open(CFG,"$filename") || return 0; print "Reading configuration from $filename\n"; while ($line = ) { if ($line =~ /^username\s+(.*)$/i) { $username = $1; } if ($line =~ /^password\s+(.*)$/i) { $pass = MD5->hexhash($1); } if ($line =~ /^email\s+(.*)$/i) { $email = $1; } if ($line =~ /^first\s+(.*)$/i) { $first = $1; } if ($line =~ /^last\s+(.*)$/i) { $last = $1; } if ($line =~ /^server\s+(.*)$/i) { $serveraddr = $1; } if ($line =~ /^port\s+(.*)$/i) { $serverport = $1; } if ($line =~ /^downloaddir\s+(.*)$/i) { $downloaddir = $1; } if ($line =~ /^speed\s+(.*)$/i) { $myspeed = $1; } if ($line =~ /^sharedir\s+(.*)$/i) { if ($shareddirs) { $shareddirs = $shareddirs . "|" . $1; } else { $shareddirs = $1; } $shareddirs =~ s/\~/$ENV{'HOME'}/; } if ($line =~ /^hotlistfile\s+(.*)$/i) { $hotlistfile = $1; $hotlistfile =~ s/\~/$ENV{'HOME'}/; } if ($line =~ /^md5cachefile\s+(.*)$/i) { $md5cachefile = $1; $md5cachefile =~ s/\~/$ENV{'HOME'}/; } if ($line =~ /^searchtype\s+(.*)$/i) { $type = $1; } if ($line =~ /^sharetype\s+(.*)$/i) { if ($sharetype) { $sharetype = $sharetype . "|" . $1; } else { $sharetype = $1; } } } close(CFG); return 1; } #Update server stats sub getstats { my $ack = 0; $blockstr = "reading STATS"; while (($line = <$serverconn>) =~ /[A-Z]/) { if ($line =~ /^Total-Users: (\d+)/) { $totalusers = $1; } if ($line =~ /^Total-Files: (\d+)/) { $totalfiles = $1; } if ($line =~ /^Total-Size: (\d+)/) { $totalsize = $1; } if ($line =~ /^Ack-Required:.*yes/i) { $ack = 1; } } if ($ack) { print $serverconn "STP/1.0 ACK\r\n"; print $serverconn "User-Agent: $useragent\r\n"; print $serverconn "Username: $username\r\n"; print $serverconn "\r\n"; } return 1; } #Download a file sub recvfile { $blockstr = "reading download response"; my $conn = shift || return 0; my $localfile = shift; my $remuser = shift; my $resindex = shift; my $result = ""; my $agent = ""; my $remfile = ""; my $resume = 0; my $start = 0; if ($localfile) { # Direct download $result = <$conn>; } my $md5 = ""; my $length = 0; if ($localfile && ($result !~ /STP\/1.0 200 OK/)) { print "Recieve File: " . $result; return 0; } $result = <$conn>; while ($result ne "\r\n") { if ($result =~ /^Content-Length:\s+(\d+)/) { $length = $1 if ($length == 0); # file size, Content-Range overrides this } if ($result =~ /^MD5:\s+(.*)\r/) { $md5 = $1; } if ($result =~ /^Username:\s+(.*)\r/) { $remuser = $1; } if ($result =~ /^User-Agent:\s+(.*)\r/) { $agent = $1; } if ($result =~ /^Filename:\s+(.*)\r/) { my $fname = $1; $remfile = $fname; $fname =~ s/.*[\\\/](.*?)$/$1/; if (!$localfile) { $localfile = $downloaddir . "/" . $fname; } } if ($result =~ /^Content-Range:\s+bytes\=(\d+)\-(\d+)\/(\d+)/) { $resume = 1; $start = $1; $length = $3; } $result = <$conn>; } if (!defined($remuser)) { $remuser = "Unknown"; } if (!defined($resindex) && !($resindex = $reshash{$remfile})) { print "Didn't ask for $remfile\n"; $conn->close(); return 0; } else { $reshash{$remfile} = ""; # Don't accept this file anymore } print "Zero Length!\n" if (!$length); my $lfh = "FH" . $fhq++; my $oret=0; if ($resume) { $oret=open($lfh,">>$localfile"); } else { while (-e $localfile) { $localfile = $localfile.".1"; } $oret=open($lfh,">$localfile"); } if(!$oret) { print "Can't open $localfile\n"; $conn->close; return 0; } my @fq = (); $fq[$FH] = $lfh; $fq[$SOCK] = $conn; $fq[$SENT] = $start; $fq[$SIZE] = $length; $fq[$NAME] = $localfile; $fq[$IP] = $conn->peerhost; $fq[$USER] = $remuser; $fq[$MD5] = $md5; $fq[$AGENT] = $agent; $fq[$RESINDEX] = $resindex; push @recvfiles, [ @fq ]; return 1; } #Main loop, Read in user command. sub getcommand { prompt; while (!selectloop) {}; my $line = <>; if (!defined($line)) { print "\n"; return 0; } if ($line =~ /^$/) { return 1; } $line =~ s/^\s+//; if ($line =~ /^q.*?\s+(.*)$/i) { return 0; } if ($line =~ /^d.*?\s+(.*)$/i) { if ($hotlist{$1}) { remhotlist $1; delete $hotlist{$1}; } return 1; } if ($line =~ /^h/i) { print "Commands:\n"; print "search - do a search, blank cancels the last search\n"; print "results - get details on file from last search\n"; print "next - get next page of last search\n"; print "get - get from the last result set\n"; print "kill [upload|download] - kill transfer\n"; print "resume - resume download\n"; print "add - share it\n"; print "trans (up|down|number|cur|all) (number) - show transfer progress\n"; print "user - add to hotlist, then search user's files\n"; print "del - take off hotlist\n"; print "who - show hotlist status\n"; print "message - send message\n"; print "filetype - change default search file type\n"; print "logoff - log off server\n"; print "reconnect - reconnect to server\n"; print "quit - leave\n"; return 1; } if ($line =~ /^f.*?\s+(.*)$/i) { $type = $1; return 1; } if ($line =~ /^t.*?\s+(.+?)\s+(\d*)$/i) { showtran $1, $2; return 1; } if ($line =~ /^t/i) { showtran; return 1; } if ($line =~ /^rec/i) { $noreconnect = 0; reconnect; return 1; } if ($line =~ /^k.*?\s+(.+?)\s+(\d*)$/i) { killtrans $1, $2; return 1; } if (!$login && (length($line) > 0)) { return 1; # Don't do anything else since we're not connected } if ($line =~ /^s.*?\s+(.+)$/i) { $words = $1; # Save for next search $words =~ tr/A-Z/a-z/; search 0, $words; $next = $maxresults; return 1; } if ($line =~ /^s/i) { cancelsearch; return 1; } if ($line =~ /^n.*?$/i) { search $next, $words; $next += $maxresults; return 1; } if ($line =~ /^g.*?\s+(\d+)/i) { if ($res[$RESULTS] > $1) { if ($res[$PORT][$1]) { get ($1+$getadd); } else { getfirewall ($1+$getadd); } } else { print "No Search Results\n"; } return 1; } if ($line =~ /^a.*?\s+(.*)$/i) { adddir $1; return 1; } if ($line =~ /^w/i) { showuserstatus; return 1; } if ($line =~ /^u.*?\s+(.*)$/i) { if ($hotlist{$1}) { $words = "USER" . $1; search 0, $words; $next = $maxresults; } else { addhotlist $1; } return 1; } if ($line =~ /^resul.*?\s+(\d+)/i) { results ($1+$getadd); return 1; } if ($line =~ /^l/i) { $noreconnect = 1; $login = 0; $serverconn->close() if ($serverconn); $serverconn = 0; return 1; } if ($line =~ /^resum.*?\s+(\d+)/i) { resume $1; return 1; } if ($line =~ /^m.*?\s(\S+)\s(.*)/i) { sendmessage $1, $2; return 1; } print "Unrecognized Command\n"; return 1; } #select between stdin, connection to server, and bound socket sub selectloop { my $select = IO::Select->new(); $select->add(\*STDIN); if ($serverconn) { $select->add($serverconn); } $select->add($localsock); my @recv = getrecvqueue; foreach $recvsock (@recv) { $select->add($recvsock); } #If we have anything in the send or recv queues, we won't block if (sendqueue) { $blockstr = "main select loop, send queue on\n"; @ready = $select->can_read(0); } elsif (!$serverconn) { $blockstr = "main select loop, server disconnected $fallback\n"; @ready = $select->can_read($fallback); if (@ready < 1) { $fallback *= 2; reconnect; } } else { $blockstr = "main select loop, waiting for input\n"; @ready = $select->can_read(); } my $dorecv = 0; my @recvls = (); foreach $fh (@ready) { if (($serverconn) && ($fh == $serverconn)) { handleinput; } elsif ($fh == $localsock) { acceptconn; } elsif ($fh == \*STDIN) { return 1; } else { #Must have been in recvqueue push @recvls, $fh; $dorecv = 1; } } recvqueue @recvls if ($dorecv); return 0; } #Respond to request from server sub handleinput { $blockstr = "reading input from server"; my $line = <$serverconn>; if (!defined($line)) { $serverconn->close() if ($serverconn); $serverconn=0; $login=0; reconnect; return 1; } if ($line =~ /STP\/1.0 HELO/) { handlehelo $serverconn; } elsif ($line =~ /STP\/1.0 STAT/) { getstats; } elsif ($line =~ /STP\/1.0 GET/) { sendfile $serverconn; prompt; } elsif ($line =~ /STP\/1.0 USER_STATUS/) { getuserstatus; } elsif ($line =~ /STP\/1.0 300 OK/) { searchresults; } elsif ($line =~ /STP\/1.0 SERVER_MESSAGE/) { recvmessage; } elsif ($line =~ /STP\/1.0 MESSAGE_FAIL/) { failmessage; } else { print "Server input: " . $line; } return 1; } #ACK the HELO sub handlehelo { $blockstr = "reading HELO"; my $conn = shift; $line = <$conn>; #Read newline print $conn "STP/1.0 ACK\r\n"; print $conn "User-Agent: $useragent\r\n"; print $conn "Username: $username\r\n"; print $conn "\r\n"; return 1; } #Handle a connection to our bound port sub acceptconn { $blockstr = "handling incoming connection"; my $sock = $localsock->accept; return 0 if (!$sock); $sock->timeout($TO); my $line = <$sock>; if (!defined($line)) { $sock->close(); return 1; } if ($line =~ /HELO/) { handlehelo $sock; $sock->close(); } if ($line =~ /GET/) { sendfile $sock; prompt; } if ($line =~ /200\s+OK/) { #$sock->autoflush(0); recvfile $sock, 0; } return 1; } #Send file to Client #Already read in STP/1.0 GET sub sendfile { my $sock = shift; my $localfile = ""; my $line = <$sock>; my $ip = 0; my $port = 0; my $remuser = ""; my $agent = ""; my $range = ""; my $start = 0; my $end = 0; $blockstr = "reading GET request"; while ($line =~ /^[A-Z]/) { if ($line =~ /^Filename:\s+(.*)\r/) { $localfile = $1; } if ($line =~ /^IP:\s+(.*)\r/) { $ip = $1; } if ($line =~ /^Port:\s+(.*)\r/) { $port = $1; } if ($line =~ /^Username:\s+(.*)\r/) { $remuser = $1; } if ($line =~ /^User-Agent:\s+(.*)\r/) { $agent = $1; } if ($line =~ /^Range:\s+(.*)\r/) { $range = $1; if ($range =~ /bytes\=(\d+)\-(\d+)/) { $start = $1; $end = $2; } } $line = <$sock>; } if ($ip) { $blockstr = "connecting to $ip:$port to send file"; $sock = new IO::Socket::INET->new (Proto => "tcp", PeerAddr => $ip, PeerPort => $port, Timeout => $TO) || return 0; } my $lfh = "FH" . $fhq++; if (($localfile !~ /\.\./) && ($localfile =~ /$shareddirs/) && ($localfile =~ /($sharetype)$/i) && (open($lfh,"$localfile"))) { my $size = -s $localfile; my $fsize = $size; if (($range) && ($start < $size) && ($end <= $size)) { sysseek $lfh, $start, 0; $size = $end; } else { $range = ""; $start = 0; } $blockstr = "sending GET response"; print $sock "STP/1.0 200 OK\r\n"; print $sock "Filename: $localfile\r\n"; print $sock "User-Agent: $useragent\r\n"; print $sock "Content-Length: " . ($size-$start) . "\r\n"; print $sock "Content-Type: application/octet-stream\r\n"; print $sock "Content-Range: $range/$fsize\r\n" if ($range); print $sock "MD5: " . md5file($localfile) . "\r\n"; print $sock "\r\n"; print "\nSending $localfile to ". $sock->peerhost . ":$remuser\n"; my @fq = (); $fq[$FH] = $lfh; $fq[$SOCK] = $sock; $fq[$SENT] = $start; $fq[$SIZE] = $size; $fq[$NAME] = $localfile; $fq[$IP] = $sock->peerhost; $fq[$USER] = $remuser; $fq[$AGENT] = $agent; push @sendfiles, [ @fq ]; } else { print $sock "STP/1.0 404 File Not Found\r\n\r\n"; } return 1; } # Opens a directory, lists the share file, and adds them to the server sub adddir { my $dir = shift; my $mode = shift; return 0 if (!$login); if (opendir(DIR,$dir)) { $blockstr = "Adding $dir\n"; print "Sharing $dir\n" if (!defined($mode)); my @files = (); if (length($sharetype) > 0) { @files = grep /($sharetype)$/i, readdir(DIR); } close(DIR); $dir = $dir . "/" if ($dir !~ /\/$/); if ($shareddirs) { $shareddirs = $shareddirs . "|" . $dir; } else { $shareddirs = $1; } print $serverconn "STP/1.0 ADD\r\n"; foreach $file (@files) { $file = $dir . $file; my @stat = stat($file); my $md5sum = ""; if ($md5cache{$file}) { $md5sum = $md5cache{$file}; } else { $md5sum = md5file($file); $md5cache{$file} = $md5sum; } print $serverconn "Filename: $file\r\n"; print $serverconn "Size: " . $stat[7] . "\r\n"; print $serverconn "MD5: " . $md5sum . "\r\n"; $numadded++; if ($numadded >= $MAXADD) { print $serverconn "\r\n"; print $serverconn "STP/1.0 ADD\r\n"; $numadded = 0; } } print $serverconn "\r\n"; } else { print "Can't open $dir\n"; } return 1; } # Print out command line opts sub usage { print "$PROGNAME/$VERSION\n"; print "$0 Usage:\n"; print "--conf -c - Use alternate config file\n"; print " Defaults: ~/.psxrc /etc/psxrc psx.cfg\n"; print "--server -s - Use alternate SX server\n"; print "--port -p - Use alternate port on SX server\n"; print "--user -u - Use alternate username\n"; print "--pass -P - Use alternate password\n"; print "--new -n - Register new user\n"; print " All command line options will override config file\n"; exit(0); } sub sendqueue { my $retval = 0; my $sel = IO::Select->new(); $blockstr = "running through send queue"; FILE: for (my $i=0; $i < @sendfiles; $i++) { if ($sendfiles[$i][$FH]) { $sel->add($sendfiles[$i][$SOCK]); my @ready = $sel->can_write(0); if (@ready < 1) { next FILE; } $retval=1; my $buf = ""; if ((my $len = sysread $sendfiles[$i][$FH], $buf, $BSIZE) && ($sendfiles[$i][$SOCK])) { if (($sendfiles[$i][$SENT] + $len) > $sendfiles[$i][$SIZE]) { eval{$sendfiles[$i][$SOCK]->send($buf,$sendfiles[$i][$SENT] + $len - $sendfiles[$i][$SIZE])}; } else { eval{$sendfiles[$i][$SOCK]->send($buf)}; } $sendfiles[$i][$SENT] += $len; if ($pipe) { # Caught SIGPIPE print "Broken upload\n"; prompt; killtrans "upload", $i; $pipe = 0; $SIG{PIPE} = \&piperr; } } else { killtrans "upload", $i; } } } return $retval; } sub recvqueue { my %sockhash = (); my $add = 0; $blockstr = "in recv queue"; for (my $i=0; $i < @recvfiles; $i++) { if ($recvfiles[$i][$FH]) { # Add to list of files to check $sockhash{$recvfiles[$i][$SOCK]} = $i; $add = 1; } } if ($add) { # Only run if we have files in progress foreach $fh (@_) { $i = $sockhash{$fh}; my $data = ""; # Find out how much data we have to read # Just reading in a fixed size can cause blocking my $rl = pack("L",0); ioctl($recvfiles[$i][$SOCK], FIONREAD(), $rl); $rl = unpack("L",$rl); eval{$recvfiles[$i][$SOCK]->read($data,$rl);}; $rl = length($data); if ($rl < 1) { killtrans "download", $i; } else { if ($pipe) { # Caught SIGPIPE print "Broken Download\n"; prompt; $pipe = 0; $SIG{PIPE} = \&piperr; killtrans "download", $i; } else { syswrite $recvfiles[$i][$FH], $data, $rl; $recvfiles[$i][$SENT] += $rl; } } } } return $add; } sub prompt { if ($login && $serverconn) { print "${totalusers}u/${totalfiles}f "; } else { print "Not connected "; } print "Command: "; return 1; } sub showtran { my $dir = ""; my $num = -1; $up=1; $down=1; if (@_) { $dir = shift; $num = shift; if ($dir =~ /^d/i) { $up=0; } if ($dir =~ /^u/i) { $down=0; } if ($dir =~ /^n/i) { print "Downloads: " . scalar @recvfiles . " Uploads: " . scalar @sendfiles . "\n"; return 1; } if ($dir !~ /^[audn]/i) { print "Bad Option\n"; return 0; } } if (@recvfiles && $down) { print "Downloads:\n" if ($num < 0); for (my $i=0; $i < @recvfiles; $i++) { if (($i==$num) || ($num < 0)) { print "$i: "; if ($recvfiles[$i][$FH]) { print "In Progress: "; } else { print "Done: "; } print "$recvfiles[$i][$NAME] $recvfiles[$i][$SENT]/$recvfiles[$i][$SIZE] $recvfiles[$i][$IP]:$recvfiles[$i][$USER]"; print " $recvfiles[$i][$AGENT]" if (length($recvfiles[$i][$AGENT]) && ($num > -1)); print "\n"; } } } if (@sendfiles && $up) { print "Uploads:\n" if ($num < 0); for (my $i=0; $i < @sendfiles; $i++) { if (($i==$num) || ($num < 0)) { print "$i: "; if ($sendfiles[$i][$FH]) { print "In Progress: "; } else { print "Done: "; } print "$sendfiles[$i][$NAME] $sendfiles[$i][$SENT]/$sendfiles[$i][$SIZE] $sendfiles[$i][$IP]:$sendfiles[$i][$USER]"; print " $sendfiles[$i][$AGENT]" if (length($sendfiles[$i][$AGENT]) && ($num > -1)); print "\n"; } } } return 1; } sub getuserstatus { $blockstr = "reading user status"; while (($line = <$serverconn>) =~ /[A-Z]/) { if ($line =~ /^Username: (.*)\r/) { $statusname = $1; } if ($line =~ /^Status: (.*)\r/) { $hotlist{$statusname} = $1; } } return 1; } sub addhotlist { my @adduser = @_; $blockstr = "sending ADDUSER"; print $serverconn "STP/1.0 ADDUSER\r\n"; foreach $adduser (@adduser) { print $serverconn "Username: $adduser\r\n"; } print $serverconn "\r\n"; return 1; } sub remhotlist { my @remuser = @_; $blockstr = "sending DELUSER"; print $serverconn "STP/1.0 DELUSER\r\n"; foreach $remuser (@remuser) { print $serverconn "Username: $remuser\r\n"; } print $serverconn "\r\n"; return 1; } sub showuserstatus { print "Username: " . $username . "\n"; foreach $key (keys %hotlist) { print "$key $hotlist{$key}\n"; } return 1; } sub readhotlist { if (($hotlistfile) && open(HLF,$hotlistfile)) { while (my $line=) { chomp $line; addhotlist $line if ($login); $hotlist{$line} = "unknown"; } close(HLF); } return 1; } sub writehotlist { if (($hotlistfile) && open(HLF,">$hotlistfile")) { foreach $key (keys %hotlist) { print HLF "$key\n"; } close(HLF); } return 1; } sub killtrans { my $d = shift; my $num = shift; if ($d =~ /^u/i) { if ($num < @sendfiles) { close($sendfiles[$num][$FH]) if ($sendfiles[$num][$FH]); $sendfiles[$num][$SOCK]->close() if ($sendfiles[$num][$SOCK]); $sendfiles[$num][$FH] = 0; } } if ($d =~ /^d/i) { if ($num < @recvfiles) { close($recvfiles[$num][$FH]) if ($recvfiles[$num][$FH]); $recvfiles[$num][$SOCK]->close() if ($recvfiles[$num][$SOCK]); $recvfiles[$num][$FH] = 0; if ((length($recvfiles[$num][$MD5]) > 0) && ((my $md5 = md5file($recvfiles[$num][$NAME])) ne $recvfiles[$num][$MD5])) { print "$recvfiles[$num][$NAME] md5 mismatch\n"; print "$md5:$recvfiles[$num][$MD5]\n"; prompt; } } } return 1; } sub piperr { $pipe = 1; return 1; } sub md5file { my $file = shift; if (open(CFH,$file)) { my $buf = ""; sysread CFH, $buf, $MD5SIZE; close(CFH); return MD5->hexhash($buf); } else { return 0; } } sub reconnect { return 1 if ($noreconnect); if (!$serverconn) { $blockstr = "reconnect"; $serverconn = connecttoserver || return 0; login; checklogin; return 0 if (!$login); $numadded = 0; my @dirs = split /\|/, $shareddirs; foreach $dir (@dirs) { adddir $dir, "quiet"; } $fallback = 2; return 1; } return 1; } sub getrecvqueue { my @ret = (); for (my $i=0; $i < @recvfiles; $i++) { if ($recvfiles[$i][$FH]) { push @ret, $recvfiles[$i][$SOCK]; } } return @ret; } sub results { my $num = shift; if ($num > $res[$RESULTS]) { print "Out of range $num $res[$RESULTS]\n"; return 0; } print "$num: "; print $res[$NAME][$num] . " " if defined($res[$NAME][$num]); print "size: " . $res[$SIZE][$num] . " " if defined($res[$SIZE][$num]); print "ip: " . $res[$IP][$num] . " " if defined($res[$IP][$num]); print "port: " . $res[$PORT][$num] . " " if defined($res[$PORT][$num]); print "user: " . $res[$SERVER][$num] . " " if defined($res[$SERVER][$num]); print "height: " . $res[$HEIGHT][$num] . " " if defined($res[$HEIGHT][$num]); print "width: " . $res[$WIDTH][$num] . " " if defined($res[$WIDTH][$num]); print "bitrate: " . $res[$BITRATE][$num] . " " if defined($res[$BITRATE][$num]); print "frequency: " . $res[$FREQ][$num] . " " if defined($res[$FREQ][$num]); print "duration: " . $res[$DURATION][$num] . " " if defined($res[$DURATION][$num]); print "frames per second: " . $res[$FPS][$num] . " " if defined($res[$FPS][$num]); print "md5sum: " . $res[$MD5][$num] . " " if defined($res[$MD5][$num]); print "\n"; return 1; } sub sigint { my $exitat = 4; print "Blocked in " . $blockstr . "\n"; $SIG{INT} = \&sigint; $sigints++; exit(0) if ($sigints > $exitat); print $exitat - $sigints + 1 . " more until exit\n"; return 1; } sub cancelsearch { $blockstr = "CANCEL_SEARCH"; print $serverconn "STP/1.0 CANCEL_SEARCH\r\n"; print $serverconn "\r\n"; return 1; } sub resume { my $num = shift; if (@recvfiles < 1) { print "No files downloaded yet\n"; return 0; } if (($num+1) > @recvfiles) { print "$num out of range\n"; return 0; } if ($recvfiles[$num][$SENT] == $recvfiles[$num][$SIZE]) { print "File is already done\n"; return 0; } my $range = $recvfiles[$num][$SENT] . "-" . $recvfiles[$num][$SIZE]; my $ri = $recvfiles[$num][$RESINDEX]; if ($res[$PORT][$ri]) { get $ri, $range; } else { getfirewall $ri, $range; } return 1; } sub writemd5cache { return 0 if (!$md5cachefile); return 0 if !open(MD5CACHE,">$md5cachefile"); foreach $key (keys %md5cache) { print MD5CACHE $md5cache{$key} . " " . $key . "\n"; } close(MD5CACHE); return 1; } sub readmd5cache { return 0 if (!$md5cachefile); return 0 if !open(MD5CACHE,$md5cachefile); while (my $line = ) { chomp $line; if ($line =~ /(.*?)\s(.*)/) { $md5cache{$2} = $1; } } close(MD5CACHE); return 1; } sub sendmessage { my $recip = shift; my $message = shift; print $serverconn "STP/1.0 MESSAGE\r\n"; print $serverconn "Username: $recip\r\n"; print $serverconn "Transfer-Encoding: base64\r\n"; print $serverconn "Sequence: " . $msgseq++ . "\r\n"; print $serverconn "Data: " . encode_base64($message,"") . "\r\n"; print $serverconn "\r\n"; return 1; } sub recvmessage { while ((my $line = <$serverconn>) =~ /^A-Z/) { if ($line =~ /^Transfer-Encoding: (.*)\r/) { } if ($line =~ /^Data: (.*)\r/) { print "\rMessage............\n"; print decode_base64($1) . "\n"; } } return 1; } sub failmessage { my $un = ""; my $err = ""; my $line = <$serverconn>; while ($line =~ /^[A-Z]/) { if ($line =~ /^Username: (.*)\r/) { $un = $1; } if ($line =~ /^Sequence: (.*)\r/) { } if ($line =~ /^Type: (.*)\r/) { $err = $1; } $line = <$serverconn>; } print "\rSend message to $un error: $err\n"; prompt; return 1; }