Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hh #5

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

hh #5

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 36 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
#功能:实现一个简易的FTP服务器,支持文件的上传、下载,以及断点续传
#采用多线程模型完成
#有port和pasv两种工作模式
# 简易的FTP服务器

#下面是个函数功能简单介绍
1, 基本模块:
## 简介

功能:实现一个简易的FTP服务器,支持文件的上传、下载,以及断点续传

- 采用多线程模型完成

- 有port和pasv两种工作模式

## 函数功能简单介绍

1. 基本模块:
main.c sysutil.c strutil.c session.c ftp_nobody.c ftp_proto.c common.h
其中,session模块是核心,
ftp_nobody模块实现nobody功能,主要实现控制功能,
ftp_proto模块负责一些具体的实现,实现数据传输功能
ftp_nobody 模块实现nobody功能,主要实现控制功能,
ftp_proto 模块负责一些具体的实现,实现数据传输功能
sysutil模块是系统函数模块、strutil模块是字符串处理模块,common.h用来存放公共头文件;
2configure.c parse_conf.c ftp_codes.h
2. configure.c parse_conf.c ftp_codes.h
分别是配置文件模块,配置文件解析模块、FTP应答的宏模块;
3 具体实现模块
command_map.c priv_sock.c trans_data.c trans_ctrl.c
3. 具体实现模块
command_map.c priv_sock.c trans_data.c trans_ctrl.c
分别对应的是命令映射控制模块、命令请求和应答模块、数据传输模块、控制连接和数据连接模块(上传和下载限速等)
4 其他
4. 其他
hash.c ftp_assist.c
哈希模块(用来控制客户端和ip连接数)、main函数实现模块

## 使用

1. 修改配置信息

根据需求修改配置文件 `ftpserver.conf` 中的值,确保防火墙打开相应的端口,确保主机 `21` 端口没被占用

2. 编译

Linux 系统下,切换到源码所在目录, 输入命令: `make`

3. 运行

输入 `./ftpServer`,即可运行程序

4. 客户端连接

使用 `ftp` 客户端,连接当前主机,登录的用户名和密码为当前主机的用户名及密码
23 changes: 23 additions & 0 deletions command_map.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ typedef struct Ftpcmd
} ftpcmd_t;

