FTP Proxy Server

本文将在Linux环境下实现一个简单的FTP代理服务器,主要内容涉及FTP主动/被动模式和简单的Socket编程。

1. 主动模式和被动模式

FTP有两种模式,即主动模式(Active Mode)和被动模式(Passive Mode),主要区别在谁在监听数据端口。

1.1 主动模式

FTP服务器在开启后一直在监听21号端口等待客户端通过任意端口进行连接,客户端通过任意端口port1连接服务器21号端口成功后,服务器通过该命令套接字发送各种FTP命令(CD、DIR、QUIT...)。当要发送的命令涉及到数据传输的时候,服务器和客户端间就要开启数据通道,如果此时客户端处于主动模式时,客户端开启并监听一个大于1024的随机端口port2,并通过命令套接字向服务器发送PORT命令通告客户端处于主动模式且正在监听port2。服务器在收到客户端的PORT命令后,使用端口20连接客户端port2(数据端口使用20只是个惯例,其实不适用影响不大),并完成数据传输。

PORT命令的格式为” PORT 223,3,123,41,99,165 “,指示客户端ip为223.3.123.41,客户端开启的随机端口port2 = 99 * 265 + 165 = 26400,在服务器返回200 PORT command successful之后,客户端才发送获取文件命令RETR。

1.2 主动模式的缺陷

从1.1中可以看出FTP在采取主动模式时,客户端需要主动监听一个大于1024的随机端口,而一般客户端的防火墙不会开放对这样一个端口的连接。

1.3 被动模式

为了给客户端带来方便,FTP被动模式下,客户端发送Request: Pasv命令,服务器在接收到命令后,主动开启一个大于1024的端口port并发送响应Response: 227 Entering Passive Mode(...),客户端主动发起连接到服务器的port,并完成数据传输。在被动模式下客户端不需要监听任何端口,因此在客户端存在某些防火墙规则的情况下会更加适合。

2. FTP代理服务器架构

1. FTP代理服务器监听套接字proxy_cmd_socket监听21号端口,当客户端连接时,得到accept_cmd_socket,proxy主连接服务器21号端口得到connect_cmd_socket,proxy转发accept_cmd_socket和connect_cmd_socket之间除了情况2和情况3的通信。

2. 当处于主动模式下客户通过accept_cmd_socket发送PORT命令时,proxy需要把PORT命令的ip换成proxy的外网ip(指server看到的proxy的ip),并随机监听一个大于1024的端口port1,把PORT命令中的端口port改为port1。

3. 当处于被动模式下服务器响应客户端的Response: 227....命令时,proxy需要把Response中的ip换成proxy的内网ip(指client看到的proxy的ip),并随机监听一个大于1024的端口port2,把Response中的端口port改为port2。

4. 当处于主动模式下,服务器收到proxy修改过的PORT命令,会主动连接proxy的端口port1得到accept_data_socket,proxy主动连接客户端的port端口得到proxy_connect_socket,proxy转发accept_data_socket_socket和proxy_connect_socket间的通信。

5. 当处于被动模式下,客户端收到proxy修改过的Response: 227...后,会连接proxy的端口port2得到accept_data_socket,proxy连接服务器的port端口得到proxy_connect_socket,proxy转发accept_data_socket_socket和proxy_connect_socket间的通信。

