65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (23投票s)

2005 年 2 月 10 日

CPOL

7分钟阅读

viewsIcon

217472

downloadIcon

5420

本文描述了代理服务器如何处理 PORT 和 PASV FTP 命令

Sample Image - ProxyFtp.gif

简介

在本文中,我将讨论通过代理服务器连接 FTP 客户端。因此,本文的读者应该具备以下主题的背景知识:

  1. TCP/IP 协议和套接字编程。
  2. 文件传输协议 (FTP) - RFC 959
  3. 代理服务器功能 - RFC 1919

实际上,FTP 客户端可以直接连接到 FTP 服务器,并通过直接套接字连接来传输和接收文件或数据。但在某些情况下,需要为 FTP 客户端提供安全保障,并且大多数互联网客户端都从局域网访问互联网,该局域网通过支持 NAT(网络地址转换)的防火墙连接到互联网,或者通过代理服务器实现完全的局域网隔离。

背景

FTP 服务器在端口 21 上监听客户端请求,因此任何想要与任何服务器通信的客户端只需连接到服务器,发送其身份验证数据以进行身份验证,然后提出请求,所有这些都在 TCP 连接中进行,如图 1 所示。

服务器和客户端之间的连接保持打开状态,以交换 FTP 命令。如果客户端想要获取数据,它可以通过两种方式请求:

  1. 客户端使用 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.
    然后,服务器通过连接到该主机地址发送数据,然后关闭连接。
  2. 客户端使用 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 客户端防火墙设置的图中所见。

我们将逐一介绍

  1. 通用:不使用代理,直接连接。
  2. 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 服务器。

  3. 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 服务器,对话继续在客户端和服务器之间进行。

  4. 带登录的 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 服务器。

  5. 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 服务器。

  6. 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 一样,我将在两种情况下进行描述。

  1. PORT 命令情况下
    • 客户端发送带有 LAN 地址和端口的 PORT 命令。
    • 代理服务器检测到 PORT 命令,并用其 WAN 地址替换 LAN 地址,然后将该命令转发给 FTP 服务器。
    • 代理服务器在接收到的端口创建一个监听套接字,并创建两个线程:
      • 一个用于从 FTP 服务器接收数据并发送给 FTP 客户端。
      • 另一个用于从 FTP 客户端接收数据并发送给 FTP 服务器。
    • FTP 服务器收到 PORT 命令,并连接到命令地址(代理服务器 WAN 地址),然后发送其数据包。
    • 代理服务器接收数据并将其传输给 FTP 客户端。

    每次客户端发出 PORT 命令时,都会重复此场景,如图 2 所示。

  2. 在 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 所示。

兴趣点

  1. 此演示是支持以下功能的大型代理服务器项目的一部分:
    • 互联网访问,并为所有局域网用户提供访问控制。
    • 根据页面内容和用户定义的规则自动进行站点过滤。
    • 协议:HTTP、FTP、SOCKS4、SOCKS5、SMTP、POP3、DNS。
    • 完整的请求日志文件和用户访问可视化日志。
    我仅包含处理 FTP 部分的代码。
  2. 我没有详细描述本文附带的代码,因为您不需要重用它,我只是想描述一下思路。如果任何人需要更多细节,可以通过邮件与我联系,或者发送需要描述的代码部分。
  3. 如果您想尝试此演示,应将您的 FTP 客户端设置为使用代理服务器,并配置代理地址和端口(21),不要忘记勾选“PASV 模式”复选框。
  4. 不要尝试在同一台机器上使用带有 PORT 命令的 FTP 客户端测试此演示,因为 FTP 客户端将在命令地址(命令发送的地址)上创建一个监听套接字,而代理会将命令地址替换为其机器地址(即同一地址),从而无法监听同一个端口。因此,您只能测试 PASV 命令。
  5. 如果您想了解代理服务器,可以查阅 RFC 1919

参考文献

  1. RFC 959 文件传输协议。J. Postel, J.K. Reynolds. 1985 年 10 月 1 日。
  2. RFC 1579 方便防火墙的 FTP。S. Bellovin. 1994 年 2 月。

鸣谢

  1. 我非常感谢我的同事在实施和测试这项工作过程中提供的帮助。(JAK)
© . All rights reserved.