#!/usr/local/bin/perl # ps で見られてもいいように $0 を書き換える BEGIN {$0=~s|^\S+ (\S+)|$1|;} use Socket; # Socketモジュールを使う $ENV{'TZ'} = "Japan"; ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); if ($sec < 10) { $sec = "0$sec"; } if ($min < 10) { $min = "0$min"; } if ($hour < 10) { $hour = "0$hour"; } $month = ($mon + 1); if ($month < 10) { $month = "0$month"; } if ($mday < 10) { $mday = "0$mday"; } $year = $year + 1900; $date = "$year年$month月$mday日 $hour時"; $file_name = "$year$month$mday$hour$min$sec\_ITC.tar.gz"; # 実行方法 perl ftp_time.pl PUT id@domain:dir/file -password password -in test_file # cron(1時間毎) 1 * * * * /usr/bin/perl /home/nobuhiro/cron_back/ftp_time.pl $username = 'id'; $password = 'password'; $hostname = 'host.domain'; $target_file = "backup\/$file_name"; $mode = 'put'; # get か put $passive = 0; # デフォルトは Active モード $infile = '/home/id/cron_back/data/test_file'; # デフォルトは '-' ('-' は標準入力) $outfile = '-'; # デフォルトは '-' ('>-' は標準出力) $timeout = 0; # タイムアウトの秒数 $verbose = 0; #----------------- 引数解析 --------------------- # 引数がなかったらエラー #if ( ! @ARGV ){&usage;} for ( $i=0 ; $i<@ARGV ; $i++ ){ $_ = $ARGV[$i]; # anonymous FTP if ( m|^ftp://(.*)|i ){ if ( $1 =~ m|^([a-zA-Z0-9\.\-\_]+)(/?.*)$| ){ $hostname = $1; $target_file = $2 || '/'; } else { &usage; } $mode = 'get'; $username = 'anonymous'; # パスワードを作成するために、 # ユーザ名とホスト名を取得 $local_username = `whoami`; chop $local_username; $local_hostname = `hostname`; chop $local_hostname; $password = "$local_username\@$local_hostname"; } elsif ( m/^GET|PUT$/i ){ $mode = $_; $mode =~ tr/A-Z/a-z/; $_ = $ARGV[++$i]; if ( m|^(.*?)@([a-zA-Z0-9\.\-\_]+):(.*)$| ){ ($username,$hostname,$target_file) = ($1,$2,$3); } else { &usage; } } elsif ( m/^-password$/i ){ $password = $ARGV[++$i] || &usage; } elsif ( m/^-in$/i ){ $infile = $ARGV[++$i] || &usage; } elsif ( m/^-out$/i ){ $outfile = $ARGV[++$i] || &usage; } elsif ( m/^-timeout$/i ){ $timeout = $ARGV[++$i] || &usage; &usage unless ( $timeout =~ m/^\d+$/ ); } elsif ( m/^-passive$/i ){ $passive = 1; } elsif ( m/^-v$/i ){ $verbose = 1; } else { &usage; } } # 必須の項目が未設定ならエラー unless ( $mode || $hostname || $username || $filename ){ &usage; } # パスワードが未設定なら、標準入力から入力させる。 # stty でエコーバックを ON/OFF にしている。 while ( $password eq "" ){ print "Password: "; system("stty -echo >/dev/null 2>&1"); $password = ; chomp $password; system("stty echo >/dev/null 2>&1"); print "\n"; } if ( $verbose ){ print STDERR "-----------------------\n"; print STDERR "mode = $mode\n"; print STDERR "hostname = $hostname\n"; print STDERR "username = $username\n"; print STDERR "password = $password\n"; print STDERR "filename = $target_file\n"; print STDERR "infile = $infile\n"; print STDERR "outfile = $outfile\n"; print STDERR "timeout = $timeout\n"; print STDERR "-----------------------\n"; } #----------------- タイムアウト設定 ------------------- if ( $timeout ){ $SIG{ALRM} = sub { if ( defined $pid ){ kill 'TERM',$pid; } die "TIMEOUT! ($timeout secs)"; }; alarm($timeout); } #------ コマンドコネクションを作成し、ログイン -------- $port = getservbyname('ftp','tcp'); &client_work(COMMAND,$hostname,$port); &read_response; &send_command("USER $username\r\n"); &read_response; &send_command("PASS $password\r\n"); $ret = &read_response; if ( $ret =~ m/^5\d\d/ ){ print STDERR "ログインできませんでした。\n"; exit; } &send_command("TYPE I\r\n"); &read_response; #----------- データコネクションオープン ------------ if ( ! $passive ){ # Active モード ($local_ip,$local_port) = &server_work; # コマンドコネクションに PORT コマンドを送信 &send_command( sprintf("PORT $local_ip,%d,%d\r\n",$local_port/256,$local_port%256) ); &read_response; } else { # Passive モード &send_command("PASV\r\n"); $ret = &read_response; # レスポンスから情報を取得 if ( $ret =~ m/^2\d\d .*\((\d+,\d+,\d+,\d+),(\d+),(\d+)\)/ ){ $data_connection_port = $2*256+$3; $data_connection_host = $1; $data_connection_host =~ s/,/\./g; } else { print STDERR $ret; exit; } } #------------- RETR/STOR/LIST を送信 --------------- # put なら STOR # get でディレクトリらしきものなら LIST # get でファイルなら RETR if ( $mode eq 'put' ){ &send_command("STOR $target_file\r\n"); } elsif ( $target_file =~ m|/$| ){ &send_command("LIST $target_file\r\n"); } else { &send_command("RETR $target_file\r\n"); } #---------------- データ送信/受信 ------------------ $start_time = time; # 親プロセス if ( $pid = fork() ){ $ret = &read_response; # エラー。子プロセスを殺す if ( $ret =~ m/^5/ ){ print STDERR $ret; kill 'TERM',$pid; } else { &read_response; } # 子プロセス。データコネクションから読み込む } else { if ( $passive ){ &client_work(DATA,$data_connection_host,$data_connection_port); } else { accept(DATA,DATA_WAITING); } if ( $mode eq 'put' ){ open(IN,$infile) || die "$infile: $!"; while (){ print DATA $_; } close(IN); } else { open(OUT,">$outfile") || die "$outfile: $!"; print OUT ; close(OUT); } close(DATA); $GETTIME = $start_time - time; $kbps = (1024 / $GETTIME) / 1000; print "$date $GETTIME kbps\n"; exit; } # QUITを送ってセッション終了 &send_command("QUIT\r\n"); &read_response; close(COMMAND); exit; #-------------------------------------------------------------- # サーバにデータを送信 # sub send_command { $verbose && print STDERR "$_[0]"; print COMMAND $_[0]; } #-------------------------------------------------------------- # サーバからのレスポンスを読み込む。 # 複数行のレスポンスに対応。 sub read_response { $buf = ; $verbose && print STDERR "--> $buf"; # 複数行のレスポンス。 if ( $buf =~ m/^(\d\d\d)-/ ){ $return_code = $1; while (){ $verbose && print STDERR "--> $_"; $buf .= $_; if ( /^$return_code / ){ last; } } } return $buf; } #------------------------------------------------------- # 引数でソケット名、ホスト、ポート番号を指定すると、 # ホスト、ポートに接続する。 sub client_work { ($SOCK,$host,$port) = @_; # ホスト名を、IP アドレスの構造体に変換 unless ( $iaddr = inet_aton($host) ){ die "$host は存在しないホストです。$!"; } # ポート番号と IP アドレスをまとめて構造体に変換 $sock_addr = pack_sockaddr_in($port,$iaddr); # ソケット生成 socket($SOCK,PF_INET,SOCK_STREAM,0) || die "ソケットを生成できません。\n"; # 指定のホストの指定の port に接続 connect($SOCK,$sock_addr) || die "$hostname のポート $port に接続できません。\n"; # ファイルハンドルをバッファリングしない select($SOCK); $|=1; select(STDOUT); } #------------------------------------------------------- # Active モードのデータ用コネクションを作成 sub server_work { # ソケット生成 socket(DATA_WAITING,PF_INET,SOCK_STREAM,0) || die "ソケットを生成できません。\n"; # ソケットオプション設定 setsockopt(DATA_WAITING,SOL_SOCKET,SO_REUSEADDR,1) || die "setsockopt でエラーが発生しました。\n"; # ソケットにアドレス(=名前)を割り付ける bind(DATA_WAITING,pack_sockaddr_in(0,INADDR_ANY)) || die "bind に失敗しました。$!"; # OSに、クライアントと接続し待ち行列に入れるよう指示 listen(DATA_WAITING,SOMAXCONN) || die "listen に失敗しました。$!"; # ローカルホストの IP アドレス・ポート番号を取得 $local_sock_addr = getsockname(COMMAND); ($tmp_port,$local_addr) = unpack_sockaddr_in($local_sock_addr); $local_ip = inet_ntoa($local_addr); $local_ip =~ s/\./,/g; # データコネクションのポート番号を取得 $local_sock_addr = getsockname(DATA_WAITING); ($local_port,$tmp_addr) = unpack_sockaddr_in($local_sock_addr); return ($local_ip,$local_port); } #-------------------------------------------------------------- # 使用方法を表示して終了 # sub usage { $myname = $0; # 自分自身の名前を取得 $myname =~ s|(.*/)?||; print STDERR <