# ftp.pl, by Roger Espel Llima <odar@pobox.com>
#
# this is an ftp client integrated into sirc.  once loaded, it provides
# the following commands:
#
# /ftp open ftp.site 	ftp.site can be a hostname or a full ftp URL
#			in the form 
#			ftp://user:password@hostname:port/path/to/file 
#			or just user@hostname.  if no user is specified,
#			sirc attempts an anonymous login.  if the file
#			is not specified, it acts like an interactive
#			ftp client
#
# /ftp list		lists all currently open ftp connections.  the
#			"list" can be omitted
#
# /ftp quit		quits an established ftp session.  can be shortened
# /ftp close		to /ftp q.
#
# /ftp cwd		does a cwd (change working directory).  can be 
#			shortened to "cd"
#
# /ftp mkdir		changes the current directory.  "md" also works.
#
# /ftp rmdir		erases a directory.  "rd" also works.
#
# /ftp delete		deletes a file.  "rm", "del" and "dele" also work.
#
# /ftp break		interrupts a running transfer.  "abor" also works
#
# /ftp rename		renames a file.  "ren" and "mv" also work.
#
# /ftp dir		lists files.  "ls" also works.
#
# /ftp binary		sets binary mode.  "bin" also works.
#
# /ftp ascii		sets ASCII mode.
#
# /ftp get file [name]  gets a file; if a name is specified, it is used
#			as the local name to save the file as.
#
# most of these commands have shortcut aliases: 
#	/fopen, /fdir, /fls, /fcd, /fget, /fmkdir, /fmd, /frmdir, /frd
#	/fquit, /fq


# global variables:
#
# $ftpopen       : have a control connection
# $ftpactive     : have a data connection
# $ftpfopen	 : have a file open
# $ftpwait	 : waiting for a data connection
# $ftpuser       : username
# $ftppass       : password
# $ftpport       : ftp port
# $ftpfile       : filename to download
# $ftpxfermsg    : what we're currently xferring
# $ftpxferbytes  : bytes so far
# $ftpfh	 : control connection fh
# $ftpdfh	 : data connection fh
# $ftpffh	 : file fh
# $ftpstart	 : time of the connection
# $ftprename	 : new name in a rename operation
# $ftpquit	 : quit after one get

$add_ons.="+ftp.pl" if $add_ons !~ /ftp/;

sub ftpcleanup {
  &remsel($ftpfh);
  close $ftpfh;
  if ($ftpwait || $ftpactive) {
    &remsel($ftpdfh);
    close $ftpdfh;
  }
  close $ftpffh if $ftpfopen;
  $ftpopen = $ftpactive = $ftpwait = $ftpfopen = $ftpquit = 0;
  $ftpfile = "";
}

$ftpopen = $ftpactive = $ftpwait = $ftpfopen = 0;

sub ftpmakeport {
  local($c1, $c2, $c3, $c4)=unpack("C4", $bindaddr);
  return "$c1,$c2,$c3,$c4," . int($ftpport/256) . "," . ($ftpport%256);
}

