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代理服务器实现
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h> #define TRUE 1
#define FALSE 0 #define CMD_PORT 21
#define BUFFSIZE 4096
#define LISTENQ 5 int acceptSocket(int socket,struct sockaddr *addr,socklen_t *addrlen);
int connectToServerByAddr(struct sockaddr_in servaddr);
int connectToServer(char *ip,unsigned short port);
int bindAndListenSocket(unsigned short port);
void splitCmd(char *buff, char **cmd,char **param);
unsigned short getPortFromFtpParam(char *param);
void getSockLocalIp(int fd,char *ipStr,int buffsize);
unsigned short getSockLocalPort(int sockfd); int main(int argc, const char *argv[])
{
int i;
fd_set master_set, working_set; //文件描述符集合
struct timeval timeout; //select 参数中的超时结构体
int proxy_cmd_socket = ; //proxy listen控制连接
int accept_cmd_socket = ; //proxy accept客户端请求的控制连接
int connect_cmd_socket = ; //proxy connect服务器建立控制连接
int proxy_data_socket = ; //proxy listen数据连接
int accept_data_socket = ; //proxy accept得到请求的数据连接(主动模式时accept得到服务器数据连接的请求,被动模式时accept得到客户端数据连接的请求)
int connect_data_socket = ; //proxy connect建立数据连接 (主动模式时connect客户端建立数据连接,被动模式时connect服务器端建立数据连接)
int selectResult = ; //select函数返回值
int select_sd = ; //select 函数监听的最大文件描述符
int pasv_mode = ; char serverProxyIp[BUFFSIZE]; //待获得
char clientProxyIp[BUFFSIZE]; //待获得 serverProxyIp和clientProxyIp可能不一样
char serverIp[BUFFSIZE]; unsigned short proxy_data_port;
unsigned short data_port;
socklen_t clilen;
struct sockaddr_in cliaddr; if(argc != ){
printf("usage : proxy server_ip\n example: proxy 121.121.121.121\n");
}
strcpy(serverIp,argv[]); FD_ZERO(&master_set); //清空master_set集合
bzero(&timeout, sizeof(timeout)); proxy_cmd_socket = bindAndListenSocket(CMD_PORT); //开启proxy_cmd_socket、bind()、listen操作
FD_SET(proxy_cmd_socket, &master_set); //将proxy_cmd_socket加入master_set集合 while (TRUE) {
FD_ZERO(&working_set); //清空working_set文件描述符集合
memcpy(&working_set, &master_set, sizeof(master_set)); //将master_set集合copy到working_set集合
timeout.tv_sec = ; //Select的超时结束时间
timeout.tv_usec = ; //ms //select循环监听 这里只对读操作的变化进行监听(working_set为监视读操作描述符所建立的集合),第三和第四个参数的NULL代表不对写操作、和误操作进行监听
selectResult = select(select_sd, &working_set, NULL, NULL, &timeout); // fail
if (selectResult < ) {
perror("select() failed\n");
exit();
} // timeout
if (selectResult == ) {
printf("select() timed out.\n");
continue;
} // selectResult > 0 时 开启循环判断有变化的文件描述符为哪个socket
for (i = ; i < select_sd; i++) {
//判断变化的文件描述符是否存在于working_set集合
if (FD_ISSET(i, &working_set)) {
if (i == proxy_cmd_socket) { accept_cmd_socket = acceptSocket(proxy_cmd_socket,NULL,NULL); //执行accept操作,建立proxy和客户端之间的控制连接
connect_cmd_socket = connectToServer(serverIp,CMD_PORT); //执行connect操作,建立proxy和服务器端之间的控制连接 getSockLocalIp(connect_cmd_socket,serverProxyIp,BUFFSIZE); //获取本地ip,格式为port和pasv使用的格式
getSockLocalIp(accept_cmd_socket,clientProxyIp,BUFFSIZE); //获取本地ip,格式为port和pasv使用的格式
printf("proxy ip from server's view : %s\n",serverProxyIp);
printf("proxy ip from client's view : %s\n",clientProxyIp); //将新得到的socket加入到master_set结合中
FD_SET(accept_cmd_socket, &master_set);
FD_SET(connect_cmd_socket, &master_set);
} if (i == accept_cmd_socket) {
char buff[BUFFSIZE] = {};
char copy[BUFFSIZE] = {}; if (read(i, buff, BUFFSIZE) == ) {
close(i); //如果接收不到内容,则关闭Socket
close(connect_cmd_socket);
printf("client closed\n"); //socket关闭后,使用FD_CLR将关闭的socket从master_set集合中移去,使得select函数不再监听关闭的socket
FD_CLR(i, &master_set);
FD_CLR(connect_cmd_socket, &master_set); } else {
printf("command received from client : %s\n",buff);
char *cmd,*param;
strcpy(copy,buff);
splitCmd(copy,&cmd,¶m);
//如果接收到内容,则对内容进行必要的处理,之后发送给服务器端(写入connect_cmd_socket) //处理客户端发给proxy的request,部分命令需要进行处理,如PORT、RETR、STOR
//PORT
//////////////
if(strcmp(cmd,"PORT") == ){ //修改ip & port
//在这儿应该让proxy_data_socket监听任意端口
proxy_data_socket = bindAndListenSocket(); //开启proxy_data_socket、bind()、listen操作
proxy_data_port = getSockLocalPort(proxy_data_socket);
FD_SET(proxy_data_socket, &master_set);//将proxy_data_socket加入master_set集合
pasv_mode = ;
data_port = getPortFromFtpParam(param);
bzero(buff,BUFFSIZE);
sprintf(buff,"PORT %s,%d,%d\r\n",serverProxyIp,proxy_data_port / ,proxy_data_port % );
} //写入proxy与server建立的cmd连接,除了PORT之外,直接转发buff内容
printf("command sent to server : %s\n",buff);
write(connect_cmd_socket, buff, strlen(buff));
}
} if (i == connect_cmd_socket) {
//处理服务器端发给proxy的reply,写入accept_cmd_socket
char buff[BUFFSIZE] = {};
if(read(i,buff,BUFFSIZE) == ){
close(i);
close(accept_cmd_socket);
FD_CLR(i,&master_set);
FD_CLR(accept_cmd_socket,&master_set);
} printf("reply received from server : %s\n",buff);
//PASV收到的端口 227 (port)
//////////////
if(buff[] == '' && buff[] == '' && buff[] == ''){
proxy_data_socket = bindAndListenSocket(); //开启proxy_data_socket、bind()、listen操作
proxy_data_port = getSockLocalPort(proxy_data_socket);
FD_SET(proxy_data_socket, &master_set);//将proxy_data_socket加入master_set集合
data_port = getPortFromFtpParam(buff + );
bzero(buff + ,BUFFSIZE - );
sprintf(buff + ,"%s,%d,%d).\r\n",clientProxyIp,proxy_data_port / ,proxy_data_port % );
}
printf("reply sent to client : %s\n",buff); write(accept_cmd_socket,buff,strlen(buff));
} if (i == proxy_data_socket) {
if(pasv_mode){ //clinet connect
accept_data_socket = acceptSocket(proxy_data_socket,NULL,NULL); //client <-> proxy
connect_data_socket = connectToServer(serverIp,data_port); //proxy <-> server
}
else{ //主动模式
accept_data_socket = acceptSocket(proxy_data_socket,NULL,NULL); //proxy <-> server
clilen = sizeof(cliaddr);
if(getpeername(accept_cmd_socket,(struct sockaddr *)&cliaddr,&clilen) < ){
perror("getpeername() failed: ");
}
cliaddr.sin_port = htons(data_port);
connect_data_socket = connectToServerByAddr(cliaddr); //client <-> proxy
} FD_SET(accept_data_socket, &master_set);
FD_SET(connect_data_socket, &master_set);
printf("data connectiong established\n");
//建立data连接(accept_data_socket、connect_data_socket)
} if (i == accept_data_socket) { int n;
char buff[BUFFSIZE] = {};
if((n = read(accept_data_socket,buff,BUFFSIZE)) == ){
close(accept_data_socket);
close(connect_data_socket);
close(proxy_data_socket);
FD_CLR(proxy_data_socket,&master_set);
FD_CLR(accept_data_socket, &master_set);
FD_CLR(connect_data_socket, &master_set);
}
else{
write(connect_data_socket,buff,n);
} //判断主被动和传输方式(上传、下载)决定如何传输数据
} if (i == connect_data_socket) {
int n;
char buff[BUFFSIZE] = {};
if((n = read(connect_data_socket,buff,BUFFSIZE)) == ){
close(accept_data_socket);
close(connect_data_socket);
close(proxy_data_socket);
FD_CLR(proxy_data_socket,&master_set);
FD_CLR(accept_data_socket, &master_set);
FD_CLR(connect_data_socket, &master_set);
}
else{
write(accept_data_socket,buff,n);
}
//判断主被动和传输方式(上传、下载)决定如何传输数据
}
}
}
} return ;
} unsigned short getSockLocalPort(int sockfd)
{
struct sockaddr_in addr;
socklen_t addrlen;
addrlen = sizeof(addr); if(getsockname(sockfd,(struct sockaddr *)&addr,&addrlen) < ){
perror("getsockname() failed: ");
exit();
} return ntohs(addr.sin_port);
} void getSockLocalIp(int fd,char *ipStr,int buffsize)
{ bzero(ipStr,buffsize); struct sockaddr_in addr;
socklen_t addrlen;
addrlen = sizeof(addr); if(getsockname(fd,(struct sockaddr *)&addr,&addrlen) < ){
perror("getsockname() failed: ");
exit();
} inet_ntop(AF_INET,&addr.sin_addr,ipStr,addrlen); char *p = ipStr;
while(*p){
if(*p == '.') *p = ',';
p++;
}
} unsigned short getPortFromFtpParam(char *param)
{
unsigned short port,t;
int count = ;
char *p = param; while(count < ){
if(*(p++) == ','){
count++;
}
} sscanf(p,"%hu",&port);
while(*p != ',' && *p != '\r' && *p != ')') p++;
if(*p == ','){
p++;
sscanf(p,"%hu",&t);
port = port * + t;
} return port;
} //从FTP命令行中解析出命令和参数
void splitCmd(char *buff, char **cmd,char **param)
{
int i;
char *p; while((p = &buff[strlen(buff) - ]) && (*p == '\r' || *p == '\n')) *p = ; p = strchr(buff,' ');
*cmd = buff; if(!p){
*param = NULL;
}else{
*p = ;
*param = p + ;
} for(i = ;i < strlen(*cmd);i++){
(*cmd)[i] = toupper((*cmd)[i]);
}
} int acceptSocket(int cmd_socket,struct sockaddr *addr,socklen_t *addrlen)
{
int fd = accept(cmd_socket,addr,addrlen);
if(fd < ){
perror("accept() failed:");
exit();
} return fd;
} int connectToServerByAddr(struct sockaddr_in servaddr)
{
int fd; struct sockaddr_in cliaddr;
bzero(&cliaddr,sizeof(cliaddr));
cliaddr.sin_family = AF_INET;
cliaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//cliaddr.sin_port = htons(20); fd = socket(AF_INET,SOCK_STREAM,);
if(fd < ){
perror("socket() failed :");
exit();
} if(bind(fd,(struct sockaddr *)&cliaddr,sizeof(cliaddr) ) < ){
perror("bind() failed :");
exit();
} servaddr.sin_family = AF_INET;
if(connect(fd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < ){
perror("connect() failed :");
exit();
} return fd;
} int connectToServer(char *ip,unsigned short port)
{
int fd;
struct sockaddr_in servaddr; bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
inet_pton(AF_INET,ip,&servaddr.sin_addr); fd = socket(AF_INET,SOCK_STREAM,);
if(fd < ){
perror("socket() failed :");
exit();
} if(connect(fd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < ){
perror("connect() failed :");
exit();
} return fd;
} int bindAndListenSocket(unsigned short port)
{
int fd;
struct sockaddr_in addr; fd = socket(AF_INET,SOCK_STREAM,);
if(fd < ){
perror("socket() failed: ");
exit();
} bzero(&addr,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port); if(bind(fd,(struct sockaddr*)&addr,sizeof(addr)) < ){
perror("bind() failed: ");
exit();
} if(listen(fd,LISTENQ) < ){
perror("listen() failed: ");
exit();
} return fd;
}
proxy.c
FTP Proxy Server的更多相关文章
- proxy server 代理服务器
有时候,我觉得自己需要去搞明白.搞清楚一个概念,帮我打通一下自己的知识体系,或者说,尝试联络起来. 1. 简介 突破自身IP限制,访问国外站点. 访问单位或者团体内部资源. 突破中国电信的IP封锁. ...
- Proxy Server代理服务器(轉載)
宽带IP城域网开通以来,单位连上了宽带网,10M的带宽让我们感受到了宽带的魅力.电信只提供7个IP地址,对任何一个单位来说都太少了,常用的解决办法是使用代理服务器.微软的MS Proxy Server ...
- How to setup vsftpd FTP file Server on Redhat 7 Linux
Forward from: https://linuxconfig.org/how-to-setup-vsftpd-ftp-file-server-on-redhat-7-linux How to s ...
- 502 Proxy Error The proxy server received an invalid response from an upstream server
Proxy Error The proxy server received an invalid response from an upstream server. The proxy server ...
- 设置Proxy Server和SQL Server实现互联网上的数据库安全
◆首先,我们需要了解一下SQL Server在WinSock上定义协议的步骤: 1. 在”启动”菜单上,指向”程序/Microsoft Proxy Server”,然后点击”Microsoft Man ...
- 转:Jmeter 用户思考时间(User think time),定时器,和代理服务器(proxy server)
在负载测试中需要考虑的的一个重要要素是思考时间(think time), 也就是在两次成功的访问请求之间的暂停时间. 有多种情形挥发导致延迟的发生: 用户需要时间阅读文字内容,或者填表,或者查找正确的 ...
- tengine2.2.3报错502的The proxy server received an invalid response from an upstream server问题处理
tengine2.2.3报错502的The proxy server received an invalid response from an upstream server问题处理 现象:访问订单的 ...
- 简单实现http proxy server怎么实现?
原文:https://blog.csdn.net/dolphin98629/article/details/54599850 简单实现http proxy server怎么实现?
- 架设FTP Server-Windows Server 2012
架设FTP Server-Windows Server 2012 https://jingyan.baidu.com/article/03b2f78c75b9b65ea237ae84.html 在 ...
随机推荐
- Git命令行下解决冲突
使用Git时,在pull.merge.rebase的过程中,经常会遇到conflict的情况. 遇到conflict时,以上处理过程会终端,并且命令行中显示(xxx|MERGING)的状态(Windo ...
- kali安装java1.7
1.先去这里下载你需要的版本 http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html 我 ...
- 办公大楼3D指纹门禁系统解决方案
随着人们对工作.生活的自动化水平也提出了越来越高的要求.以大楼安保对出入大楼的外来人员进行登记放行或以铁锁.钥匙和卡为代表的出入管理方式已无法满足需求. 利用科技的手段,实现办公大楼的安全现代化.管理 ...
- Intent四个重要属性
Intent四个重要属性 Intent作为联系各Activity之间的纽带,其作用并不仅仅只限于简单的数据传递.通过其自带的属性,其实可以方便的完成很多较为复杂的操作.例如直接调用拨号功能.直接自 ...
- get传中文参数乱码解决方法
通常我们前端不同页面之间传参数用得最多的方法就是get方法:在url后面加上参数.例如:www.test.com?id=1&name=hello. 英文和字母很好处理,但是如果有的参数值为中文 ...
- python 3次登录
#!/usr/bin/env python #-*- encoding: utf- -*- import sys import os import getpass import platform # ...
- 利用Github Pages生成一个快速访问的网址,展示自己的项目
利用Github Pages展示自己的项目 写了个小项目,想要分享出去,一直在用Github管理项目,了解到Github还有Github Pages功能,然后惊喜的发现Github pages可以搭建 ...
- mysql delete 使用别名 语法
今天删除数据,写了这么条sql语句, DELETE from sys_menus s WHERE s.MENU_ID in (86,87,88); 结果报错.. [Err] 1064 - You ...
- Gevent中的同步与异步详解
同步,异步概念 1.同步就是发生调用时,一定等待结果返回,整个调用才结束: 2.异步就是发生调用后,立即返回,不等待结果返回.被调用者通过状态.通知来通知调用者,或通过回调函数处理这个调用. 查询 1 ...
- sqlservr (708) 打开日志文件 C:\Windows\system32\LogFiles\Sum\Api.log 时出现错误 -1032 (0xfffffbf8)
在windows server 2012 standard上新安装好的SQL Server 2014,查看错误日志,发现此报错 sqlservr (708) 打开日志文件 C:\Windows\sys ...