使用Net::FTP进行巡游

当我们想要在机器之间交换文件时,我们通常会想到rsync、scp、git,甚至是缓慢且复杂的(看着你Artifactory和S3),但答案通常就在你的眼前:FTP!

“文件传输协议”提供了一种非常简单方便的文件共享方式。它经受了考验,几乎不需要维护,并且具有简单的匿名访问机制。它可以与多种标准认证方法以及一些虚拟认证方法集成,这里我没有展示。

在这篇文章中,我将安装一个本地FTP服务器,并在Perl中创建一个简单的FTP客户端。

一些背景信息

在《$work》中,我必须维护一支由开发者组成的队伍,他们从手工制作的本地配置文件中创建定制的构建管道。

这个文件并非“有意”托管,就像你在Travis CI或GitHub Action中看到的那样,但它被用来向一个“重型客户端”提供数据,该客户端通过HTTP API调用解析、解析模板,并在一些集中的自动化服务器中创建工作空间。

帮助开发者根据规范创建这个文件需要大量的支持(又一个文件格式),当我们想要帮助他们解决失败的工作空间创建/构建问题时,我们是无助的(无法从工作空间中检索配置)。

我得到了一个想法,在创建构建管道工作空间时自动备份和集中配置文件。这旨在帮助开发者(配置“示例”)和支持团队(查看历史记录,版本化后我们可以检查差异,文件可回放)。约束条件是能够从各种地方与不同的用户交换文件。FTP协议非常适合这个。

我还添加了一个cron作业来自动提交和推送到git仓库,我们神奇地拥有了一个列出版本化配置文件的网站。

此外,FTP后来证明也几乎不需要支持。我的意思是,真的不需要维护!

下载和安装ftpd

我决定使用pure-ftpd,但如果你想要,还有一些其他的良好替代方案。

首先,我下载了tar包,解压它,然后进入其目录

$ wget https://download.pureftpd.org/pub/pure-ftpd/releases/pure-ftpd-1.0.49.tar.gz
$ tar xvzf pure-ftpd-1.0.49.tar.gz
$ cd pure-ftpd-1.0.49/

我配置了ftpd,以便我可以作为一个普通用户(非root用户)使用非限制端口执行它,并将目标目录设置为我的$HOME/ftpd

$ ./configure --prefix=$HOME/ftpd --with-nonroot && make && make install

我创建了两个目录。ftp是我将要发布和运行的目录,我将在这里放置pidfile。

$ cd $HOME/ftpd
$ mkdir ftp
$ mkdir run

现在我们可以启动FTP服务器了。我需要提供一些自定义配置

  • FTP_ANON_DIR是我想要发布的目录
  • -e允许匿名访问
  • -M允许匿名用户创建目录
  • -g指定pidfile的目录

    $ FTP_ANON_DIR=`pwd`/ftp ; ./sbin/pure-ftpd -e -M -g run &

此时,我应该有一个正在运行的FTP服务器。让我们检查一下!

使用ftp进行测试

首先,我用预安装的ftp客户端进行测试。如果一切正常,我会看到典型的FTP交换

$ ftp localhost 2121
Connected to localhost.
220---------- Welcome to Pure-FTPd ----------
220-You are user number 1 of 50 allowed.
220-Local time is now 11:56. Server port: 2121.
220-Only anonymous FTP is allowed here
220 You will be disconnected after 15 minutes of inactivity.
Name (localhost:tib):
230 Anonymous user logged in
Remote system type is UNIX.
Using binary mode to transfer files.
ftp>

如果我得到ftp: connect: Connection refused,可能是因为以下原因之一:

  • ftpd没有运行(使用ps aux | grep "ftp[d]"进行检查)
  • 我使用了错误的端口

如果我得到421 Can't change directory to /home/tib/ftpd/ftp/ [/],可能是因为我没有创建在FTP_ANON_DIR中指定的目录。

Perl中的简单客户端

好的,这很酷,但到目前为止我只玩过ftp服务器和预安装的ftp客户端。现在写一些Perl怎么样?