static ftpcmd_t ctrl_cmds[] = {

/* 访问控制命令 */
{"USER", do_user },
{"PASS", do_pass },
Expand All @@ -28,6 +29,7 @@ static ftpcmd_t ctrl_cmds[] = {
{"ACCT", NULL },
{"SMNT", NULL },
{"REIN", NULL },

/* 传输参数命令 */
{"PORT", do_port },
{"PASV", do_pasv },
Expand Down Expand Up @@ -71,10 +73,12 @@ void do_command_map(Session_t *sess)
int size = sizeof(ctrl_cmds) / sizeof(ctrl_cmds[0]); //数组大小
for (i=0; i<size; ++i)
{
/* 匹配当前命令的值 */
if (strcmp(ctrl_cmds[i].cmd, sess->com) == 0)
{
if (ctrl_cmds[i].cmd_handler != NULL)
{
/* 执行命令对应的 handler 函数 */
ctrl_cmds[i].cmd_handler(sess);
}
else
Expand All @@ -94,6 +98,9 @@ void do_command_map(Session_t *sess)
}
}

/*
* 回复 session 信息
*/
void ftp_reply(Session_t *sess, int status, const char *text)
{
char tmp[1024] = { 0 };
Expand All @@ -111,6 +118,7 @@ void ftp_lreply(Session_t *sess, int status, const char *text)
void do_user(Session_t *sess)
{
struct passwd *pw;
/* 获取用户登录的相关信息,返回用户ID,用户登录目录等信息 */
if((pw = getpwnam(sess->args)) == NULL)
{
ftp_reply(sess, FTP_LOGINERR, "Login incorrect.");
Expand All @@ -124,7 +132,20 @@ void do_user(Session_t *sess)
void do_pass(Session_t *sess)
{
//struct passwd *getpwuid(uid_t uid)
// /* The passwd structure. */
// struct passwd
// {
// char *pw_name; /* 用户名*/
// char *pw_passwd; /* 密码.*/
// __uid_t pw_uid; /* 用户ID.*/
// __gid_t pw_gid; /*组ID.*/
// char *pw_gecos; /*真实名*/
// char *pw_dir; /* 主目录.*/
// char *pw_shell; /*使用的shell*/
// };

struct passwd *pw;
/* 通过用户 ID 查找用户的密码数据 */
if((pw = getpwuid(sess->user_uid)) == NULL)
{
ftp_reply(sess, FTP_LOGINERR, "Login incorrect.");
Expand All @@ -133,6 +154,7 @@ void do_pass(Session_t *sess)

//struct spwd *getspnam(const char *name);
struct spwd *spw;
/* linux函数库中访问shadow的口令 */
if((spw = getspnam(pw->pw_name)) == NULL)
{
ftp_reply(sess, FTP_LOGINERR, "Login incorrect.");
Expand All @@ -149,6 +171,7 @@ void do_pass(Session_t *sess)

if(setegid(pw->pw_gid) == -1)
ERR_EXIT("setegid");
/* 重新设置执行目前进程的有效用户识别码 */
if(seteuid(pw->pw_uid) == -1)
ERR_EXIT("seteuid");

Expand Down
2 changes: 2 additions & 0 deletions configure.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
int tunable_pasv_enable = 1;
int tunable_port_enable = 1;
unsigned int tunable_listen_port = 21;
unsigned int tunable_pasv_port_min = 39000;
unsigned int tunable_pasv_port_max = 40000;
unsigned int tunable_max_clients = 2000;
unsigned int tunable_max_per_ip = 50;
unsigned int tunable_accept_timeout = 60;
Expand Down
2 changes: 2 additions & 0 deletions configure.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
extern int tunable_pasv_enable;
extern int tunable_port_enable;
extern unsigned int tunable_listen_port;
extern unsigned int tunable_pasv_port_min;
extern unsigned int tunable_pasv_port_max;
extern unsigned int tunable_max_clients;
extern unsigned int tunable_max_per_ip;
extern unsigned int tunable_accept_timeout;
Expand Down
2 changes: 2 additions & 0 deletions ftp_assist.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ void print_conf()
printf("tunable_port_enable=%d\n", tunable_port_enable);

printf("tunable_listen_port=%u\n", tunable_listen_port);
printf("tunable_pasv_port_min=%u\n", tunable_pasv_port_min);
printf("tunable_pasv_port_max=%u\n", tunable_pasv_port_max);
printf("tunable_max_clients=%u\n", tunable_max_clients);
printf("tunable_max_per_ip=%u\n", tunable_max_per_ip);
printf("tunable_accept_timeout=%u\n", tunable_accept_timeout);
Expand Down
3 changes: 3 additions & 0 deletions ftp_nobody.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ void handle_nobody(Session_t *sess)
char cmd;
while(1)
{
/* 接收子进程发送的命令 */
cmd = priv_sock_recv_cmd(sess->nobody_fd);
switch (cmd)
{
case PRIV_SOCK_GET_DATA_SOCK:
//获取数据套接字
privop_pasv_get_data_sock(sess);
break;
case PRIV_SOCK_PASV_ACTIVE:
//判断pasv模式是否开启
privop_pasv_active(sess);
break;
case PRIV_SOCK_PASV_LISTEN:
Expand Down
6 changes: 5 additions & 1 deletion ftp_proto.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ void handle_proto(Session_t *sess)
{
exit(EXIT_SUCCESS);
}

/* 去除行尾 回车换行符 */
str_trim_crlf(sess->command);
/* 将命令切割为两部分 */
str_split(sess->command, sess->com, sess->args, ' ');
str_upper(sess->com);
/* 字符串转换为大写 */
str_upper(sess->com);
printf("COMMD=[%s], ARGS=[%s]\n", sess->com, sess->args);

do_command_map(sess); //执行命令映射
Expand Down
8 changes: 6 additions & 2 deletions ftpserver.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
pasv_enable=YES
port_enable=YES
listen_port=8989
listen_port=21
# PASV 模式使用的端口范围
pasv_port_min=39000
pasv_port_max=40000
max_clients=100
max_per_ip=2
accept_timeout=60
Expand All @@ -10,5 +13,6 @@ data_connection_timeout=900
local_umask=077
upload_max_rate=0
download_max_rate=0
listen_address=192.168.17.140
# 服务器端监听任意 IP 地址的主机
listen_address=0.0.0.0

8 changes: 6 additions & 2 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ int main(int argc, const char *argv[])
parseconf_load_file("ftpserver.conf");
print_conf();

init_hash();
init_hash();

//创建一个监听fd
int listenfd = tcp_server(tunable_listen_address, tunable_listen_port);
Expand All @@ -30,7 +30,7 @@ int main(int argc, const char *argv[])
while(1)
{
//每当用户连接上,就fork一个子进程

struct sockaddr_in addr;
int peerfd = accept_timeout(listenfd, &addr, tunable_accept_timeout);
if(peerfd == -1 && errno == ETIMEDOUT)
Expand All @@ -47,6 +47,8 @@ int main(int argc, const char *argv[])
ERR_EXIT("fork");
else if(pid == 0)
{
// 子进程
// 关闭父进程中的监听端口
close(listenfd);

sess.peer_fd = peerfd;
Expand All @@ -57,8 +59,10 @@ int main(int argc, const char *argv[])
}
else
{
// 父进程
//pid_to_ip
add_pid_ip_to_hash(pid, ip);
// 关闭数据连接 socket
close(peerfd);
}
}
Expand Down
5 changes: 5 additions & 0 deletions parse_conf.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ static struct parseconf_uint_setting
parseconf_uint_array[] =
{
{ "listen_port", &tunable_listen_port },
{ "pasv_port_min", &tunable_pasv_port_min },
{ "pasv_port_max", &tunable_pasv_port_max },
{ "max_clients", &tunable_max_clients },
{ "max_per_ip", &tunable_max_per_ip },
{ "accept_timeout", &tunable_accept_timeout },
Expand Down Expand Up @@ -97,6 +99,7 @@ static void parseconf_load_setting(const char *setting)


{
/* 监听的 IP 地址 */
const struct parseconf_str_setting *p_str_setting = parseconf_str_array;
while (p_str_setting->p_setting_name != NULL)
{
Expand All @@ -115,6 +118,7 @@ static void parseconf_load_setting(const char *setting)
}

{
/* PORT 模式 PASV 模式是否使能 */
const struct parseconf_bool_setting *p_bool_setting = parseconf_bool_array;
while (p_bool_setting->p_setting_name != NULL)
{
Expand Down Expand Up @@ -143,6 +147,7 @@ static void parseconf_load_setting(const char *setting)
}

{
/* 各数字设置 */
const struct parseconf_uint_setting *p_uint_setting = parseconf_uint_array;
while (p_uint_setting->p_setting_name != NULL)
{
Expand Down
61 changes: 60 additions & 1 deletion priv_command.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
#include "sysutil.h"
#include "priv_sock.h"
#include "configure.h"
#include <time.h>

/* 获得一个随机数 */
unsigned int zrand(void)
{
return (unsigned int)(((int)time(0)) * 1103515245 + 12345 % 0x7fff);
}

//获取数据套接字
void privop_pasv_get_data_sock(Session_t *sess)
Expand All @@ -11,6 +18,7 @@ void privop_pasv_get_data_sock(Session_t *sess)
priv_sock_recv_str(sess->nobody_fd, ip, sizeof ip);
uint16_t port = priv_sock_recv_int(sess->nobody_fd);
//创建fd
/* 端口号:20 !!! */
int data_fd = tcp_client(20);

struct sockaddr_in addr;
Expand Down Expand Up @@ -39,11 +47,62 @@ void privop_pasv_listen(Session_t *sess)
//创建listen fd
char ip[16] = {0};
get_local_ip(ip);
int listenfd = tcp_server(ip, 20);

/* ==== 从指定的端口范围内获取一个可用的端口号 ==*/
int firstport = tunable_pasv_port_min;
int lastport = tunable_pasv_port_max;

int firstporttried = firstport + zrand() % (lastport - firstport + 1);
int p = firstporttried;
int data_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (data_fd == -1)
{
ERR_EXIT("socket");
// error(425, MSG_CANT_PASSIVE);
// return;
}
int on = 1;
if (setsockopt(data_fd, SOL_SOCKET, SO_REUSEADDR,
(char *)&on, sizeof on) < 0)
{
ERR_EXIT("setsockopt");
// error(421, "setsockopt");
// return;
}
for (;;)
{
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(p);
if (bind(data_fd, (struct sockaddr *)&addr,
sizeof(addr)) == 0)
{
break;
}
p--;
if (p < firstport)
{
p = lastport;
}
if (p == firstporttried)
{
(void)close(data_fd);
data_fd = -1;
// addreply_noformat(425, MSG_PORTS_BUSY);
// return;
ERR_EXIT("MSG_PORTS_BUSY");
}
}
/* ========================================*/
close(data_fd);
int listenfd = tcp_server(ip, p);
// int listenfd = tcp_server(ip, 36000);
sess->listen_fd = listenfd;

struct sockaddr_in addr;
socklen_t len = sizeof addr;
/* 获取套接字的本地协议地址 */
if(getsockname(listenfd, (struct sockaddr*)&addr, &len) == -1)
ERR_EXIT("getsockname");

Expand Down
Loading