代理服务器如何为 FTP 客户端提供服务?






4.80/5 (23投票s)
本文描述了代理服务器如何处理 PORT 和 PASV FTP 命令
简介
在本文中,我将讨论通过代理服务器连接 FTP 客户端。因此,本文的读者应该具备以下主题的背景知识:
实际上,FTP 客户端可以直接连接到 FTP 服务器,并通过直接套接字连接来传输和接收文件或数据。但在某些情况下,需要为 FTP 客户端提供安全保障,并且大多数互联网客户端都从局域网访问互联网,该局域网通过支持 NAT(网络地址转换)的防火墙连接到互联网,或者通过代理服务器实现完全的局域网隔离。
背景
FTP 服务器在端口 21 上监听客户端请求,因此任何想要与任何服务器通信的客户端只需连接到服务器,发送其身份验证数据以进行身份验证,然后提出请求,所有这些都在 TCP 连接中进行,如图 1 所示。
服务器和客户端之间的连接保持打开状态,以交换 FTP 命令。如果客户端想要获取数据,它可以通过两种方式请求:
- 客户端使用 PORT 命令指定一个非默认的客户端数据端口。
client: PORT h1,h2,h3,h4,p1,p2 server: 200 Command okay. // where h1 is the high order 8 bits of the internet host address.
然后,服务器通过连接到该主机地址发送数据,然后关闭连接。 - 客户端使用 PASV 命令请求服务器端标识一个非默认的服务器端数据端口。
client: PASV server: 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). // where h1 is the high order 8 bits of the internet host address.
然后,客户端连接到该主机地址,并在该通道上接收其对数据请求的回复,然后服务器关闭连接。
代理服务器在 FTP 客户端和 FTP 服务器之间的角色
用户连接/身份验证
首先,我将描述用户连接/身份验证过程以及各种代理如何实现请求。当 FTP 客户端连接到 FTP 服务器时,它只需解析 FTP 服务器地址并连接到端口 21(默认 FTP 端口),FTP 服务器会回复一条欢迎消息,告知 FTP 客户端它已准备好接收用户身份验证信息。但对于代理服务器呢?FTP 客户端如何告诉代理服务器它想要连接的 FTP 服务器的地址?答案是,可以通过多种方式实现,正如您从以下来自 CuteFTP 客户端防火墙设置的图中所见。
我们将逐一介绍
- 通用:不使用代理,直接连接。
- SITE 站点
client: // connect to proxy server proxy: 220 FTP Virtual Server client: USER proxy: 331 send password. client: PASS proxy: 230 Login OK. Proceed. client: site ftp.cuteftp.com proxy: // connect to ftp.cuteftp.com server: 220 GlobalSCAPE Secure FTP Server (v. 2.0) proxy: USER anonymous server: 331 Password required for anonymous. proxy: PASS ******** server: 230 Login OK. Proceed.
正如您所见,客户端首先向代理发送身份验证信息,代理将其保存起来,直到它收到
SITE
命令中的 FTP 服务器名称(例如 site ftp.cuteftp.com),然后代理连接 FTP 服务器并将登录信息重新发送给 FTP 服务器。 - USER 用户@站点
client: // connect to proxy server proxy: 220 FTP Virtual Server client: USER anonymous@ftp.cuteftp.com proxy: // connect to ftp.cuteftp.com server: 220 GlobalSCAPE Secure FTP Server (v. 2.0) proxy: USER anonymous server: 331 Password required for anonymous. client: PASS ******** server: 230 Login OK. Proceed.
正如您所见,客户端在
USER
命令中发送用户名和 FTP 服务器(例如 USER anonymous@ftp.cuteftp.com),然后代理连接 FTP 服务器并将用户名发送给 FTP 服务器,对话继续在客户端和服务器之间进行。 - 带登录的 USER
client: // connect to proxy server proxy: 220 FTP Virtual Server client: USER proxy: 331 send password. client: PASS ******** proxy: 230 Login OK. Proceed. client: USER anonymous@ftp.cuteftp.com proxy: // connect to ftp.cuteftp.com server: 220 GlobalSCAPE Secure FTP Server (v. 2.0) proxy: USER anonymous server: 331 Password required for anonymous. proxy: PASS ******** server: 230 Login OK. Proceed.
正如您所见,客户端首先向代理发送身份验证信息,代理将其保存起来,直到它收到
USER
命令中的 FTP 服务器名称(例如 USER anonymous@ftp.cuteftp.com),然后代理连接 FTP 服务器并将登录信息重新发送给 FTP 服务器。 - USER/PASS/ACCT
client: // connect to proxy server proxy: 220 FTP Virtual Server client: USER proxy: 331 send password. client: PASS proxy: 230 Login OK. Proceed. client: open ftp.cuteftp.com proxy: // connect to ftp.cuteftp.com server: 220 GlobalSCAPE Secure FTP Server (v. 2.0) proxy: USER anonymous server: 331 Password required for anonymous. proxy: PASS ******** server: 230 Login OK. Proceed.
正如您所见,客户端首先向代理发送身份验证信息,代理将其保存起来,直到它收到
open
命令中的 FTP 服务器名称(例如 open ftp.cuteftp.com),然后代理连接 FTP 服务器并将登录信息重新发送给 FTP 服务器。 - OPEN 站点
client: // connect to proxy server proxy: 220 FTP Virtual Server client: USER anonymous@ftp.cuteftp.com proxy: // connect to ftp.cuteftp.com server: 220 GlobalSCAPE Secure FTP Server (v. 2.0) proxy: USER anonymous server: 331 Password required for anonymous. proxy: PASS ******** server: 230 Login OK. Proceed.
正如您所见,客户端首先向代理发送身份验证信息,代理将其保存起来,直到它收到
open
命令(例如 open ftp.cuteftp.com)或USER
命令(例如 USER anonymous@ftp.cuteftp.com)中的 FTP 服务器名称,然后代理连接 FTP 服务器并将登录信息重新发送给 FTP 服务器。
您可以单独使用 FTP 客户端测试每种情况,也许您的 FTP 客户端有不同的设置,您可以查看处理所有这些情况的代码。
send(SocketClient, "220 FTP Virtual Server\r\n", 24, 0); pRequest->nPort = 21; CString strPass, strWelcome; bool bSendWelcome = true; while(true) { pRequest->nLength = recv(SocketClient, str.GetBuffer(1024), 1024, 0); str.ReleaseBuffer(pRequest->nLength); if(memicmp(str, "USER", 4) == 0) { if((nIndex = str.Find("@")) != -1) { bSendWelcome = false; pRequest->strHost = str.Mid(nIndex+1); pRequest->strHost.TrimRight(); HostPort(pRequest->strHost, pRequest->nPort); pRequest->strRequest = str.Left(nIndex)+"\r\n"; break; } else { bSendWelcome = true; pRequest->strRequest = str; str.TrimRight(); if(str.GetLength() == 4) pRequest->strRequest.Insert(5, "anonymous"); // if host still not found then ask for login password send(SocketClient, "331 send password.\r\n", 20, 0); } } else if(memicmp(str, "SITE", 4) == 0 || memicmp(str, "OPEN", 4) == 0) { if((nIndex = str.Find(" ")) == -1) goto END_REQUEST; pRequest->strHost = str.Mid(nIndex+1); pRequest->strHost.TrimRight(); HostPort(pRequest->strHost, pRequest->nPort); break; } else if(memicmp(str, "PASS", 4) == 0) { strPass = str; send(SocketClient, "230 Login OK. Proceed.\r\n", 24, 0); } else if(pRequest->strHost != "") break; } // connect to ftp server if(ConnectHost(SocketHost, pRequest) == false) goto END_REQUEST; if(pRequest->strRequest.IsEmpty() == false) { // Receive ready command nIndex = RequestRecv(pRequest, SocketHost, strWelcome.GetBuffer(1024), 1024); strWelcome.ReleaseBuffer(nIndex); // send USER command RequestSend(pRequest, SocketHost, pRequest->strRequest.GetBuffer(0), pRequest->strRequest.GetLength()); if(strPass.IsEmpty() == false) { // Receive reply of USER command nIndex = RequestRecv(pRequest, SocketHost, str.GetBuffer(1024), 1024); str.ReleaseBuffer(nIndex); // send password RequestSend(pRequest, SocketHost, strPass.GetBuffer(0), strPass.GetLength()); if(bSendWelcome) { // Receive reply of PASS command RequestRecv(pRequest, SocketHost, str.GetBuffer(1024), 1024); // send welcome message to client send(SocketClient, strWelcome.GetBuffer(0), strWelcome.GetLength(), 0); } } }
PASV 和 PORT 命令处理
任何代理服务器至少应该有一个外部地址(WAN 地址)和一个内部地址(LAN 地址),如图 2 所示。如果客户端通过代理服务器连接,那么它就有一个 LAN 地址(内部地址),这意味着它无法直接连接到互联网。如果 FTP 客户端发送 PORT
命令,它将指定其 LAN 地址或其他 LAN 地址来接收服务器连接,但服务器无法绕过代理服务器访问 LAN 地址。PASV 情况也是如此,因为 LAN 客户端需要连接到服务器端的外部地址来打开数据通道,但它也无法绕过代理服务器。这里就体现了代理服务器替换这些地址的作用,就像 NAT 一样,我将在两种情况下进行描述。
- 在
PORT
命令情况下- 客户端发送带有 LAN 地址和端口的
PORT
命令。 - 代理服务器检测到
PORT
命令,并用其 WAN 地址替换 LAN 地址,然后将该命令转发给 FTP 服务器。 - 代理服务器在接收到的端口创建一个监听套接字,并创建两个线程:
- 一个用于从 FTP 服务器接收数据并发送给 FTP 客户端。
- 另一个用于从 FTP 客户端接收数据并发送给 FTP 服务器。
- FTP 服务器收到
PORT
命令,并连接到命令地址(代理服务器 WAN 地址),然后发送其数据包。 - 代理服务器接收数据并将其传输给 FTP 客户端。
每次客户端发出
PORT
命令时,都会重复此场景,如图 2 所示。 - 客户端发送带有 LAN 地址和端口的
- 在 PASV 命令情况下
- 客户端发送 PASV 命令。
- 代理服务器将 PASV 命令发送给 FTP 服务器。
- 代理服务器检测到回复,并用其 LAN 地址替换 FTP 服务器的 WAN 地址,然后将回复转发给 FTP 客户端。
- 代理服务器在接收到的端口创建一个监听套接字,并创建两个线程:
- 一个用于从 FTP 服务器接收数据并发送给 FTP 客户端。
- 另一个用于从 FTP 客户端接收数据并发送给 FTP 服务器。
- FTP 客户端收到回复,并连接到回复地址(代理 LAN 地址)(FTP 客户端和代理之间的通道 1)。
- 代理服务器连接到 FTP 服务器上创建的监听套接字(代理和 FTP 服务器之间的通道 2)。
- FTP 服务器通过通道 2 发送数据给代理服务器,代理服务器再通过通道 1 发送给 FTP 客户端。
每次客户端发出 PASV 命令时,都会重复此场景,如图 3 所示。
兴趣点
- 此演示是支持以下功能的大型代理服务器项目的一部分:
- 互联网访问,并为所有局域网用户提供访问控制。
- 根据页面内容和用户定义的规则自动进行站点过滤。
- 协议:HTTP、FTP、SOCKS4、SOCKS5、SMTP、POP3、DNS。
- 完整的请求日志文件和用户访问可视化日志。
- 我没有详细描述本文附带的代码,因为您不需要重用它,我只是想描述一下思路。如果任何人需要更多细节,可以通过邮件与我联系,或者发送需要描述的代码部分。
- 如果您想尝试此演示,应将您的 FTP 客户端设置为使用代理服务器,并配置代理地址和端口(21),不要忘记勾选“PASV 模式”复选框。
- 不要尝试在同一台机器上使用带有
PORT
命令的 FTP 客户端测试此演示,因为 FTP 客户端将在命令地址(命令发送的地址)上创建一个监听套接字,而代理会将命令地址替换为其机器地址(即同一地址),从而无法监听同一个端口。因此,您只能测试 PASV 命令。 - 如果您想了解代理服务器,可以查阅 RFC 1919。
参考文献
- RFC 959 文件传输协议。J. Postel, J.K. Reynolds. 1985 年 10 月 1 日。
- RFC 1579 方便防火墙的 FTP。S. Bellovin. 1994 年 2 月。
鸣谢
- 我非常感谢我的同事在实施和测试这项工作过程中提供的帮助。(JAK)