Net::FTP是一个针对FTP协议的出色的CPAN模块,我会使用它。

简单列表

首先,一个非常简单的列表脚本ls.pl。这个程序连接到服务器,请求文件列表,并输出每个文件。很明显,用Perl玩FTP非常简单直接!

#!/usr/bin/env perl

use warnings;
use strict;

use Net::FTP;
my $HOST = "localhost";
my $PORT = 2121;


my $ftp = Net::FTP->new($HOST, Port => $PORT, Debug => 0)
	or die "Cannot connect to $HOST: $@";
$ftp->login() or die "Cannot login ", $ftp->message;
foreach my $f ($ftp->ls()) { print "$f\n"; }
$ftp->quit;

上传

接下来呢?也许上传一些东西?同样,这很简单。不是列出文件,而是put上传它们

#!/usr/bin/env perl

use warnings;
use strict;

use Net::FTP;
my $HOST = "localhost";
my $PORT = 2121;

my $ftp = Net::FTP->new($HOST, Port => $PORT, Debug => 0)
	or die "Cannot connect to $HOST: $@";
$ftp->login() or die "Cannot login ", $ftp->message;
foreach my $file(@ARGV) {
    $ftp->put("$file", "$file")
    	or die "Cannot put $file", $ftp->message;
}
$ftp->quit;

我运行这个脚本,并提供了我想上传的文件

$ perl upload.pl file1.txt file2.txt`.

整合起来

我提出一个更完整的客户端,具有一些命令行解析和更多操作。除了之前的列表和上传代码,这里我添加了查看文件的方式。使用Getopt::Long来处理命令行参数。

#!/usr/bin/env perl

use warnings;
use strict;

use Getopt::Long;
use File::Slurp;

use Net::FTP;
my $HOST = "localhost";
my $PORT = 2121;

my %options = ();

GetOptions(
	"action|c=s" => \$options{'action'},
	"file|f=s"   => \$options{'file'},
	"help|h"     => \$options{'help'}
	);

sub print_usage() {
	print "List all files :\n\t$0 -c list\n";
	print "Upload file :\n\t$0 -c upload -f file.txt\n";
	print "Print file :\n\t$0 -c view -f file.txt\n\n";
}

sub get_ftp() {
	my $ftp = Net::FTP->new($HOST, Port => $PORT, Debug => 0)
		or die "Cannot connect to $HOST: $@";
	$ftp->login() or die "Cannot login ", $ftp->message;
}

# ls / on remote ftp
sub list() {
	my $ftp = get_ftp();
	foreach my $f ($ftp->ls()) { print "$f\n"; }
	$ftp->quit;
}

# Upload a file
sub upload($) {
	my $file = shift;
	(-e $file) or return 1;

	my $ftp = get_ftp();
	$ftp->login() or die "Cannot login ", $ftp->message;
	$ftp->put("$file") or die "Cannot put $file ", $ftp->message;
	$ftp->quit;
}

# Read a file
sub view($) {
	my $file = shift;

	my $ftp = get_ftp();
	$ftp->get("$file") or die "Cannot read $file ", $ftp->message;
        if(-e $file) { print read_file($file); }
	$ftp->quit;
}

if($options{'action'} eq 'list') {
	list();
} elsif($options{'action'} eq 'upload') {
	upload($options{'file'});
} elsif($options{'action'} eq 'view') {
	view($options{'file'});
} else {
	print_usage();
}

更多关于设计和安全

这个薄薄的包装可以扩展以执行更多任务,例如检查允许或不允许的名称模式,或者根据上传者或文件名称的前缀整理文件。记住,这只是在客户端!如果你想得到真正的保障,你最好在服务器端也实施一些保护措施。但是,目标不是在这里讨论安全,而是要玩FTP!我希望你已经和我一起愉快地游览了Net::FTP

标签

Thibault Duponchelle

Thibault Duponchelle是一名软件开发者。主要对GNU/Linux、开源、Perl、C和汇编感兴趣。

浏览他们的文章

反馈

这篇文章有什么问题吗?请在GitHub上打开一个问题或pull request来帮助我们。