| |
【1】Apache FtpServerをインストールし、デフォルト(設定ファイルなし)で起動する
Windows11のPCにApache FtpServerをインストールしてFTP/FTPSの送受信の実験をしてみます。
-
公式ホームページの
ダウンロード
からzipをダウンロードして展開します、
-
展開してできたディレクトリを任意の場所に配置します。
ここでは
C:\Wintools\apache-ftpserver-1.2.0
に配置したとします。
-
コマンドプロンプトから
cd C:\Wintools\apache-ftpserver-1.2.0\bin
ftpd.bat
|
でデフォルト設定でFTPサーバが起動します。ポートは21です。
【2】設定ファイルtypicalをそのまま使って起動する
設定ファイルで動作を変更できます。サンプルとして用意されている ..\res\conf\ftpd-typical.xml を指定してみます。
C:\Wintools\apache-ftpserver-1.2.0\bin\ftpd.bat C:\Wintools\apache-ftpserver-1.2.0\res\conf\ftpd-typical.xml
|
このファイルの中に書かれている通り、ポートは2121に変わりました。
また、users.properties でユーザーを追加できます。
最初から定義されている「admin」ユーザーのパスワードはMD5ハッシュで書かれていますが、
ガイドにも、
そのファイルのコメントにも書かれている通り、PWも「admin」です。
curlを使って接続を確認してみましょう。
curl -u admin:admin ftp://localhost:2121/
------
-rw------- 1 user group 28 Mar 7 2022 README.txt
------
|
ファイルが表示されました。
このadminユーザーのホームはどこなのか?というと、users.properties の定義通り、res\home が表示されています。
同様にftpコマンドで接続もできます。
ftp
ftp>open localhost 2121
|
と対話型でポートを指定すればok。
【3】自分の自己証明書(オレオレ証明書)を作ってFTPSサーバとして起動する
今時廃れかけた単なるFTPサーバをPC上に起動するだけでは何にもなりません。
いよいよFTPSサーバとして使う時です。
まずはSSLサーバ証明書のキーストアファイルを作ります。
cd C:\Wintools\apache-ftpserver-1.2.0\res
keytool -genkey -keyalg RSA -storepass ftpserverpw -keystore myftpserver.jks
|
keytool実行時、実験するだけなら内容は適当でも構いませんが、
「よろしいですか」に「Y」と入力すること。yesは駄目なので注意。
作成できたら、次にftpd-typical.xmlを複製して、自分専用の設定ファイルを作ります。
copy ftpd-typical.xml ftpd-ftps-sample.xml
|
内容を編集します。下記の部分に、keytoolで作ったキーストアファイルの名前とPWを指定します。
<listeners>
<nio-listener name="default" port="2121" implicit-ssl="false">
<ssl>
<keystore file="./res/myftpserver.jks" password="ftpserverpw" />
</ssl>
</nio-listener>
</listeners>
|
このモード(explicit)では、クライアントはFTPでもFTPSでも接続できます。
クライアントがFTPSに対応していればID/PWの送信が暗号化され、非対応ならば平文で通信されます。
ではサーバを起動します。
C:\Wintools\apache-ftpserver-1.2.0\bin\ftpd.bat C:\Wintools\apache-ftpserver-1.2.0\res\conf\ftpd-ftps-sample.xml
|
サーバが起動できたら先ほど同様、curlコマンドで接続してみます。
curl --ssl-reqd -k -v -u admin:admin ftp://localhost:2121/
|
-kは正式な証明書でなくてもOKとみなす意味です。
また-vで詳しい出力を出すと、
> AUTH SSL
< 234 Command AUTH okay; starting SSL connection.
|
といった文字が出るので、確かにFTPSで接続しているのが確認できます。
【4】FTPSを強制する(implicitモード)
explicitモードでは、クライアント側が暗号化有無を選択できます。
そうではなくて、サーバー側でSSL暗号化を強制するには、implicitモードにします。
<listeners>
<nio-listener name="default" port="2121" implicit-ssl="true">
<ssl>
<keystore file="./res/myftpserver.jks" password="ftpserverpw" />
</ssl>
</nio-listener>
</listeners>
|
変更点は implicit-ssl="true" の部分だけです。
ではまた、先ほど同様、curlコマンドで接続してみます。
implicitの場合、curlで接続するときにftp://の代わりにftps://とします。
curl --ssl-reqd -k -v -u admin:admin ftps://localhost:2121/
|
implicitモードの場合、SSL非対応では接続できないので、接続できればFTPS通信していると即分かります。
【5】PythonでFTPSクライアントを作る
まず簡単な方。explicitで接続するサンプルプログラムです。
from ftplib import FTP_TLS
# explicitモードの場合(Aapche FtpServerではimplicit-ssl=falseの場合)
ftps = FTP_TLS()
ftps.connect('localhost', 2121) # Apache FtpServer
ftps.login('admin', 'admin')
ftps.prot_p() # 暗号化を有効化
# ファイルの送信
#with open('example.txt', 'rb') as f:
# ftps.storbinary('STOR example.txt', f)
# ファイルの受信
with open('README.txt', 'wb') as fp:
ftps.retrbinary('RETR README.txt', fp.write)
ftps.quit()
|
次にimplicitで接続するサンプルプログラムです。
import ftplib
import ssl
class ImplicitFTP_TLS(ftplib.FTP_TLS):
"""FTP_TLS subclass that automatically wraps sockets in SSL to support implicit FTPS."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._sock = None
@property
def sock(self):
"""Return the socket."""
return self._sock
@sock.setter
def sock(self, value):
"""When modifying the socket, ensure that it is ssl wrapped."""
if value is not None and not isinstance(value, ssl.SSLSocket):
value = self.context.wrap_socket(value)
self._sock = value
ftps = ImplicitFTP_TLS()
ftps.connect('localhost', 2121) # Apache FtpServer
ftps.login('admin', 'admin')
ftps.prot_p() # 暗号化を有効化
# ファイルの送信
#with open('example.txt', 'rb') as f:
# ftps.storbinary('STOR example.txt', f)
# ファイルの受信
with open('README.txt', 'wb') as fp:
ftps.retrbinary('RETR README.txt', fp.write)
ftps.quit()
|
【6】JavaでFTPSクライアントを作る
JavaでFTPSクライアントを作るにはApache Commons Netを使うのが簡単です。
最も簡単な、ログインしてルートディレクトリをリストするだけのサンプルプログラムです。
import java.io.IOException;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPSClient;
public class FtpsDirListExample {
public static void main(String[] args) {
String server = "localhost";
int port = 2121;
String username = "admin";
String password = "admin";
// Implicitモードなら真、Explicitモードなら偽を指定する
FTPSClient client = new FTPSClient("TLS", false);
//FTPSClient client = new FTPSClient(false);
// 日本語のファイル名を扱うための設定
client.setControlEncoding("UTF-8");
client.setAutodetectUTF8(true);
try {
client.connect(server, port);
System.out.println("Connected to " + server);
// TLS/SSL暗号化通信を設定
client.execPBSZ(0);
client.execPROT("P");
client.setFileType(FTP.BINARY_FILE_TYPE);
// local/remoteでactive/passiveがある
//client.enterLocalPassiveMode();
if (client.login(username, password)) {
System.out.println("Login successful!");
FTPFile[] files = client.listFiles("/");
System.out.println("Directory listing for '/':");
for (FTPFile file : files) {
System.out.printf("%s\t%s\t%d bytes%n",
file.getName(),
file.isDirectory() ? "<DIR>" : "<FILE>",
file.getSize());
}
client.logout();
} else {
System.out.println("Login failed!");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (client.isConnected()) {
client.disconnect();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
|
Commons Netはできるだけ新しいバージョンを使うのが良いです。
私が試すとバージョン3.7では接続が固まり、バージョン3.11.1 (2025年1月26日時点最新) では動作しました。
FTPSClientのコンストラクタのboolean引数で、Implicit(真)かExplicit(偽)をサーバに合わせて設定します。
ここを間違えると、
Implicitのサーバに偽で接続を試みる…応答がなく固まる
Explicitのサーバに真で接続を試みる…javax.net.ssl.SSLException: Unsupported or unrecognized SSL message
となります。
またApache FtpServerでうまく動作して、他のサーバに接続するときは、以下の注意点があります。
-
まず、Explicit/Implicitの設定に合わせてポートの調整が必要です。
Apache FtpServer以外のFTPSサーバでは、Explicitの場合はポート21を、Implicitでは990を使うのが一般的です。
-
IIS等のFTPSサーバではLISTコマンドの禁止を設定できます。
client.listFiles()メソッドは裏でFTPのLISTコマンドを実行していますが、禁止されていると当然エラーが発生します。
LISTが使えない場合、NLSTコマンドが使えるなら client.listNames() で、
MLSDコマンドが使えるなら client.sendCommand("MLSD") で一部を代替できるかもしれません。
サーバのセキュリティ設定に合わせて調整が必要です。
なお、lftpコマンドのlsでは、これらのLIST、NLST、MLSDの利用可否を、
lftp自身が内部的に判定して可能な方法を使い分けているようです。
従って「lftpでディレクトリ内容が見れるのに、Javaで接続するとlistFiles()がエラーになる」
などは普通に起きうると思われます。
サーバのセキュリティ方針をよく聞いて、混乱しないようにしないといけませんね。

(first uploaded 2025/01/02 last updated 2025/01/26, URANO398)
|