sub cmd_ftp {
  &getarg;
  $ftpquit = 0 unless $newarg =~ /^zoinx$/i;
  if ($newarg =~ /^o(pen)?$/i) {
    &tell("*\cbE\cb* You already have an open ftp session, use /ftp close").
      return if $ftpopen;
    &getarg;
    $newarg =~ s/^ftp\:\/\///;
    if ($newarg =~ /^([^\@]+\@)?([^\:\/]+)(:\d+)?(\/.*)?$/) {
      ($ftpuser, $ftphost, $ftpport, $ftpfile, $ftppass)=($1, $2, $3, $4, '');
      $ftpuser =~ s/\@$//;
      $ftppass = $1 if $ftpuser =~ s/:(.*)$//;
      $ftpport =~ s/^://;
      $ftpport = 21 unless $ftpport;
      $ftpuser = "anonymous" if $ftpuser eq '';
      $ftppass = "$username\@" 
	if $ftppass eq '' && ($ftpuser eq 'anonymous' || $ftpuser eq 'ftp');
      &connect($ftpfh, $ftphost, $ftpport) || return;
      $ftpopen = 1;
      &addsel($ftpfh, "ftp", 1);
    } else {
      &tell("*\cbE\cb* Use: /ftp open [user[:passwd]\@]ftp.host[:port]");
    }
  } elsif (!$ftpopen) {
    &tell("*\cbE\cb* No open ftp session");
  } elsif ($newarg =~ /^(list)?$/i) {
    if ($ftpuser =~ /^(anonymous|ftp)$/i) {
      &tell("*\cbF\cb* anonymous FTP connection established with $ftphost");
    } else {
      &tell("*\cbF\cb* FTP connection established with $ftphost, userid $ftpuser");
    }
    if ($ftpactive) {
      &tell("*\cbF\cb* Currently $ftpxfermsg, $ftpxferbytes bytes so far");
    } elsif ($ftpwait) {
      &tell("*\cbF\cb* Waiting for data connection...");
    }
  } elsif ($newarg =~ /^(q|quit|close)$/i) {
    &ftpcleanup;
    &tell("*\cbF\cb* FTP session closed");
  } elsif ($newarg =~ /^(cd|cwd)$/i) {
    &getarg;
    &tell("*\cbE\cb* I need a directory name"), return if $newarg eq '';
    print $ftpfh "CWD $newarg\n";
  } elsif ($newarg =~ /^(md|mkdir)$/i) {
    &getarg;
    &tell("*\cbE\cb* I need a directory name"), return if $newarg eq '';
    print $ftpfh "MKD $newarg\n";
  } elsif ($newarg =~ /^(rd|rmdir)$/i) {
    &getarg;
    &tell("*\cbE\cb* I need a directory name"), return if $newarg eq '';
    print $ftpfh "RMD $newarg\n";
  } elsif ($newarg =~ /^(del|dele|delete|rm)$/i) {
    &getarg;
    &tell("*\cbE\cb* I need a file name"), return if $newarg eq '';
    print $ftpfh "DELE $newarg\n";
  } elsif ($newarg =~ /^(break|abor|abort)$/i) {
    &tell("*\cbE\cb* No established connection"), return
      unless $ftpwait || $ftpactive;
    &tell("*\cbF\cb* Aborting connection");
    print $ftpfh "ABOR\n";
    if ($ftpwait || $ftpactive) {
      &remsel($ftpdfh);
      close $ftpdfh;
      $ftpwait = $ftpactive = 0;
    }
    if ($ftpfopen) {
      close $ftpffh;
      $ftpfopen = 0;
    }
  } elsif ($newarg =~ /^(ren|rename|mv)$/i) {
    &getarg;
    &tell("*\cbE\cb* I need two file names"), return 
      if $newarg eq '' || $args eq '';
    print $ftpfh "RNFR $newarg\n";
    &getarg;
    $ftprename=$newarg;
  } elsif ($newarg =~ /^(ls|dir)$/i) {
    &tell("*\cbE\cb* There is an active transfer, break it with /ftp break"), 
      return if $ftpactive || $ftpwait;
    $ftpport = &listen($ftpdfh) || return;
    print $ftpfh "PORT ".&ftpmakeport()."\n";
    if ($args eq '') {
      print $ftpfh "LIST\n";
    } else {
      print $ftpfh "LIST $args\n";
    }
    $ftpwait = 1;
    &addsel($ftpdfh, "ftplist1", 0);
  } elsif ($newarg =~ /^bin(ary)?/i) {
    print $ftpfh "TYPE I\n";
  } elsif ($newarg =~ /^ascii/i) {
    print $ftpfh "TYPE A\n";
  } elsif ($newarg =~ /^(get|zoinx)$/i) {
    &getarg;
    &tell("*\cbE\cb* I need a filename"), return if $newarg eq '';
    &tell("*\cbE\cb* There is an active transfer, break it with /ftp break"), 
      return if $ftpactive || $ftpwait;
    local($rf, $lf)=($newarg);
    &getarg;
    $ftpxfermsg = "getting $rf";
    $lf = ($newarg ne '' ? $newarg : $rf);
    $lf =~ s/^.*\///;
    $ftpffh=&newfh;
    &tell("*\cbE\cb* Can't write to ${lf}!"), return 
      unless open($ftpffh, "> $lf");
    select($ftpffh); $|=1; select(STDOUT);
    $ftpfopen=1;
    $ftpport = &listen($ftpdfh);
    if ($ftpport == 0) {
      close $ftpffh;
      $ftpfopen = 0;
      return;
    }
    print $ftpfh "PORT ".&ftpmakeport()."\n";
    print $ftpfh "RETR $rf\n";
    $ftpwait = 1;
    &addsel($ftpdfh, "ftpget1", 0);
    $ftpstart = time;
  } elsif ($newarg =~ /^(put|send)$/i) {
    &tell("*\cbE\cb* FTP PUT is not implemented");
  } else {
    &tell("*\cbE\cb* FTP command not understood");
  }
}
&addcmd("ftp");

