FTP Proxy Server

Posted 莫扎特的代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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只是个惯例,其实不适用影响不大),并完成数据传输。

FTP Active Mode

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 

以上是关于FTP Proxy Server的主要内容,如果未能解决你的问题,请参考以下文章

java Ftp上传创建多层文件的代码片段

Microsoft SQL Server 代码片段收集

缺少 SQL SERVER 2014 代码片段

七个办法只有一个有效:200 PORT command successful. Consider using PASV.425 Failed to establish connection.(代码片段

在Tomcat的安装目录下conf目录下的server.xml文件中增加一个xml代码片段,该代码片段中每个属性的含义与用途

尝试登录时FTP Server奇怪的响应