3. FTP代理服务器实现

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <sys/types.h>
  4 #include <unistd.h>
  5 #include <fcntl.h>
  6 #include <arpa/inet.h>
  7 #include <string.h>
  8 #include <ctype.h>
  9
 10
 11 #define TRUE 1
 12 #define FALSE 0
 13
 14 #define CMD_PORT 21
 15 #define BUFFSIZE 4096
 16 #define LISTENQ 5
 17
 18
 19 int acceptSocket(int socket,struct sockaddr *addr,socklen_t *addrlen);
 20 int connectToServerByAddr(struct sockaddr_in servaddr);
 21 int connectToServer(char *ip,unsigned short port);
 22 int bindAndListenSocket(unsigned short port);
 23 void splitCmd(char *buff, char **cmd,char **param);
 24 unsigned short getPortFromFtpParam(char *param);
 25 void getSockLocalIp(int fd,char *ipStr,int buffsize);
 26 unsigned short getSockLocalPort(int sockfd);
 27
 28 int main(int argc, const char *argv[])
 29 {
 30     int i;
 31     fd_set master_set, working_set;  //文件描述符集合
 32     struct timeval timeout;          //select 参数中的超时结构体
 33     int proxy_cmd_socket    = 0;     //proxy listen控制连接
 34     int accept_cmd_socket   = 0;     //proxy accept客户端请求的控制连接
 35     int connect_cmd_socket  = 0;     //proxy connect服务器建立控制连接
 36     int proxy_data_socket   = 0;     //proxy listen数据连接
 37     int accept_data_socket  = 0;     //proxy accept得到请求的数据连接(主动模式时accept得到服务器数据连接的请求,被动模式时accept得到客户端数据连接的请求)
 38     int connect_data_socket = 0;     //proxy connect建立数据连接 (主动模式时connect客户端建立数据连接,被动模式时connect服务器端建立数据连接)
 39     int selectResult = 0;     //select函数返回值
 40     int select_sd = 10;    //select 函数监听的最大文件描述符
 41     int pasv_mode = 1;
 42
 43     char serverProxyIp[BUFFSIZE];    //待获得
 44     char clientProxyIp[BUFFSIZE];    //待获得 serverProxyIp和clientProxyIp可能不一样
 45     char serverIp[BUFFSIZE];
 46
 47     unsigned short proxy_data_port;
 48     unsigned short data_port;
 49     socklen_t clilen;
 50     struct sockaddr_in cliaddr;
 51
 52     if(argc != 2){
 53         printf("usage : proxy server_ip\n example: proxy 121.121.121.121\n");
 54     }
 55     strcpy(serverIp,argv[1]);
 56
 57
 58     FD_ZERO(&master_set);   //清空master_set集合
 59     bzero(&timeout, sizeof(timeout));
 60
 61     proxy_cmd_socket = bindAndListenSocket(CMD_PORT);  //开启proxy_cmd_socket、bind()、listen操作
 62     FD_SET(proxy_cmd_socket, &master_set);  //将proxy_cmd_socket加入master_set集合
 63
 64     while (TRUE) {
 65         FD_ZERO(&working_set); //清空working_set文件描述符集合
 66         memcpy(&working_set, &master_set, sizeof(master_set)); //将master_set集合copy到working_set集合
 67         timeout.tv_sec = 60;    //Select的超时结束时间
 68         timeout.tv_usec = 0;    //ms
 69
 70         //select循环监听 这里只对读操作的变化进行监听(working_set为监视读操作描述符所建立的集合),第三和第四个参数的NULL代表不对写操作、和误操作进行监听
 71         selectResult = select(select_sd, &working_set, NULL, NULL, &timeout);
 72
 73         // fail
 74         if (selectResult < 0) {
 75             perror("select() failed\n");
 76             exit(1);
 77         }
 78
 79         // timeout
 80         if (selectResult == 0) {
 81             printf("select() timed out.\n");
 82             continue;
 83         }
 84
 85         // selectResult > 0 时 开启循环判断有变化的文件描述符为哪个socket
 86         for (i = 0; i < select_sd; i++) {
 87             //判断变化的文件描述符是否存在于working_set集合
 88             if (FD_ISSET(i, &working_set)) {
 89                 if (i == proxy_cmd_socket) {
 90
 91                     accept_cmd_socket = acceptSocket(proxy_cmd_socket,NULL,NULL);  //执行accept操作,建立proxy和客户端之间的控制连接
 92                     connect_cmd_socket = connectToServer(serverIp,CMD_PORT); //执行connect操作,建立proxy和服务器端之间的控制连接
 93
 94                     getSockLocalIp(connect_cmd_socket,serverProxyIp,BUFFSIZE);            //获取本地ip,格式为port和pasv使用的格式
 95                     getSockLocalIp(accept_cmd_socket,clientProxyIp,BUFFSIZE);            //获取本地ip,格式为port和pasv使用的格式
 96                     printf("proxy ip from server‘s view : %s\n",serverProxyIp);
 97                     printf("proxy ip from client‘s view : %s\n",clientProxyIp);
 98
 99                     //将新得到的socket加入到master_set结合中
100                     FD_SET(accept_cmd_socket, &master_set);
101                     FD_SET(connect_cmd_socket, &master_set);
102                 }
103
104                 if (i == accept_cmd_socket) {
105                     char buff[BUFFSIZE] = {0};
106                     char copy[BUFFSIZE] = {0};
107
108                     if (read(i, buff, BUFFSIZE) == 0) {
109                         close(i); //如果接收不到内容,则关闭Socket
110                         close(connect_cmd_socket);
111                         printf("client closed\n");
112
113                         //socket关闭后,使用FD_CLR将关闭的socket从master_set集合中移去,使得select函数不再监听关闭的socket
114                         FD_CLR(i, &master_set);
115                         FD_CLR(connect_cmd_socket, &master_set);
116
117                     } else {
118                         printf("command received from client : %s\n",buff);
119                         char *cmd,*param;
120                         strcpy(copy,buff);
121                         splitCmd(copy,&cmd,&param);
122                         //如果接收到内容,则对内容进行必要的处理,之后发送给服务器端(写入connect_cmd_socket)
123
124                         //处理客户端发给proxy的request,部分命令需要进行处理,如PORT、RETR、STOR
125                         //PORT
126                         //////////////
127                         if(strcmp(cmd,"PORT") == 0){                //修改ip & port
128                             //在这儿应该让proxy_data_socket监听任意端口
129                             proxy_data_socket = bindAndListenSocket(0); //开启proxy_data_socket、bind()、listen操作
130                             proxy_data_port = getSockLocalPort(proxy_data_socket);
131                             FD_SET(proxy_data_socket, &master_set);//将proxy_data_socket加入master_set集合
132                             pasv_mode = 0;
133                             data_port = getPortFromFtpParam(param);
134                             bzero(buff,BUFFSIZE);
135                             sprintf(buff,"PORT %s,%d,%d\r\n",serverProxyIp,proxy_data_port / 256,proxy_data_port % 256);
136                         }
137
138                         //写入proxy与server建立的cmd连接,除了PORT之外,直接转发buff内容
139                        printf("command sent to server : %s\n",buff);
140                        write(connect_cmd_socket, buff, strlen(buff));
141                     }
142                 }
143
144                 if (i == connect_cmd_socket) {
145                   //处理服务器端发给proxy的reply,写入accept_cmd_socket
146                     char buff[BUFFSIZE] = {0};
147                     if(read(i,buff,BUFFSIZE) == 0){
148                         close(i);
149                         close(accept_cmd_socket);
150                         FD_CLR(i,&master_set);
151                         FD_CLR(accept_cmd_socket,&master_set);
152                     }
153
154                       printf("reply received from server : %s\n",buff);
155                   //PASV收到的端口 227 (port)
156                   //////////////
157                     if(buff[0] == ‘2‘ && buff[1] == ‘2‘ && buff[2] == ‘7‘){
158                         proxy_data_socket = bindAndListenSocket(0); //开启proxy_data_socket、bind()、listen操作
159                         proxy_data_port = getSockLocalPort(proxy_data_socket);
160                         FD_SET(proxy_data_socket, &master_set);//将proxy_data_socket加入master_set集合
161                         data_port = getPortFromFtpParam(buff + 27);
162                         bzero(buff + 27,BUFFSIZE - 27);
163                         sprintf(buff + 27,"%s,%d,%d).\r\n",clientProxyIp,proxy_data_port / 256,proxy_data_port % 256);
164                     }
165                       printf("reply sent to client : %s\n",buff);
166
167                     write(accept_cmd_socket,buff,strlen(buff));
168                 }
169
170                 if (i == proxy_data_socket) {
171                     if(pasv_mode){            //clinet connect
172                         accept_data_socket = acceptSocket(proxy_data_socket,NULL,NULL);        //client <-> proxy
173                         connect_data_socket = connectToServer(serverIp,data_port);        //proxy <-> server
174                     }
175                     else{    //主动模式
176                         accept_data_socket = acceptSocket(proxy_data_socket,NULL,NULL);        //proxy <-> server
177                         clilen = sizeof(cliaddr);
178                         if(getpeername(accept_cmd_socket,(struct sockaddr *)&cliaddr,&clilen) < 0){
179                             perror("getpeername() failed: ");
180                         }
181                         cliaddr.sin_port = htons(data_port);
182                         connect_data_socket = connectToServerByAddr(cliaddr);        //client <-> proxy
183                     }
184
185                     FD_SET(accept_data_socket, &master_set);
186                     FD_SET(connect_data_socket, &master_set);
187                     printf("data connectiong established\n");
188                   //建立data连接(accept_data_socket、connect_data_socket)
189                 }
190
191                 if (i == accept_data_socket) {
192
193                     int n;
194                     char buff[BUFFSIZE] = {0};
195                     if((n = read(accept_data_socket,buff,BUFFSIZE)) == 0){
196                         close(accept_data_socket);
197                         close(connect_data_socket);
198                         close(proxy_data_socket);
199                         FD_CLR(proxy_data_socket,&master_set);
200                         FD_CLR(accept_data_socket, &master_set);
201                         FD_CLR(connect_data_socket, &master_set);
202                     }
203                     else{
204                         write(connect_data_socket,buff,n);
205                     }
206
207
208                     //判断主被动和传输方式(上传、下载)决定如何传输数据
209                 }
210
211                 if (i == connect_data_socket) {
212                     int n;
213                     char buff[BUFFSIZE] = {0};
214                     if((n = read(connect_data_socket,buff,BUFFSIZE)) == 0){
215                         close(accept_data_socket);
216                         close(connect_data_socket);
217                         close(proxy_data_socket);
218                         FD_CLR(proxy_data_socket,&master_set);
219                         FD_CLR(accept_data_socket, &master_set);
220                         FD_CLR(connect_data_socket, &master_set);
221                     }
222                     else{
223                         write(accept_data_socket,buff,n);
224                     }
225                     //判断主被动和传输方式(上传、下载)决定如何传输数据
226                 }
227             }
228         }
229     }
230
231     return 0;
232 }
233
234 unsigned short getSockLocalPort(int sockfd)
235 {
236     struct sockaddr_in addr;
237     socklen_t addrlen;
238     addrlen = sizeof(addr);
239
240     if(getsockname(sockfd,(struct sockaddr *)&addr,&addrlen) < 0){
241         perror("getsockname() failed: ");
242         exit(1);
243     }
244
245     return ntohs(addr.sin_port);
246 }
247
248
249 void getSockLocalIp(int fd,char *ipStr,int buffsize)
250 {
251
252     bzero(ipStr,buffsize);
253
254     struct sockaddr_in addr;
255     socklen_t addrlen;
256     addrlen = sizeof(addr);
257
258     if(getsockname(fd,(struct sockaddr *)&addr,&addrlen) < 0){
259         perror("getsockname() failed: ");
260         exit(1);
261     }
262
263     inet_ntop(AF_INET,&addr.sin_addr,ipStr,addrlen);
264
265     char *p = ipStr;
266     while(*p){
267         if(*p == ‘.‘) *p = ‘,‘;
268         p++;
269     }
270 }
271
272 unsigned short getPortFromFtpParam(char *param)
273 {
274     unsigned short port,t;
275     int count = 0;
276     char *p = param;
277
278     while(count < 4){
279         if(*(p++) == ‘,‘){
280             count++;
281         }
282     }
283
284     sscanf(p,"%hu",&port);
285     while(*p != ‘,‘ && *p != ‘\r‘ && *p != ‘)‘) p++;
286     if(*p == ‘,‘){
287         p++;
288         sscanf(p,"%hu",&t);
289         port = port * 256 + t;
290     }
291
292     return port;
293 }
294
295 //从FTP命令行中解析出命令和参数
296 void splitCmd(char *buff, char **cmd,char **param)
297 {
298     int i;
299     char *p;
300
301     while((p = &buff[strlen(buff) - 1]) && (*p == ‘\r‘ || *p == ‘\n‘)) *p = 0;
302
303     p = strchr(buff,‘ ‘);
304     *cmd = buff;
305
306     if(!p){
307         *param = NULL;
308     }else{
309         *p = 0;
310         *param = p + 1;
311     }
312
313     for(i = 0;i < strlen(*cmd);i++){
314         (*cmd)[i] = toupper((*cmd)[i]);
315     }
316 }
317
318
319 int acceptSocket(int cmd_socket,struct sockaddr *addr,socklen_t *addrlen)
320 {
321     int fd = accept(cmd_socket,addr,addrlen);
322     if(fd < 1){
323         perror("accept() failed:");
324         exit(1);
325     }
326
327     return fd;
328 }
329
330
331 int connectToServerByAddr(struct sockaddr_in servaddr)
332 {
333     int fd;
334
335     struct sockaddr_in cliaddr;
336     bzero(&cliaddr,sizeof(cliaddr));
337     cliaddr.sin_family = AF_INET;
338     cliaddr.sin_addr.s_addr = htonl(INADDR_ANY);
339     //cliaddr.sin_port = htons(20);
340
341     fd = socket(AF_INET,SOCK_STREAM,0);
342     if(fd < 0){
343         perror("socket() failed :");
344         exit(1);
345     }
346
347     if(bind(fd,(struct sockaddr *)&cliaddr,sizeof(cliaddr) ) < 0){
348         perror("bind() failed :");
349         exit(1);
350     }
351
352     servaddr.sin_family = AF_INET;
353     if(connect(fd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0){
354         perror("connect() failed :");
355         exit(1);
356     }
357
358     return fd;
359 }
360
361
362 int connectToServer(char *ip,unsigned short port)
363 {
364     int fd;
365     struct sockaddr_in servaddr;
366
367     bzero(&servaddr,sizeof(servaddr));
368     servaddr.sin_family = AF_INET;
369     servaddr.sin_port = htons(port);
370     inet_pton(AF_INET,ip,&servaddr.sin_addr);
371
372     fd = socket(AF_INET,SOCK_STREAM,0);
373     if(fd < 0){
374         perror("socket() failed :");
375         exit(1);
376     }
377
378     if(connect(fd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0){
379         perror("connect() failed :");
380         exit(1);
381     }
382
383     return fd;
384 }
385
386 int bindAndListenSocket(unsigned short port)
387 {
388     int fd;
389     struct sockaddr_in addr;
390
391     fd = socket(AF_INET,SOCK_STREAM,0);
392     if(fd < 0){
393         perror("socket() failed: ");
394         exit(1);
395     }
396
397     bzero(&addr,sizeof(addr));
398     addr.sin_family = AF_INET;
399     addr.sin_addr.s_addr = htonl(INADDR_ANY);
400     addr.sin_port = htons(port);
401
402     if(bind(fd,(struct sockaddr*)&addr,sizeof(addr)) < 0){
403         perror("bind() failed: ");
404         exit(1);
405     }
406
407     if(listen(fd,LISTENQ) < 0){
408         perror("listen() failed: ");
409         exit(1);
410     }
411
412     return fd;
413 }

proxy.c

时间: 2024-10-25 07:54:14

FTP Proxy Server的相关文章

role of proxy server

代理(Proxy),也称"网络代理",是一种特殊的网络服务, 允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接.一些网关.路由器等网络设备具备网络代理功能.一般认为代理服务器有利于保障网络终端的隐私或安全,防止攻击. 提供代理服务的电脑系统或者其他类型的网络终端成为代理服务器(proxy server). 作用:防火墙 功能:代理网络用户去获得网络信息 一个完整的代理请求过程为: 客户端首先与代理服务器创建连接,接着根据代理服务器所使用的代理

利用tinyproxy在Linux上搭建HTTP Proxy Server

之所以需要用到HTTP Proxy Server并不是为了要翻墙,而是为了让没有公网IP地址的内网主机通过有公网IP地址的外网主机访问Internet.举个例子,阿里云ECS在购买时可以不购买公网IP地址,但这种没有公网IP地址的ECS云主机(实例)是没有访问Internet的能力的,也就是说无法在这台实例上下载文件,这在部署应用如部署MySQL时可能遇到无法完成安装问题.解决的办法有两种,一种是在另一台具有公网访问能力的ECS实例上搭建VPN服务,另一种是在另一台具有公网访问能力的ECS实例上

设置Proxy Server和SQL Server实现互联网上的数据库安全

◆首先,我们需要了解一下SQL Server在WinSock上定义协议的步骤: 1. 在"启动"菜单上,指向"程序/Microsoft Proxy Server",然后点击"Microsoft Management Console". 2. 展开"Internet Information Service",再展开运行Proxy Server的服务器. 3. 右击WinSock Proxy service, 再点击属性. 4.

Practical 1: HTTP Web Proxy Server Programming Practical

Practical 1: HTTP Web Proxy Server Programming PracticalDue Mar 29 by 17:00 Points 24Adapted from Kurose & Ross - Computer Networking: a top-down approach featuring the InternetCBOK categories: Abstraction, Design, Data & Information, Networking,

【工作】Proxy Server的优化 - 检测目标网站URL变化

在工作中,我在组里负责一个Proxy(代理)的Module,这个Module是针对微软的Office 365的邮件门户OWA实现,工作起来后,用户访问Office 365 OWA,无需再输入Office 365的网址,只需输入我们Proxy的地址,然后我们会将请求转送到Office 365 OWA,达到用户访问的目的,并使用户的体验如同实际访问Office 365 OWA一样. 其实我们Proxy的原理是,使用Node.js构建一个http Server,拿到client端(实际是Browser

FTP FileZilla Server 本地加密C# 实现

最近公司要做一个资料管理模块,因系统是C/S架构,原来小文件都是直接使用7Z压缩后保存到SQL Server数据库 而资料管理模块也就是文件上传,下载加权限管理,考虑文件较多,还可能比较大,所以打算在服务器上直接保存文件. C/S上传文件到服务器,我知道的有几个实现方法: 1.使用IIS服务,通过B/S方式上传到服务器.这种方式需要服务器上部署IIS,提供WebServers服务,上传文件到指定目录后进行数据库编码保存. 2.使用FTP服务,服务端安装FTP服务器,客户端使用FTP类进行访问,好

[备忘]Windows Server 2008 R2部署FTP FileZilla Server防火墙设置

有一台服务器,之前文件迁移少,现准备用FileZilla Server当FTP服务器,服务器系统是Windows Server 2008 R2,同样适用FileZilla Client连接服务器FTP,总是出现连接超时的情况. 防火墙的入站.出站已经都允许了修改之后的FTP端口,只要防火墙关闭就能连上,说明还是防火墙拦截了. 最后发现,一定要在出站和入站规则里面,同时把FileZilla的服务应用程序(在FileZilla安装目录下的FileZilla server.exe) 设为允许,立刻就能

开源Squid Proxy Server 设置

设置Squid的目的当你在企业内部,Internet断掉的情况下,你可以默认路由走别的路径出去,比如从Squid 所在网络出口出去 Part 1. restart the squid service the squid path is working in /usr/local/squid/sbin/squid Configuration file /usr/local/squid/etc/squid.conf sudo ./squid -s <--- start the processsudo

Squid Proxy Server 3.1

Improve the performance of your network using the caching and access control capabilitiess of squid. A beginner level knowledge of Linux/Unix operating system familiarity with basic commands is all what you need.Squid runs almost on all Linux/Unix op