被动模式的列表显示
查看linux中进程的命令
ps -ef | grep miniftpd
结束进程
killall miniftpd
static void do_port(session_t *sess)
{
unsigned char v[6] = {0};
sscanf(sess->arg, "%d, %d, %d, %d, %d, %d,", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]);
//printf("v[0] = %d, v[1]=%d, v[2]=%d, v[3]=%d, v[4]=%d, v[5]=%d\n",
// v[0],v[1],v[2],v[3],v[4],v[5]);
sess->port_addr = (struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));
memset(sess->port_addr, 0, sizeof(struct sockaddr_in));
//sin_family
sess->port_addr->sin_family = AF_INET;
//sin_addr
unsigned char *p = (unsigned char*)&sess->port_addr->sin_addr;
p[0] = v[0];
p[1] = v[1];
p[2] = v[2];
p[3] = v[3];
//sin_port
p = (unsigned char*)&sess->port_addr->sin_port;
p[0] = v[4];
p[1] = v[5];
//200 PORT command successful. Consider using PASV.
ftp_reply(sess, FTP_PORTOK, "PORT command successful. Consider using PASV.");
}
static void do_pasv(session_t *sess)
{
//ip = "192.168.0.200"
char *ip = "192.168.0.200";
int sockfd = tcp_server(ip, 0);
struct sockaddr_in address;
socklen_t addrlen = sizeof(address);
if(getsockname(sockfd, (struct sockaddr*)&address, &addrlen) < 0)
ERR_EXIT("getsockname");//获取该套接字所绑定的端口
//printf("ip = %s\n", inet_ntoa(addr.sin_addr));
//printf("port = %d\n",ntohs(addr.sin_port));
unsigned short port = ntohs(address.sin_port);
unsigned char addr[6] = {0};
sscanf(ip, "%u.%u.%u.%u", &addr[0], &addr[1], &addr[2], &addr[3]);
addr[4] = ((port>>8) & 0x00ff);
addr[5] = port & 0x00ff;
char buf[MAX_BUFFER_SIZE] = {0};
sprintf(buf, "Entering Passive Mode (%u,%u,%u,%u,%u,%u)", addr[0],addr[1],addr[2],addr[3],addr[4],addr[5]);
//port = 8080
//227 Entering Passive Mode (192,168,0,200,76,240).
sess->pasv_listen_fd = sockfd;//数据套接字
ftp_reply(sess, FTP_PASVOK, buf);
}
///
int port_active(session_t *sess)
{
if(sess->port_addr != 0)
return 1;
return 0;
}
int pasv_active(session_t *sess)
{
if(sess->pasv_listen_fd != -1)
return 1;
return 0;
}
int get_transfer_fd(session_t *sess)
{
if(!port_active(sess) && !pasv_active(sess))
{
//425 Use PORT or PASV first
ftp_reply(sess, FTP_BADSENDCONN, "Use PORT or PASV first");
return 0;
}
if(port_active(sess) && pasv_active(sess))
{
//425 PORT both PASV active.
ftp_reply(sess, FTP_BADSENDCONN, "PORT both PASV active.");
return 0;
}
int datafd;
if(port_active(sess))
{
if((datafd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return 0;
socklen_t addrlen = sizeof(struct sockaddr);
/*
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port = htons(20);
address.sin_addr.s_addr = inet_addr("192.168.0.200");
if(bind(datafd, (struct sockaddr*)&address, addrlen) < 0)
ERR_EXIT("bind 20");
*/
if(connect(datafd, (struct sockaddr*)sess->port_addr, addrlen) < 0)
return 0;
}
if(pasv_active(sess))
{
struct sockaddr_in addrcli;
socklen_t addrlen = sizeof(addrcli);
if((datafd = accept(sess->pasv_listen_fd, (struct sockaddr*)&addrcli, &addrlen))<0)
return 0;
close(sess->pasv_listen_fd);//数据连接建立完成,就关闭
sess->pasv_listen_fd = -1;//还原原来的初始值
}
sess->data_fd = datafd;
if(sess->port_addr)
{
free(sess->port_addr);
sess->port_addr = 0;
}
return 1;
}
进程模型修改
ftp进程和nobody进程。在两个进程之间创建socketpair作为通道,nobody循环readline()。当ftp进程结束之后,readline()返回值为0,随后nobody进程结束。最后在主线中安装忽略子进程结束的信号,使主线进程忽略nobody进程,避免nobody进程变成僵尸进程。
进程间的通信
ftp进程<-------->nobody进程之间的通信
首先定义一个内部通信模块privsocket.h,用来发送和接收:命令、字符串、整数、套接字等
#ifndef _PRIV_SOCK_H_
#define _PRIV_SOCK_H_
#include"session.h"
//FTP服务进程向nobody进程请求的命令
#define PRIV_SOCK_GET_DATA_SOCK 1
#define PRIV_SOCK_PASV_ACTIVE 2
#define PRIV_SOCK_PASV_LISTEN 3
#define PRIV_SOCK_PASV_ACCEPT 4
//nobody 进程对FTP服务进程的应答
#define PRIV_SOCK_RESULT_OK 1
#define PRIV_SOCK_RESULT_BAD 2
void priv_sock_init(session_t *sess);
void priv_sock_close(session_t *sess);
void priv_sock_set_parent_context(session_t *sess);
void priv_sock_set_child_context(session_t *sess);
void priv_sock_send_cmd(int fd, char cmd);
char priv_sock_get_cmd(int fd);
void priv_sock_send_result(int fd, char res);
char priv_sock_get_result(int fd);
void priv_sock_send_int(int fd, int the_int);
int priv_sock_get_int(int fd);
void priv_sock_send_buf(int fd, const char *buf, unsigned int len);
void priv_sock_recv_buf(int fd, char *buf, unsigned int len);
void priv_sock_send_fd(int sock_fd, int fd);
int priv_sock_recv_fd(int sock_fd);
#endif /* _PRIV_SOCK_H_ */
nobody进程协助主动模式建立数据连接
下面简述一下过程:
- ftp进程向nobody进程发送命令请求获取数据套接字。
- nobody进程收到请求,并执行相应的函数:创建流式套接字datafd,并设置端口重用,绑定20端口,阻塞。
- ftp进程向nobody进程发送解析到的cli的IP和PORT,阻塞。
- nobody进程收到IP和PORT,建立连接,向ftp进程发送PRIV_SOCK_RESULT_OK,而后发送datafd,关闭datafd。
- ftp进程将收到的datafd存入sess->data_fd。
nobody进程权限提升
查看内核版本
uname -a
为什么要用nobody进程?
主动模式时候需要绑定20端口,只有用nobody进程提升权限,才能够实现绑定
主动模式为什么一定要绑定20端口?
提升nobody进程权限的方法:
//最小化特权提升函数
void minimize_privilege(void)
{
struct passwd *pw = getpwnam("nobody");
if(pw == NULL)
return;
if(setegid(pw->pw_gid) < 0)
ERR_EXIT("setegid");
if(seteuid(pw->pw_uid) < 0)
ERR_EXIT("seteuid");
struct __user_cap_header_struct cap_header;
struct __user_cap_data_struct cap_data;
//初始化
memset(&cap_header, 0, sizeof(cap_header));
memset(&cap_data, 0, sizeof(cap_data));
//根据手册赋相应的值
cap_header.version = _LINUX_CAPABILITY_VERSION_1;//根据系统的版本填充
cap_header.pid = 0;//提升为root权限
__u32 cap_mask = 0;//设置具体权限的掩码
cap_mask |= (1<<CAP_NET_BIND_SERVICE);//使其具有绑定小于1024端口的权限
cap_data.effective = cap_data.permitted = cap_mask;
cap_data.inheritable = 0;//????
capset(&cap_header, &cap_data);
}
nobody进程协助被动模式建立数据连接
被动模式的实现要复杂很多!封装和不封装各有自己的好处。
- ftp进程发送命令,向nobody进程请求监听套接字,阻塞。
- nobody进程创建套接字,获取临时端口。并保存socketfd到sess->pasv_listen_fd。向ftp进程发送port。
- ftp进程收到port,并将IP和PORT发送到cli。
- 判断pasv是否被激活?向nobody 进程发送请求命令;nobody进程执行相应的函数,判断session结构体中的pasv_listen_fd是否更新,更新的话,应答1(说明pasv激活)。
- ftp进程向nobody进程发送命令,请求accept套接字,阻塞
- nobody进程,aceept (pasv_listen_fd),应答OK,并将accept得到的datafd,发送给ftp进程,close datafd , close pasv_listen_fd。
- ftp进程将收到的datafd存入会话结构体中。