sub sel_ftplist1 {
  local($nfh);
  &remsel($ftpdfh);
  if (&accept($nfh, $ftpdfh)) {
    $ftpdfh = $nfh;
    &addsel($ftpdfh, "ftplist2", 1);
    $ftpwait = 0;
    $ftpactive = 1;
    $ftpxfermsg = "getting a file list";
    $ftpxferbytes = 0;
  } else {
    &tell("*\cbE\cb* FTP protocol error");
    $ftpwait = $ftpactive = 0;
  }
}

sub sel_ftpget1 {
  local($nfh);
  &remsel($ftpdfh);
  if (&accept($nfh, $ftpdfh)) {
    $ftpdfh = $nfh;
    &addsel($ftpdfh, "ftpget2", 0);
    $ftpwait = 0;
    $ftpactive = 1;
    $ftpxferbytes = 0;
  } else {
    &tell("*\cbE\cb* FTP protocol error");
    $ftpwait = $ftpactive = $ftpfopen = 0;
    close $ftpffh;
  }
}

sub sel_ftplist2 {
  local($l)=@_;
  if ($l eq '') {
    $ftpwait = $ftpactive = 0;
    &tell("*\cbF\cb* End of list");
  } else {
    $ftpxferbytes += length($l);
    chop($l);
    &tell("*\cbF\cb* $l");
  }
}

sub sel_ftpget2 {
  local($a, $buf)=(0, '');
  $a = sysread($ftpdfh, $buf, 4096);
  if ($a) {
    $ftpxferbytes += $a;
    print $ftpffh $buf;
  } else {
    close $ftpdfh;
    close $ftpffh;
    &remsel($ftpdfh);
    $ftpwait = $ftpactive = $ftpfopen = 0;
    if ($ftpquit) {
      &tell("*\cbF\cb* FTP session terminated; $ftpxferbytes transferred in " .
	    (time - $ftpstart) . " seconds");
      &ftpcleanup;
    } else {
      &tell("*\cbF\cb* FTP transfer terminated; $ftpxferbytes transferred in " .
	    (time - $ftpstart) . " seconds");
    }
  }
}

sub sel_ftp {
  local($l)=@_;
  if ($l eq '') {
    &ftpcleanup;
    &tell("*\cbE\cb* FTP connection lost");
    return;
  }
  chop($l);
  &tell("*\cbF\cb* $l") unless 
    $l =~ /^331 /  || ($ftpfile ne '' && ($l =~ /^\d\d\d-/ || $l =~ /^200/));
  if ($ftphookcwd) {
    return if $l =~ /^200/ || $l =~ /^250-/;
    $ftphookcwd = 0;
    if ($l =~ /^250/) {
      &docommand("ftp zoinx $ftpfile");
      $ftpfile = "";
      $ftpquit = 1;
      return;
    }
  }
  if ($l =~ /^\d\d\d-/ || $l =~ /^226/) {
    # nothing
  } elsif ($l =~ /^(220|530)/) {
    print $ftpfh "USER $ftpuser\n";
  } elsif ($l =~ /^230/) {
    print $ftpfh "TYPE I\n";
    if ($ftpfile) {
      local($path);
      $path=$1 if $ftpfile =~ s/^(.*)\///;
      if ($ftpfile ne '') {
	print $ftpfh "CWD $path\n";
	$ftphookcwd = 1;
      }
    }
  } elsif ($l =~ /^331/) {
    if ($ftppass ne '') {
      print $ftpfh "PASS $ftppass\n";
    } else {
      &getuserpass("*\cbF\cb* Password for $ftpuser? ", "Passwd: ");
      print $ftpfh "PASS $_\n";
    }
  } elsif ($l =~ /^350/) {
    print $ftpfh "RNTO $ftprename\n";
  } elsif ($l =~ /^550/) {
    if ($ftpwait) {
      $ftpwait = 0;
      &remsel($ftpdfh);
      close $ftpdfh;
    }
    if ($ftpfopen) {
      $ftpfopen = 0;
      close $ftpffh;
    }
    $ftpfile = $ftphookcwd = 0;
  }
}

&docommand("^alias fopen ftp open");
&docommand("^alias fdir ftp dir");
&docommand("^alias fls ftp dir");
&docommand("^alias fcd ftp cd");
&docommand("^alias fget ftp get");
&docommand("^alias fmkdir ftp mkdir");
&docommand("^alias fmd ftp mkdir");
&docommand("^alias frmdir ftp rmdir");
&docommand("^alias frd ftp rmdir");
&docommand("^alias fquit ftp quit");
&docommand("^alias fq ftp quit");

