#!/usr/bin/perl
#
#  Copyright (c) 1998 Tomokazu ISHII <t-ishii@tryplanet.com>
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions
#  are met:
#  1. Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#  2. Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
#  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
#  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
#  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
#  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
#  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#  SUCH DAMAGE.
#
#================================================
# ftpfind v0.996 1998/12/08
#     find directory&file on a ftp server
#================================================


#--------------------------------
# equ definition
$verbose        = 1;	# 0:off, 1:normal, 2:debug
$SOCK_STREAM    = 1;	# Solaris:2, other:1
$copy_timestamp = 1;	# 0:no, 1:copy
$opt_cpmod      = 1;    # 0:no, 1:yes

$sock_addr = 'S n a4 x8';
$cport     = 21;	# control port
$dport     = 0;		# data port (0:let system pick one)
$AF_INET   = 2;
$use_pasv  = 0;		# 0:no, 1:yes

#--------------------------------
# options
$opt_url      = '';
$opt_server   = '';
$opt_dir      = '';
$opt_localdir = '';
$opt_login    = '';
$opt_password = '';
$opt_proxy    = '';

$opt_ls       = 0;
@opt_regexp   = ();
$opt_print    = 0;
@opt_types    = ();   # d:directory, f:file, l:symbolic link

$opt_movekind = 0;    # 0:none, 1:delete, 2:get, 3:put, 4:chmod
$opt_new      = 0;
$opt_resume   = 0;
$opt_chmod    = '';

#--------------------------------
# main
$oldfh = select(STDERR);
$| = 1;
select(STDOUT);
$| = 1;
select($oldfh);

$ENV{'TZ'} = 'GMT';

&set_opt();
&print_opt();

# connect
&copen();
if ($opt_proxy eq '') {
    &cconnect($opt_server, $cport);
    &login($opt_login, $opt_password);
}
else {
    &cconnect($opt_proxy, $cport);
    &login($opt_login.'@'.$opt_server, $opt_password);
}
&cmdTYPE("i");

if ($opt_dir eq '') {
    $opt_dir = &cmdPWD();
    if ($opt_dir eq '') {
	$opt_dir = '/';
    }
}
&do_all();

# end process
&cmdQUIT();
close(SCTRL);
&print_msg(2,"process end.\n");
exit 0;

