Apache Commons FTP 被动模式,如何设置远程监听端口(数据流)
Apache Commons FTP Passive mode, how to set remote listening port (data stream)
我尝试使用 apache-commons-net-3.7.2 连接 FTP 服务器(隐式 TLS,使用客户端证书 + login/password 进行双因素身份验证)。
我可以验证自己,进入被动模式,但是客户端为了通过数据套接字获取数据,没有连接到服务器成功。
我可以在同一台计算机上使用 WinSCP(相同设置)连接自己。我已经激活 WinSCP 日志以查看协议详细信息,并且我已经使用相同的选项调整了我的源代码。我可以使用 ProtocolCommandListener
验证我的协议是否正常。我知道需要被动模式,因为 WinSCP 发出 PASV
命令。
我可以看到 WinSCP 连接到端口 62564 上的数据套接字(我已将 FTP IP 地址替换为 XXX)
2021-01-06 10:25:35.575 227 Entering Passive Mode (192,168,4,122,244,100).
2021-01-06 10:25:35.575 Server sent passive reply with unroutable address 192.168.4.122, using host address instead.
2021-01-06 10:25:35.575 MLSD
2021-01-06 10:25:35.575 Connexion à 83.XXX.XXX.XXX:62564...
2021-01-06 10:25:35.604 150 Transferring directory
我还看到服务器为 PASV
命令发送的回复不包括要连接的端口。
public class TestApi {
public static void _parseExtendedPassiveModeReply(String reply)
{
reply = reply.substring(reply.indexOf('(') + 1,
reply.indexOf(')')).trim();
char delim1, delim2, delim3, delim4;
delim1 = reply.charAt(0);
delim2 = reply.charAt(1);
delim3 = reply.charAt(2);
delim4 = reply.charAt(reply.length()-1);
if (!(delim1 == delim2) || !(delim2 == delim3)
|| !(delim3 == delim4)) {
System.out.println("Could not parse extended passive host information.\nServer Reply: " + reply);
}
int port;
try
{
port = Integer.parseInt(reply.substring(3, reply.length()-1));
}
catch (NumberFormatException e)
{
System.out.println("Could not parse extended passive host information.\nServer Reply: " + reply);
}
}
public static void main(String[] args) throws SocketException, IOException, GeneralSecurityException {
String hostname = args[0];
int port = Integer.parseInt(args[1]);
String login = args[2];
String pwd = args[3];
FTPSClient client = new FTPSClient("TLS",true);
File clientCertStore = new File("myJCEKS keystore");
KeyManager keyManager = KeyManagerUtils.createClientKeyManager("JCEKS",clientCertStore,"","myalias","");
client.setKeyManager(keyManager);
client.connect(hostname, port);
int reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
client.disconnect();
System.err.println("FTP server refused connection.");
System.exit(1);
} else {
if (client.login(login, pwd)) {
client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.err), true));
client.sendCommand("OPTS","UTF8 ON");
client.sendCommand("PBSZ","0");
client.sendCommand("PROT","P");
int retour = client.pasv();
System.out.println(retour);
_parseExtendedPassiveModeReply(client.getReplyString());
System.out.println(client.printWorkingDirectory());
reply = client.getReplyCode();
System.out.println(reply);
System.out.println(client.listHelp());
//it freezes here, after sending MLDS command
//same thing using regular api for listing files (WinSCP use MLSD while regular api uses LIST)
client.sendCommand("MLSD");
//and so on
System.out.println("LOGOUT");
client.logout();
} else {
System.out.println("echec login");
}
}
}
}
我想 API 不知道必须在哪个端口上发送数据请求并使用默认值,这是不正确的。不知道WinSCP是怎么算出62564端口号的。
OPTS UTF8 ON
200 Command OPTS succeed
PBSZ 0
200 PBSZ=0
PROT P
200 PRIVATE data channel protection level set
PASV
227 Entering Passive Mode (192,168,4,122,245,74).
227
PWD
Could not parse extended passive host information.
Server Reply: 192,168,4,122,245,74
Could not parse extended passive host information.
Server Reply: 192,168,4,122,245,74
257 "/" is current directory
/
257
HELP
214-The following commands are implemented
ABOR ACCT ALLO* APPE CDUP CWD DELE FEAT+ HELP
HOST+ LANG+ LIST MDTM+ MLST+ MKD MODE NLST NOOP
OPTS+ PASS PASV PORT PWD QUIT REIN REST RETR
RMD RNFR RNTO SITE SIZE SMNT STAT STOR STOU
STRU* SYST TYPE USER XCUP XCRC XCWD XMD5 XMKD
XPWD XRMD XSIGN XSHA1 XSHA256 XSHA512 XQUOTA
214 Help complete
214-The following commands are implemented
ABOR ACCT ALLO* APPE CDUP CWD DELE FEAT+ HELP
HOST+ LANG+ LIST MDTM+ MLST+ MKD MODE NLST NOOP
OPTS+ PASS PASV PORT PWD QUIT REIN REST RETR
RMD RNFR RNTO SITE SIZE SMNT STAT STOR STOU
STRU* SYST TYPE USER XCUP XCRC XCWD XMD5 XMKD
XPWD XRMD XSIGN XSHA1 XSHA256 XSHA512 XQUOTA
214 Help complete
MLSD
在 API 文档、源代码、FTP RFC 中搜索了几个小时后,我不知道该怎么做。
你的假设是错误的。您没有设置端口。服务器告诉你要连接到哪个端口。
对于 WinSCP:
2021-01-06 10:25:35.575 227 Entering Passive Mode (192,168,4,122,244,100).
...
2021-01-06 10:25:35.575 Connexion à 83.XXX.XXX.XXX:62564...
其中 62564 = (244 << 8) + 100
见RFC 959, section 4.1.2. Transfer parameter commands, Page 28。
PASV
响应解析失败,因为您使用了错误的代码。 _parseExtendedPassiveModeReply
用于 EPSV
。对于 PASV
,使用 _parsePassiveModeReply
。在那里您还将看到上述公式的实现:
int oct1 = Integer.parseInt(m.group(2));
int oct2 = Integer.parseInt(m.group(3));
__passivePort = (oct1 << 8) | oct2;
我尝试使用 apache-commons-net-3.7.2 连接 FTP 服务器(隐式 TLS,使用客户端证书 + login/password 进行双因素身份验证)。
我可以验证自己,进入被动模式,但是客户端为了通过数据套接字获取数据,没有连接到服务器成功。
我可以在同一台计算机上使用 WinSCP(相同设置)连接自己。我已经激活 WinSCP 日志以查看协议详细信息,并且我已经使用相同的选项调整了我的源代码。我可以使用 ProtocolCommandListener
验证我的协议是否正常。我知道需要被动模式,因为 WinSCP 发出 PASV
命令。
我可以看到 WinSCP 连接到端口 62564 上的数据套接字(我已将 FTP IP 地址替换为 XXX)
2021-01-06 10:25:35.575 227 Entering Passive Mode (192,168,4,122,244,100).
2021-01-06 10:25:35.575 Server sent passive reply with unroutable address 192.168.4.122, using host address instead.
2021-01-06 10:25:35.575 MLSD
2021-01-06 10:25:35.575 Connexion à 83.XXX.XXX.XXX:62564...
2021-01-06 10:25:35.604 150 Transferring directory
我还看到服务器为 PASV
命令发送的回复不包括要连接的端口。
public class TestApi {
public static void _parseExtendedPassiveModeReply(String reply)
{
reply = reply.substring(reply.indexOf('(') + 1,
reply.indexOf(')')).trim();
char delim1, delim2, delim3, delim4;
delim1 = reply.charAt(0);
delim2 = reply.charAt(1);
delim3 = reply.charAt(2);
delim4 = reply.charAt(reply.length()-1);
if (!(delim1 == delim2) || !(delim2 == delim3)
|| !(delim3 == delim4)) {
System.out.println("Could not parse extended passive host information.\nServer Reply: " + reply);
}
int port;
try
{
port = Integer.parseInt(reply.substring(3, reply.length()-1));
}
catch (NumberFormatException e)
{
System.out.println("Could not parse extended passive host information.\nServer Reply: " + reply);
}
}
public static void main(String[] args) throws SocketException, IOException, GeneralSecurityException {
String hostname = args[0];
int port = Integer.parseInt(args[1]);
String login = args[2];
String pwd = args[3];
FTPSClient client = new FTPSClient("TLS",true);
File clientCertStore = new File("myJCEKS keystore");
KeyManager keyManager = KeyManagerUtils.createClientKeyManager("JCEKS",clientCertStore,"","myalias","");
client.setKeyManager(keyManager);
client.connect(hostname, port);
int reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
client.disconnect();
System.err.println("FTP server refused connection.");
System.exit(1);
} else {
if (client.login(login, pwd)) {
client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.err), true));
client.sendCommand("OPTS","UTF8 ON");
client.sendCommand("PBSZ","0");
client.sendCommand("PROT","P");
int retour = client.pasv();
System.out.println(retour);
_parseExtendedPassiveModeReply(client.getReplyString());
System.out.println(client.printWorkingDirectory());
reply = client.getReplyCode();
System.out.println(reply);
System.out.println(client.listHelp());
//it freezes here, after sending MLDS command
//same thing using regular api for listing files (WinSCP use MLSD while regular api uses LIST)
client.sendCommand("MLSD");
//and so on
System.out.println("LOGOUT");
client.logout();
} else {
System.out.println("echec login");
}
}
}
}
我想 API 不知道必须在哪个端口上发送数据请求并使用默认值,这是不正确的。不知道WinSCP是怎么算出62564端口号的。
OPTS UTF8 ON
200 Command OPTS succeed
PBSZ 0
200 PBSZ=0
PROT P
200 PRIVATE data channel protection level set
PASV
227 Entering Passive Mode (192,168,4,122,245,74).
227
PWD
Could not parse extended passive host information.
Server Reply: 192,168,4,122,245,74
Could not parse extended passive host information.
Server Reply: 192,168,4,122,245,74
257 "/" is current directory
/
257
HELP
214-The following commands are implemented
ABOR ACCT ALLO* APPE CDUP CWD DELE FEAT+ HELP
HOST+ LANG+ LIST MDTM+ MLST+ MKD MODE NLST NOOP
OPTS+ PASS PASV PORT PWD QUIT REIN REST RETR
RMD RNFR RNTO SITE SIZE SMNT STAT STOR STOU
STRU* SYST TYPE USER XCUP XCRC XCWD XMD5 XMKD
XPWD XRMD XSIGN XSHA1 XSHA256 XSHA512 XQUOTA
214 Help complete
214-The following commands are implemented
ABOR ACCT ALLO* APPE CDUP CWD DELE FEAT+ HELP
HOST+ LANG+ LIST MDTM+ MLST+ MKD MODE NLST NOOP
OPTS+ PASS PASV PORT PWD QUIT REIN REST RETR
RMD RNFR RNTO SITE SIZE SMNT STAT STOR STOU
STRU* SYST TYPE USER XCUP XCRC XCWD XMD5 XMKD
XPWD XRMD XSIGN XSHA1 XSHA256 XSHA512 XQUOTA
214 Help complete
MLSD
在 API 文档、源代码、FTP RFC 中搜索了几个小时后,我不知道该怎么做。
你的假设是错误的。您没有设置端口。服务器告诉你要连接到哪个端口。
对于 WinSCP:
2021-01-06 10:25:35.575 227 Entering Passive Mode (192,168,4,122,244,100).
...
2021-01-06 10:25:35.575 Connexion à 83.XXX.XXX.XXX:62564...
其中 62564 = (244 << 8) + 100
见RFC 959, section 4.1.2. Transfer parameter commands, Page 28。
PASV
响应解析失败,因为您使用了错误的代码。 _parseExtendedPassiveModeReply
用于 EPSV
。对于 PASV
,使用 _parsePassiveModeReply
。在那里您还将看到上述公式的实现:
int oct1 = Integer.parseInt(m.group(2));
int oct2 = Integer.parseInt(m.group(3));
__passivePort = (oct1 << 8) | oct2;