#--------------------------------
# do_all()
sub do_all {
    local(@files,$file,@words);

    if (($opt_movekind == 0) || ($opt_movekind == 4)) {	# none or chmod
	if (&cmdCWD($opt_dir) == 0) {
	    if ($opt_dir !~ /\/$/) {
		$opt_dir .= '/';
	    }
	    &do_print_alldirs($opt_dir);
	}
	else {
	    @files = &cmdLIST($opt_dir);
	    $file  = $files[0];
	    $file  =~ s/[\r\n]//g;
	    @words = &lsparse($file);
	    if (($#files != 0) || ($#words == 0)) {
		&print_msg(1,"%s: No such file or directory\n",$opt_dir);
	    }
	    else {
		&do_print_onefile($opt_dir,$file);
	    }
	}
    }
    elsif ($opt_movekind == 1) {	# delete
	if (&cmdCWD($opt_dir) == 0) {
	    if ($opt_dir !~ /\/$/) {
		$opt_dir .= '/';
	    }
	    &do_delete_alldirs($opt_dir);
	}
	else {
	    @files = &cmdLIST($opt_dir);
	    $file  = $files[0];
	    $file  =~ s/[\r\n]//g;
	    @words = &lsparse($file);
	    if (($#files != 0) || ($#words == 0)) {
		&print_msg(1,"%s: No such file or directory\n",$opt_dir);
	    }
	    else {
		&do_delete_onefile($opt_dir,$file);
	    }
	}
    }
    elsif ($opt_movekind == 2) {	# get
	if ($opt_localdir !~ /\/$/) {
	    $opt_localdir .= '/';
	}
	if (&cmdCWD($opt_dir) == 0) {
	    if ($opt_dir !~ /\/$/) {
		$opt_dir .= '/';
	    }
	    &do_get_alldirs($opt_dir,$opt_localdir);
	}
	else {
	    if ($opt_dir =~ /\/$/) {
		&print_msg(1,"%s: No such directory\n",$opt_dir);
		return;
	    }
	    @files = &cmdLIST($opt_dir);
	    $file  = $files[0];
	    $file  =~ s/[\r\n]//g;
	    @words = &lsparse($file);
	    if (($#files != 0) || ($#words == 0)) {
		&print_msg(1,"%s: No such file or directory\n",$opt_dir);
	    }
	    else {
		local(@tmp) = split(/\//,$words[0]);
		&do_get_onefile($opt_dir,$file,
			    $opt_localdir . $tmp[$#tmp],0);
	    }
	}
    }
    elsif ($opt_movekind == 3) {	# put
	if (-e $opt_localdir) {
	    if ($opt_dir !~ /\/$/) {
		$opt_dir .= '/';
	    }
	    if (-d $opt_localdir) {
		if ($opt_localdir !~ /\/$/) {
		    $opt_localdir .= '/';
		}
		&do_put_alldirs($opt_dir,$opt_localdir);
	    }
	    elsif ($opt_localdir !~ /\/$/) {
		@words = split(/\//,$opt_localdir);
		&do_put_onefile($opt_dir . $words[$#words],$opt_localdir);
	    }
	}
    }
}

#--------------------------------
# usage()
sub usage {
    print "usage: $0 URL [-proxy proxy_server] \\\n";
    print "    [-login login_name] [-password password] \\\n";
    print "    [-regexp pattern] [-type d|f|l] [-ls] [-print] \\\n";
    print "    [-delete|-get [directory] [-new] [-resume] \\\n";
    print "            |-put [directory] [-new] \\\n";
    print "            |-chmod 0???]\n";
}

#--------------------------------
# print_msg()
sub print_msg {
    local($level) = shift(@_);
    if ($level <= $verbose) {
	printf(STDERR @_);
    }
}

#--------------------------------
# lsparse(file_status)
sub lsparse {
    local($file_status) = $_[0];
    if ($file_status =~ /^(\S+)$/) {
	return ($1,$1);
    }
    elsif ($file_status =~ /^([\-dFl][\-rwxSsTt]{9})\s+(.*)\s+(.*) -> (.*)$/) {
	return ($3,$1,$2,$4);
    }
    elsif ($file_status =~ /^([\-dFl][\-rwxSsTt]{9})\s+(.*)\s+(.*)$/) {
	return ($3,$1,$2);
    }
    else {
	if (($file_status =~ /^\S{10}(\s+\S+){8}/)
	    || ($file_status =~ /^\S{10}(\s+\S+){10}/)) {
	    &print_msg(2, "%s: unsupported file mode\n", $file_status);
	}
	else {
	    &print_msg(2, "%s: parse error\n", $file_status);
	}
	return;
    }
}


#++++++++++++++++++++++++++++++++++++++++++++++++
#  -ls, -print option only ...plus chmod
#++++++++++++++++++++++++++++++++++++++++++++++++

#--------------------------------
# do_print_alldirs(path)
sub do_print_alldirs {
    local($path_remote) = $_[0];
    local(@files,$file,@words);
    local($i,$path_tmp);

    if ($path_remote !~ /\/$/) {
	$path_remote .= '/';
    }
    @files = &cmdLIST('.');

    for ($i=0 ; $i<=$#files ; $i++) {
	$file  = $files[$i];
	$file  =~ s/[\r\n]//g;
	@words = &lsparse($file);
	if (($#words == 0) || ($words[0] eq '.') || ($words[0] eq '..')) {
	    next;
	}
	&do_print_onefile($path_remote . $words[0],$file);
	if ($#words == 1) {
	    if (&cmdCWD($path_remote . $words[0]) == 0) {
		&do_print_alldirs($path_remote . $words[0]);
	    }
	}
	elsif ($words[1] =~ /^d/) {
	    if (&cmdCWD($path_remote . $words[0]) == 0) {
		&do_print_alldirs($path_remote . $words[0]);
	    }
	}
	elsif (($words[1] =~ /^l/) && ($#words == 3)) {
	    if (&check_scope($opt_dir,$path_remote,$words[3]) != 0) {
		if (&cmdCWD($path_remote . $words[0]) == 0) {
		    &do_print_alldirs($path_remote . $words[0]);
		}
	    }
	}
    }
}

#--------------------------------
# do_print_onefile(path_remote,file_status)
sub do_print_onefile {
    local($path) = $_[0];
    local($file) = $_[1];
    if (&ifselect($file) != 0) {
	&do_ls($path,$file);
	&do_print($path);
	if ($opt_movekind == 4) { # chmod
	    &cmdCHMOD($opt_chmod,$path);
	}
    }
}

#--------------------------------
# ifselect(file_status)
sub ifselect {
    local($i);
    local(@words) = &lsparse($_[0]);
    local($flag)  = 1;
    for ($i = 0; $i <= $#opt_regexp; $i++) {
	if ($words[0] !~ $opt_regexp[$i]) {
	    $flag = 0;
	}
    }
    for ($i = 0; $i <= $#opt_types; $i++) {
	if ($words[1] !~ /^$opt_types[$i]/) {
	    $flag = 0;
	}
    }
    return $flag;
}

#--------------------------------
# do_ls(path_remote,file_status)
sub do_ls {
    if ($opt_ls != 0) {
	local(@words) = &lsparse($_[1]);
	if ($#words <= 1) {
	    &do_print($_[0]);
	    return;
	}
	printf("%s ", $words[1]);
	local(@w2) = split(/ +/,$words[2]);
	printf("%4s ", $w2[0]);
	if ($#w2 == 5) {
	    printf("%-17s ", $w2[1]);
	    printf("%8s ", $w2[2]);
	    printf("%4s %4s %6s ", $w2[3], $w2[4], $w2[5]);
	}
	elsif ($#w2 == 6) {
	    printf("%-8s %-8s ", $w2[1], $w2[2]);
	    printf("%8s ", $w2[3]);
	    printf("%4s %4s %6s ", $w2[4], $w2[5], $w2[6]);
	}
	else {
	    &print_msg(2, "%s: parse error\n", $words[2]);
	}
	if ($#words == 3) {
	    printf("%s -> %s",$_[0],$words[3]);
	}
	else {
	    printf("%s",$_[0]);
	}
	printf("\n");
	return;
    }
}

#--------------------------------
# do_print(path)
sub do_print {
    if ($opt_print != 0) {
	printf("$_[0]\n");
    }
}


#++++++++++++++++++++++++++++++++++++++++++++++++
#  -delete option
#++++++++++++++++++++++++++++++++++++++++++++++++

#--------------------------------
# do_delete_alldirs(directory)
sub do_delete_alldirs {
    local($path) = $_[0];
    local(@files,$file,@words);
    local($i,$path_tmp);

    if ($path !~ /\/$/) {
	$path .= '/';
    }
    @files = &cmdLIST('.');

    for ($i=0 ; $i<=$#files ; $i++) {
	$file  = $files[$i];
	$file  =~ s/[\r\n]//g;
	@words = &lsparse($file);
	if (($#words == 0) || ($words[0] eq '.') || ($words[0] eq '..')) {
	    next;
	}
	if ($#words == 1) {
	    if (&cmdCWD($path . $words[0]) == 0) {
		&do_delete_alldirs($path . $words[0]);
		&cmdCWD('..');
		&do_delete_onefile($path . $words[0], 'd');
	    }
	    else {
		&do_delete_onefile($path . $words[0], 'f');
	    }
	}
	else {
	    if ($words[1] =~ /^d/) {
		if (&cmdCWD($path . $words[0]) == 0) {
		    &do_delete_alldirs($path . $words[0]);
		    &cmdCWD('..');
		}
	    }
	    if ($words[1] =~ /^[\-Fdl]/) {
		&do_delete_onefile($path . $words[0], $file);
	    }
	}
    }
}

#--------------------------------
# do_delete_onefile(path_remote,file_status)
sub do_delete_onefile {
    local($path) = $_[0];
    local($file) = $_[1];
    if (&ifselect($file) != 0) {
	&do_ls($path,$file);
	&do_print($path,$file);
	&do_delete($path,$file);
    }
}

#--------------------------------
# do_delete(path_remote,file_status)
sub do_delete {
    local($path) = $_[0];
    local($file) = $_[1];
    if ($file =~ /^d/) {
	&cmdRMD($path);
    }
    else {
	&cmdDELE($path);
    }
}


#++++++++++++++++++++++++++++++++++++++++++++++++
#  -get option
#++++++++++++++++++++++++++++++++++++++++++++++++

#--------------------------------
# do_get_alldirs(path_remote,path_local)
sub do_get_alldirs {
    local($path_remote) = $_[0];
    local($path_local)  = $_[1];
    local(@files,$file,@words);
    local($i,$path_tmp);

    if ($path_remote !~ /\/$/) {
	$path_remote .= '/';
    }
    @files = &cmdLIST('.');
    if ($path_local !~ /\/$/) {
	$path_local .= '/';
    }

    for ($i=0 ; $i<=$#files ; $i++) {
	$file  = $files[$i];
	$file  =~ s/[\r\n]//g;
	@words = &lsparse($file);
	if (($#words == 0) || ($words[0] eq '.') || ($words[0] eq '..')) {
	    next;
	}
	if ($#words == 1) {
	    if (&cmdCWD($path_remote . $words[0]) == 0) {
		&do_get_onefile($path_remote . $words[0],$file,
				$path_local . $words[0],1);
		&do_get_alldirs($path_remote . $words[0],
				$path_local . $words[0]);
	    }
	    else {
		&do_get_onefile($path_remote . $words[0],$file,
				$path_local . $words[0],0);
	    }
	}
	elsif ($words[1] =~ /^[\-F]/) {
	    &do_get_onefile($path_remote . $words[0],$file,
			    $path_local . $words[0],0);
	}
	elsif ($words[1] =~ /^d/) {
	    if (&cmdCWD($path_remote . $words[0]) == 0) {
		&do_get_onefile($path_remote . $words[0],$file,
				$path_local . $words[0],1);
		&do_get_alldirs($path_remote . $words[0],
				$path_local . $words[0]);
	    }
	}
	elsif (($words[1] =~ /^l/) && ($#words == 3)) {
	    if (&check_scope($opt_dir,$path_remote,$words[3]) == 0) {
		&do_get_onefile($path_remote . $words[0],$file,
				$path_local . $words[0],2);
	    }
	    else {
		if (&cmdCWD($path_remote . $words[0]) == 0) {
		    &do_get_onefile($path_remote . $words[0],$file,
				    $path_local . $words[0],1);
		    &do_get_alldirs($path_remote . $words[0],
				    $path_local . $words[0]);
		}
		else {
		    &do_get_onefile($path_remote . $words[0],$file,
				    $path_local . $words[0],0);
		}
	    }
	}
    }
}

#--------------------------------
# do_get_onefile(path_remote,file_status,path_local,type)
#         type: 0=file,1=directory,2=symlink
sub do_get_onefile {
    local($path_remote) = $_[0];
    local($file)        = $_[1];
    local($path_local)  = $_[2];
    local($type)        = $_[3];
    local($flag_chmod)  = 1;
    local(@words,$len,$get_type);
    local($timestamp);
        # get_type: 0=all,1=append
    if (&ifselect($file) != 0) {
	$get_type = 0;
	if ($opt_resume != 0) {
	    $len   = -s $path_local;
	    @words = split(/ +/,$file);
	    if ($words[4] <= $len) {
		return;
	    }
	    $get_type = 1;
	}
	if ($copy_timestamp != 0) {
	    $timestamp = &cmdMDTM($path_remote);
	}
	if (($opt_new != 0) && (-e $path_local)) {
	    if ($type == 0) {
		if ($copy_timestamp == 0) {
		    if (&check_time($path_remote,$path_local) <= 0) {
			return;
		    }
		}
		else {
		    if (&check_time2($timestamp,$path_local) <= 0) {
			return;
		    }
		}
		$get_type = 0;
	    }
	    elsif ($type == 1) {
		return;
	    }
	}
	&do_print_onefile($path_remote,$file);
	if ($type == 0) {	# file
	    &create_localdir($path_local);
	    &do_get($path_remote,$path_local,$get_type);
	    if (($copy_timestamp != 0) && ($timestamp ne '00000000000000')) {
		&set_timestamp($timestamp, $path_local);
	    }
	}
	elsif ($type == 1) {	# directory
	    if ($path_local !~ /\/$/) {
		$path_local .= '/';
	    }
	    &create_localdir($path_local);
	}
	elsif ($type == 2) {	# symlink
	    $flag_chmod = 0;
	    @words = &lsparse($file);
	    if ($words[3] =~ /^\//) {
		$words[3] =~ s/^$opt_dir/$opt_localdir/;
	    }
	    &create_localdir($path_local);
	    if (-e $path_local) {
		if (unlink($path_local) != 1) {
		    &print_msg(1,"unlink: %s\n",$!);
		}
	    }
	    symlink($words[3],$path_local);
	}
	if (($opt_cpmod != 0) && ($flag_chmod != 0)) {
	    local($mode) = &get_permission_remote($file);
	    &print_msg(2,"chmod %04o %s\n",$mode,$path_local);
	    if (chmod($mode,$path_local) == 0) {
		&print_msg(1,"cannot execute \'chmod %04o %s\'.\n",
			   $mode,$path_local);
		# exit 1;
	    }
	}
    }
}

#--------------------------------
# do_get(path_remote,path_local,get_type)
#    get_type: 0=all,1=append
sub do_get {
    if ($_[2] == 0) {
	&cmdRETR($_[0],$_[1]);
    }
    else {
	&cmdREST($_[0],$_[1],-s $_[1]);
    }
}

#--------------------------------
# create_localdir(path_local)
sub create_localdir {
    local($path)  =  $_[0];
    local(@words) = split(/\//,$path);
    local($tmp) = '';
    local($i);
    if ($path =~ /\/$/) {
	$imax = $#words;
    }
    else {
	$imax = $#words - 1;
    }
    for ( $i = 0 ; $i <= $imax ; $i++ ) {
	if ($words[$i] eq '') {
	    next;
	}
	$tmp .= '/' . $words[$i];
	if ((-d $tmp) == 0) {
	    if (mkdir($tmp,0777) == 0) {
		&print_msg(1,"mkdir: %s\n",$!);
		exit 1;
	    }
	}
    }
    return 0;
}

#--------------------------------
# check_time(file_remote,file_local)
#   return 0: equal
#         -1: file_remote <(new) file_local
#          1: file_remote (new)> file_local
sub check_time {
    local($time_remote) = &cmdMDTM($_[0]);
    local(@status)      = stat($_[1]);
    local(@time_tmp)    = gmtime($status[9]);
    local($time_local)
	= sprintf("19%02d%02d%02d%02d%02d%02d",
		  $time_tmp[5],$time_tmp[4]+1,
		  $time_tmp[3],$time_tmp[2],
		  $time_tmp[1],$time_tmp[0] );
    if ($time_remote eq $time_local) {
	return 0;
    }
    elsif ($time_remote lt $time_local) {
	return -1;
    }
    else {
	return 1;
    }
}

#--------------------------------
# check_time2(time_remote,file_local)
#   return 0: equal
#         -1: time_remote <(new) file_local
#          1: time_remote (new)> file_local
sub check_time2 {
    local($time_remote) = $_[0];
    local(@status)      = stat($_[1]);
    local(@time_tmp)    = gmtime($status[9]);
    local($time_local)
	= sprintf("19%02d%02d%02d%02d%02d%02d",
		  $time_tmp[5],$time_tmp[4]+1,
		  $time_tmp[3],$time_tmp[2],
		  $time_tmp[1],$time_tmp[0] );
    if ($time_remote eq $time_local) {
	return 0;
    }
    elsif ($time_remote lt $time_local) {
	return -1;
    }
    else {
	return 1;
    }
}

#--------------------------------
# get_permission_remote(file_status)
sub get_permission_remote {
    local($status)  = $_[0];
    local($l,$c,$r) = (0,0,0);
    if ($status =~ /^.r......../) { $l += 4; }
    if ($status =~ /^..w......./) { $l += 2; }
    if ($status =~ /^...x....../) { $l += 1; }
    if ($status =~ /^....r...../) { $c += 4; }
    if ($status =~ /^.....w..../) { $c += 2; }
    if ($status =~ /^......x.../) { $c += 1; }
    if ($status =~ /^.......r../) { $r += 4; }
    if ($status =~ /^........w./) { $r += 2; }
    if ($status =~ /^.........x/) { $r += 1; }
    return (($l*8*8)+($c*8)+$r);
}

#--------------------------------
# check_scope(basedir,remote_path,file)
sub check_scope {
    local($basedir)     = $_[0];
    local($remote_path) = $_[1];
    local($file)        = $_[2];
    if ($file =~ /^\//) {
	if ($file =~ /^$basedir/) {
	    return 0;
	}
    }
    else {
	local($q0,$q1,$total);
	local($min,$q2,$q3);
	if ($basedir eq $remote_path) {
	    $total = 0;
	}
	else {
	    local(@words) = split(/^$basedir/,$remote_path);
	    if ( $#words != 1 ) {
		return 1;
	    }
	    ($q0,$q1,$total) = &ptoi($words[1]);
	}
	($min,$q2,$q3) = &ptoi($file);
	if (0 <= ($total+$min)) {
	    return 0;
	}
    }
    return 1;
}

#--------------------------------
# ptoi(path)
sub ptoi {
    local(@words) = split(/\//,$_[0]);
    local($min,$max,$total) = (0,0,0);
    local($i);
    for ($i = 0 ; $i <= $#words ; $i++) {
        if ( $words[$i] eq '.' ) {
	    next;
        } elsif ( $words[$i] eq '..' ) {
	    $total--;
        } else {
	    $total++;
        }
	if ($total < $min) {
	    $min = $total;
	}
	if ($max < $total) {
	    $max = $total;
	}
    }
    return ($min,$max,$total);
}

#--------------------------------
# set_timestamp(timestamp, path)
sub set_timestamp {
    local($timestamp) = $_[0];
    local($path)      = $_[1];
    if ($timestamp =~ /^(\d{12})(\d\d)$/) {
	local(@args) = ("touch","-am","-t","$1.$2","$path");
	if (system(@args) != 0) {
	    &print_msg(1,"touch %s: %s\n", $path, $!);
	    exit 1;
	}
    }
}


#++++++++++++++++++++++++++++++++++++++++++++++++
#  -put option
#++++++++++++++++++++++++++++++++++++++++++++++++

#--------------------------------
# do_put_alldirs(path_remote,path_local)
sub do_put_alldirs {
    local($path_remote) = $_[0];
    local($path_local)  = $_[1];
    local($file);
    local(@tmp);

    if ($path_remote =~ /\/$/) {
	chop($path_remote);
    }
    if (-d $path_local) {
	if ($path_local =~ /\/$/) {
	    chop($path_local);
	}
    }
    else {
	if ($path_local =~ /\/$/) {
	    &usage();
	    exit 1;
	}
    }
    local(@allfiles) = &local_allfilename($path_local);
    while(@allfiles) {
	@tmp  = split(/$path_local/,$allfiles[0]);
	$file = $tmp[1];
	if ($file eq '') {
	    shift @allfiles;
	    next;
	}
	&do_put_onefile($path_remote . $file,$allfiles[0]);
	shift @allfiles;
    }
}

#--------------------------------
# do_put_onefile(path_remote,path_local)
sub do_put_onefile {
    local($path_remote) = $_[0];
    local($path_local)  = $_[1];
    local($mode);
    if (&ifselect_local($path_local) != 0) {
	if ($opt_new != 0) {
	    if (&check_time($path_remote,$path_local) >= 0) {
		return;
	    }
	}
	if (-d $path_local) {
	    &do_print_local($path_remote);
	    &cmdMKD($path_remote);
	}
	else {
	    &do_print_local($path_remote);
	    &cmdSTOR($path_local,$path_remote);
	}
	if ($opt_cpmod != 0) {
	    $mode = sprintf("%04o",&get_permission_local($path_local));
	    &print_msg(2,"chmod %s %s\n",$mode,$path_remote);
	    &cmdCHMOD($mode,$path_remote);
	}
    }
}

#--------------------------------
# local_allfilename(directory)
sub local_allfilename {
    local(@files,@status,@allfiles);
    local($dir) = $_[0];
    if (open(FILES,"find $dir -print -follow|") == 0) {
	&print_msg(1,"open: %s\n",$!);
	exit 1;
    }
    while (<FILES>) {
	chop($_);
	push(@files,$_);
    }
    close(FILES);
    @allfiles = sort(@files);
}

#--------------------------------
# ifselect_local(file_local)
sub ifselect_local {
    local($path)  = $_[0];
    local(@words) = split(/\//,$path);
    local($file)  = $words[$#words];
    local($flag)  = 1;
    local($i);
    for ($i = 0 ; $i <= $#opt_regexp ; $i++) {
	if ($file !~ $opt_regexp[$i]) {
	    $flag = 0;
	}
    }
    for ( $i = 0 ; $i <= $#opt_types ; $i++ ) {
	if (-l $path) {
	    if ($opt_types[$i] ne 'l') {
		$flag = 0;
	    }
	}
	elsif (-d $path) {
	    if ($opt_types[$i] ne 'd') {
		$flag = 0;
	    }
	}
	elsif (-f $path) {
	    if ($opt_types[$i] ne '-') {
		$flag = 0;
	    }
	}
    }
    return $flag;
}

#--------------------------------
# do_print_local(path)
sub do_print_local {
    if (($opt_print != 0) || ($opt_ls != 0)) {
	printf("$_[0]\n");
    }
}

#--------------------------------
# get_permission_local(path)
sub get_permission_local {
    local(@status) = stat($_[0]);
    return ($status[2]%(8*8*8));
}


#++++++++++++++++++++++++++++++++++++++++++++++++
#  parse option
#++++++++++++++++++++++++++++++++++++++++++++++++

#--------------------------------
# print_opt()
sub print_opt {
    local($i);
    if (2 <= $verbose) {
	printf( STDERR "url:      %s\n", $opt_url );
	printf( STDERR "server:   %s\n", $opt_server );
	printf( STDERR "dir:      %s\n", $opt_dir );
	printf( STDERR "proxy:    %s\n", $opt_proxy );
	printf( STDERR "login:    %s\n", $opt_login );
	if (3 <= $verbose) {
	    printf( STDERR "password: %s\n", $opt_password );
	}
	printf( STDERR "ls:       %d\n", $opt_ls );
	for ( $i = 0 ; $i <= $#opt_regexp ; $i++ ) {
	    printf( STDERR "regexp%d:  %s\n", $i, $opt_regexp[$i] );
	}
	for ( $i = 0 ; $i <= $#opt_types ; $i++ ) {
	    printf( STDERR "type%d:    %s\n", $i, $opt_types[$i] );
	}
	printf( STDERR "print:    %d\n", $opt_print );
	printf( STDERR "(0:none,1:delete,2:get,3:put,4:chmod): %d\n", $opt_movekind);
	printf( STDERR "localdir: %s\n", $opt_localdir );
	printf( STDERR "new:      %d\n", $opt_new );
	printf( STDERR "resume:   %d\n", $opt_resume );
	printf( STDERR "chmod:    %s\n", $opt_chmod );
	printf( STDERR "cpmod:    %d\n", $opt_cpmod );
    }
}

#--------------------------------
# set_opt()
sub set_opt {
    local($ret_login,$ret_password);
    local($tmp);
    &analyze_args();
    ($opt_server,$opt_dir)     = &analyze_url($opt_url);
    ($ret_login,$ret_password) = &read_netrc($opt_server);
    if ($opt_login eq '') {
	$opt_login = $ret_login;
	if ($opt_login eq '') {
	    $opt_login = getlogin();
	    printf(STDERR "Name (%s:%s): ", $opt_server,$opt_login);
	    $tmp = <STDIN>;
	    chop($tmp);
	    if ($tmp =~ /\S+/) {
		$opt_login = $&;
	    }
	}
    }
    if ($opt_password eq '') {
	$opt_password = $ret_password;
	if ($opt_password eq '') {
	    system 'stty', '-echo';
	    printf(STDERR "Password: ");
	    $opt_password = <STDIN>;
	    printf(STDERR "\n");
	    chop($opt_password);
	    system 'stty', 'echo';
	}
    }
}

#--------------------------------
# analyze_args()
sub analyze_args {
    local($i);
    if ($#ARGV < 0) {
	&usage();
	exit 1;
    }

    $i = 0;			                # url
    $opt_url = $ARGV[$i];
    $i++;
    for ( ; $i <= $#ARGV ; $i++ ) {
	if ($ARGV[$i] eq '-login') {		# -login
	    $i++;
	    if (($#ARGV < $i) || ($opt_login ne '')) {
		&usage();
		exit 1;
	    }
	    $opt_login = $ARGV[$i];
	}
	elsif ($ARGV[$i] eq '-password') {	# -password
	    $i++;
	    if (($#ARGV < $i) || ($opt_password ne '')) {
		&usage();
		exit 1;
	    }
	    $opt_password = $ARGV[$i];
	}
	elsif ($ARGV[$i] eq '-ls') {		# -ls
	    $opt_ls  = 1;
	}
	elsif ($ARGV[$i] eq '-regexp') {	# -regexp
	    $i++;
	    if ($#ARGV < $i) {
		&usage();
		exit 1;
	    }
	    push(@opt_regexp,$ARGV[$i]);
	}
	elsif ($ARGV[$i] eq '-type') {		# -type
	    $i++;
	    if ($#ARGV < $i) {
		&usage();
		exit 1;
	    }
	    if ($ARGV[$i] !~ /^[dfl]$/) {
		&usage();
		exit 1;
	    }
	    if ($ARGV[$i] !~ /f/) {
		push(@opt_types,$ARGV[$i]);
	    }
	    else {
		push(@opt_types,'-');
	    }
	}
	elsif ($ARGV[$i] eq '-print') {		# -print
	    $opt_print = 1;
	}
	elsif ($ARGV[$i] eq '-delete') {	# -delete
	    if ($opt_movekind != 0) {
		&usage();
		exit 1;
	    }
	    $opt_movekind = 1;
	}
	elsif ($ARGV[$i] eq '-get') {		# -get
	    if ($opt_movekind != 0) {
		&usage();
		exit 1;
	    }
	    $opt_movekind = 2;
	    if (($i < $#ARGV) && ($ARGV[$i+1] !~ /^-/)) {
		$i++;
		$opt_localdir = $ARGV[$i];
	    }
	}
	elsif ($ARGV[$i] eq '-put') {          # -put
	    if ($opt_movekind != 0) {
		&usage();
		exit 1;
	    }
	    $opt_movekind = 3;
	    if (($i < $#ARGV) && ($ARGV[$i+1] !~ /^-/)) {
		$i++;
		$opt_localdir = $ARGV[$i];
	    }
	}
	elsif ($ARGV[$i] eq '-new') {         # -new
	    if (($opt_movekind != 2) && ($opt_movekind != 3)) {
		&usage();
		exit 1;
	    }
	    $opt_new = 1;
	}
	elsif ($ARGV[$i] eq '-resume') {      # -resume
	    if ($opt_movekind != 2) {
		&usage();
		exit 1;
	    }
	    $opt_resume = 1;
	}
	elsif ($ARGV[$i] eq '-chmod') {      # -chmod
	    $i++;
	    if (($opt_movekind != 0) || ($#ARGV < $i)) {
		&usage();
		exit 1;
	    }
	    if ($ARGV[$i] !~ /^0[0-7][0-7][0-7]$/) {
		&usage();
		exit 1;
	    }
	    $opt_movekind = 4;
	    $opt_chmod    = $ARGV[$i];
	}
	elsif ($ARGV[$i] eq '-cpmod') {      # -cpmod
	    if (($opt_movekind != 2) && ($opt_movekind != 3)) {
		&usage();
		exit 1;
	    }
	    $opt_cpmod = 1;
	}
	elsif ($ARGV[$i] eq '-proxy') {	     # -proxy
	    $i++;
	    if (($#ARGV < $i) || ($opt_proxy ne '')) {
		&usage();
		exit 1;
	    }
	    $opt_proxy = $ARGV[$i];
	}
	else {
	    &usage();
	    exit 1;
	}
    }

    if (($opt_movekind == 2) || ($opt_movekind == 3)) {
	if ($opt_localdir !~ /^\//) {
	    $opt_localdir = $ENV{'PWD'} . '/' . $opt_localdir;
	}
    }

    if (($opt_movekind == 0)
	&& ($opt_ls == 0)
	&& ($opt_print == 0)) {
	$opt_print = 1;
    }
}

#--------------------------------
# analyze_url(url)
sub analyze_url {
    local($url) = $_[0];
    local($server,$directory);
    if ($url =~ /^ftp:\/\/(\S+)/) {
	$url = $1;
    }
    if (($i = index($url,'/')) == -1) {
	$server = $url;
    }
    else {
	$server    = substr($url,0,$i);
	$directory = substr($url,$i);
    }
    return ($server,$directory);
}

#--------------------------------
# read_netrc(machine)
sub read_netrc {
    local($machine)      = $_[0];
    local($file)         = $ENV{'HOME'};
    local($ret_login)    = '';
    local($ret_password) = '';
    local(@words);
    if ($file =~ /\/$/) {
	$file .= '.netrc';
    }
    else {
	$file .= '/' . '.netrc';
    }
    if (open(NETRC,$file) == 0) {
	&print_msg(2,"cannot open %s.\n",$file);
	return ('','');
    }
    $i = 0;
    while (<NETRC>) {
	chop($_);
	@words = split(/ +/,$_);
	if (($words[0] =~ /^machine/) && ($words[1] eq $machine)) {
	    $i = 2;
	}
	elsif ($words[0] =~ /^default/) {
	    $i = 1;
	}
	if ($i != 0) {
	    for ( ; $i <= $#words ; $i++ ) {
		if ($words[$i] eq 'login') {
		    $i++;
		    $ret_login = $words[$i];
		}
		elsif ($words[$i] eq 'password') {
		    $i++;
		    $ret_password = $words[$i];
		}
	    }
	    last;
	}
    }
    close(NETRC);
    return ($ret_login,$ret_password);
}


#++++++++++++++++++++++++++++++++++++++++++++++++
#  socket
#++++++++++++++++++++++++++++++++++++++++++++++++

#--------------------------------
# copen()
sub copen {
    local($oldfh);
    if (socket(SCTRL,$AF_INET,$SOCK_STREAM,0) == 0) {
	&print_msg(1,"socket: %s\n",$!);
	exit 1;
    }
    $oldfh = select(SCTRL);
    $| = 1;
    select($oldfh);
}

#--------------------------------
# cconnect(server,port)
sub cconnect {
    local($name,$aliases,$type,$len,$thataddr,$that);
    ($name,$aliases,$type,$len,$thataddr) = gethostbyname($_[0]);
    $that = pack($sock_addr,$AF_INET,$_[1],$thataddr);
    if (connect(SCTRL,$that) == 0) {
	&print_msg(1,"connect: %s\n",$!);
	exit 1;
    }
    local($tmp);
    for (;;) {
	$tmp = &cgets();
	if ($tmp !~ /^2/) {
	    &print_msg(1,"connect failed.\n");
	    exit 1;
	}
	elsif ($tmp =~ /^220[ \t]/) {
	    last;
	}
    }
}

#--------------------------------
sub cprintf {
    &print_msg(2,@_);
    printf(SCTRL @_);
}

#--------------------------------
sub cgets {
    local($s);
    $s = <SCTRL>;
    &print_msg(2,"%s",$s);
    $s;
}

#--------------------------------
# dopen()
sub dopen {
    local($oldfh);
    local($this);
    if ($use_pasv) {
	if (socket(NSDATA,$AF_INET,$SOCK_STREAM,0) == 0) {
	    &print_msg(1,"socket: %s\n",$!);
	    exit 1;
	}
	local(@adr) = &cmdPASV();
	if ($#adr == 0) {
	    &print_msg(1,"Passive mode refused.\n");
	    exit 1;
	}
	local($ipadr) = sprintf("%d.%d.%d.%d", $adr[0],$adr[1],$adr[2],$adr[3]);
	local($port)  = $adr[4]*256 + $adr[5];
	local($name,$aliases,$type,$len,$thataddr,$that);
	($name,$aliases,$type,$len,$thataddr) = gethostbyname($ipadr);
	$that = pack($sock_addr,$AF_INET,$port,$thataddr);
	if (connect(NSDATA,$that) == 0) {
	    &print_msg(1,"connect: %s\n",$!);
	    exit 1;
	}
    }
    else {
	if (socket(SDATA,$AF_INET,$SOCK_STREAM,0) == 0) {
	    &print_msg(1,"socket: %s\n",$!);
	    exit 1;
	}
	$oldfh = select(SDATA);
	$| = 1;
	select($oldfh);
	$this = pack($sock_addr,$AF_INET,$dport,"\0\0\0\0");
	if (bind(SDATA,$this) == 0) {
	    &print_msg(1,"bind: %s\n",$!);
	    exit 1;
	}
	if (listen(SDATA,5) == 0) {
	    &print_msg(1,"listen: %s\n",$!);
	    exit 1;
	}
	&cmdPORT();
    }
}

#--------------------------------
# dconnect()
sub dconnect {
    if ($use_pasv) {
	# NOP
    }
    else {
	if (accept(NSDATA,SDATA) != 0) {
	    &print_msg(1,"accept: %s\n",$!);
	    exit 1;
	}
	close(SDATA);
    }
}

#--------------------------------
# dclose()
sub dclose {
    close(NSDATA);
}


#++++++++++++++++++++++++++++++++++++++++++++++++
#  ftp command
#++++++++++++++++++++++++++++++++++++++++++++++++

#--------------------------------
sub login {
    local($tmp);
    &cprintf("USER %s\n", $_[0]);
    for (;;) {
	$tmp = &cgets();
	if ($tmp =~ /^230[ \t]/) {
	    &print_msg(2,"login ok.\n");
	    return;
	} elsif ($tmp =~ /^331[ \t]/) {
	    last;
        } elsif ($tmp =~ /^\d{3}-/) {
	    next;
	} else {
	    &print_msg(1,"%s",$tmp);
	    exit 1;
	}
    }
    &cprintf("PASS %s\n", $_[1]);
    for (;;) {
	$tmp = &cgets();
	if (($tmp !~ /^2/) || ($tmp =~ /^202/)) {
	    &print_msg(1,"%s",$tmp);
	    exit 1;
	}
	elsif ($tmp =~ /^230[ \t]/) {
	    last;
	}
    }
    &print_msg(2,"login ok.\n");
}

#--------------------------------
# cmdPORT()
sub cmdPORT {
    local($mysockaddr,$family,$port,$myaddr,$myport);
    local($a,$b,$c,$d,$hi,$lo);

    # get myport number
    $mysockaddr               = getsockname(SDATA);
    ($family,$myport,$myaddr) = unpack($sock_addr,$mysockaddr);

    # get myaddr
    $mysockaddr               = getsockname(SCTRL);
    ($family,$port,$myaddr)   = unpack($sock_addr,$mysockaddr);

    # write to server "PORT x,x,x,x,x,x"
    ($a,$b,$c,$d) = unpack('C4',$myaddr);
    $hi           = ($myport >> 8) & 0x00ff;
    $lo           = $myport & 0x00ff;
    &cprintf("PORT %d,%d,%d,%d,%d,%d\n", $a,$b,$c,$d,$hi,$lo);
    local($tmp) = &cgets();
    if ($tmp !~ /^2/) {
	&print_msg(1,"%s",$tmp);
	exit 1;
    }
}

#--------------------------------
# cmdLIST(directory)
sub cmdLIST {
    local($tmp);
    &dopen();
    &cprintf("LIST %s\n",$_[0]);
    $tmp = &cgets();
    if ($tmp !~ /^150/) {
	&print_msg(1,"%s",$tmp);
	exit 1;
    }
    &dconnect();
    local(@ret) = <NSDATA>;
    &dclose();
    $tmp = &cgets();
    if ($tmp !~ /^2/) {
	&print_msg(1,"%s",$tmp);
	exit 1;
    }
    @ret;
}

#--------------------------------
# cmdNLST(directory)
sub cmdNLST {
    local($tmp);
    &dopen();
    &cprintf("NLST %s\n",$_[0]);
    &cgets();
    &dconnect();
    local(@ret) = <NSDATA>;
    &dclose();
    $tmp = &cgets();
    if ($tmp !~ /^2/) {
	&print_msg(1,"%s",$tmp);
	exit 1;
    }
    @ret;
}

#--------------------------------
# cmdTYPE(type)
sub cmdTYPE {
    &cprintf("TYPE %s\n",$_[0]);
    local($tmp) = &cgets();
    if ($tmp !~ /^2/) {
	&print_msg(1,"%s",$tmp);
	exit 1;
    }
}

#--------------------------------
# cmdQUIT()
sub cmdQUIT {
    &cprintf("QUIT\n");
    local($tmp) = &cgets();
    if ($tmp !~ /^2/) {
	&print_msg(1,"%s",$tmp);
	exit 1;
    }
}

#--------------------------------
# cmdRMD(directory)
sub cmdRMD {
    &cprintf("RMD %s\n",$_[0]);
    local($tmp) = &cgets();
    if ($tmp !~ /^2/) {
	&print_msg(1,"%s",$tmp);
    }
}

#--------------------------------
# cmdDELE(file)
sub cmdDELE {
    &cprintf("DELE %s\n",$_[0]);
    local($tmp) = &cgets();
    if ($tmp !~ /^2/) {
	&print_msg(1,"%s",$tmp);
    }
}

#--------------------------------
# cmdRETR(fromfile,tofile)
sub cmdRETR {
    &dopen();
    &cprintf("RETR %s\n",$_[0]);
    local($ret) = &cgets();
    if ($ret !~ /^150/) {
	&print_msg(1,"%s",$ret);
	&dclose();
	return;
    }
    if (open(TO,">$_[1]") == 0) {
	&print_msg(1,"open %s: %s\n", $_[1], $!);
	exit 1;
    }
    &dconnect();
    while (<NSDATA>) {
	print TO $_;
    }
    close(TO);
    &dclose();
    $ret = &cgets();
    if ($ret !~ /^2/) {
	&print_msg(1,"%s",$ret);
	exit 1;
    }
}

#--------------------------------
# cmdCWD(directory)
sub cmdCWD {
    local($tmp);
    &cprintf("CWD %s\n",$_[0]);
    for (;;) {
	$tmp = &cgets();
	if ($tmp !~ /^250-/) {
	    if ($tmp =~ /^250/) {
		return 0;
	    }
	    else {
		return 1;
	    }
	}
    }
}

#--------------------------------
# cmdMKD(directory)
sub cmdMKD {
    &cprintf("MKD %s\n",$_[0]);
    &cgets();
}

#--------------------------------
# cmdSTOR(fromfile,tofile)
sub cmdSTOR {
    local($tmp);
    &dopen();
    &cprintf("STOR %s\n",$_[1]);
    $tmp = &cgets();
    if ($tmp !~ /^150/) {
	&print_msg(1,"%s",$tmp);
	exit 1;
    }
    if (open(FROM,$_[0]) == 0) {
	&print_msg(1,"open %s: %s\n", $_[0], $!);
	# exit 1;
    }
    &dconnect();
    while (<FROM>) {
	print NSDATA $_;
    }
    close(FROM);
    &dclose();
    $tmp = &cgets();
    if ($tmp !~ /^2/) {
	&print_msg(1,"%s",$tmp);
	exit 1;
    }
}

#--------------------------------
# cmdMDTM(file)
sub cmdMDTM {
    local($tmp);
    &cprintf("MDTM %s\n",$_[0]);
    if (($tmp=&cgets()) !~ /^2/) {
	return "00000000000000";
    }
    local(@ret) = split(/ +/,$tmp);
    $ret[1] =~ s/[\r\n]//g;
    return $ret[1];
}

#--------------------------------
# cmdREST(fromfile,tofile,len)
sub cmdREST {
    &dopen();
    &cprintf("REST %d\n",$_[2]);
    local($ret) = &cgets();
    if ($ret !~ /^350/) {
	&print_msg(1,"%s",$ret);
	&dclose();
	return;
    }
    &cprintf("RETR %s\n",$_[0]);
    $ret = &cgets();
    if ($ret !~ /^150/) {
	&print_msg(1,"%s",$ret);
	&dclose();
	return;
    }
    if (open(TO,">>$_[1]") == 0) {
	&print_msg(1,"open %s: %s\n", $_[1], $!);
	exit 1;
    }
    &dconnect();
    while (<NSDATA>) {
	print TO $_;
    }
    close(TO);
    &dclose();
    $tmp = &cgets();
    if ($tmp !~ /^2/) {
	&print_msg(1,"%s",$tmp);
	exit 1;
    }
}

#--------------------------------
# cmdPWD()
sub cmdPWD {
    &cprintf("PWD\n");
    local($tmp) = &cgets();
    if ($tmp !~ /^2/) {
	return '';
    }
    local(@word) = split(/ +/,$tmp);
    $word[1] =~ s/[\"]//g;
    return $word[1];
}

#--------------------------------
# cmdCHMOD(mode,path)
sub cmdCHMOD {
    &cprintf("SITE CHMOD %s %s\n",$_[0],$_[1]);
    local($tmp) = &cgets();
     if ($tmp !~ /^2/) {
	 &print_msg(1,"%s",$tmp);
	 exit 1;
    }
    return;
}

#--------------------------------
# cmdPASV()
sub cmdPASV {
    &cprintf("PASV\n");
    local($tmp) = &cgets();
    if ($tmp !~ /^227\s+/) {
	return 0;
    }
    if ($tmp =~ /(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)/) {
	return ($1,$2,$3,$4,$5,$6);
    }
    else {
	return 0;
    }
}
