ftp关键技术二:nobody进程创建和使用(一)
本文将从以下几个方面具体阐述nobody进程的前世今生
为什么需要nobody进程?
进程间通信的协议制定
为什么需要nobody进程
(1)为什么要使用nobody进程和服务进程两个进程?
1.在PORT模式下,服务器会主动建立数据通道连接客户端,服务器可能就没有权限做这种事情,就需要nobody进程来帮忙。 Nobody进程会通过unix域协议(本机通信效率高) 将套接字传递给服务进程。普通用户没有权限绑定20端口,需要nobody进程的协助,所以需要nobody进程作为控制进程。
2.事实上无论是PORT模式还是PASV模式,创建套接字还是后面对套接字的监听这些操作涉及到于内核的相关操作放在服务进程都是不安全。其实最近看到一个文章,文中指出以root启动在验证后转到用户进程也会不安全的。
(2)为什么使用多进程而不是多线程?
原因是在多线程或IO复用的情况下,当前目录是共享的,无法根据每一个连接来拥有自己的当前目录,也就是说当前用户目录的切换会影响到其他的用户。
(3)ftp服务器的架构
<!-- more -->
进程间通信的协议制定
首先采用Unix域的内部通信协议需要创建一个Unix的套接字进行通信
void priv_sock_init(session_t *sess) {int sockfds[2];if (socketpair(PF_UNIX, SOCK_STREAM, 0, sockfds) < 0)ERR_EXIT("socketpair");sess->parent_fd = sockfds[0];sess->child_fd = sockfds[1]; } void priv_sock_set_parent_context(session_t *sess) {if (sess->child_fd != -1){close(sess->child_fd);sess->child_fd = -1;} } void priv_sock_set_child_context(session_t *sess) {if (sess->parent_fd != -1){close(sess->parent_fd);sess->parent_fd = -1;} }
sess作为两个进程共有的用户信息,在两个进程创建初期sess内部便被写入了Unix的套接字通信
void begin_session(session_t *sess) {activate_oobinline(sess->ctrl_fd);priv_sock_init(sess); //写入套接字pid_t pid;pid = fork();if (pid < 0)ERR_EXIT("fork"); if (pid == 0) {priv_sock_set_child_context(sess);handle_child(sess);} else {priv_sock_set_parent_context(sess);handle_parent(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);
我们可以看到主要有两个功能的函数,一是负责内部的命令的接受、实现和返回结果,二是负责传输数据。
首先看看第一部分是怎么实现的吧
// 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_send_cmd(int fd, char cmd) {int ret;ret = writen(fd, &cmd, sizeof(cmd));if (ret != sizeof(cmd)){fprintf(stderr, "priv_sock_send_cmd error\n");exit(EXIT_FAILURE);} } char priv_sock_get_cmd(int fd) {char res;int ret;ret = readn(fd, &res, sizeof(res));if (ret == 0){printf("ftp process exit\n");exit(EXIT_SUCCESS);}if (ret != sizeof(res)){fprintf(stderr, "priv_sock_get_cmd error\n");exit(EXIT_FAILURE);} return res; }
这里提供了get_cmd和send_cmd的实现,可以看到只是简单包装下send和read函数
这里就不再赘述其他函数,对于这些函数,我们主要关注一组特殊函数
void priv_sock_send_fd(int sock_fd, int fd) {send_fd(sock_fd, fd); } int priv_sock_recv_fd(int sock_fd) {return recv_fd(sock_fd); }
为什么这个比较特殊呢?因为这不是传输一个四个字节的整形,而是传输一个打开的文件描述符,我们想让发送进程和接受进程共享同一文件表项。在技术上,我们是将一个打开文件表项的指针从一个进程发送到另一个进程,该指针被分配到接受进程第一个可用的描述符中。发送结束后,发送进程通常会关闭该描述符。
为了在UNIX域套接字交换文件描述符,我们需要关注以下系统函数
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); struct msghdr {void *msg_name; /* optional address */socklen_t msg_namelen; /* size of address */struct iovec *msg_iov; /* scatter/gather array */size_t msg_iovlen; /* # elements in msg_iov */void *msg_control; /* ancillary data, see below */size_t msg_controllen; /* ancillary data buffer len */int msg_flags; /* flags on received message */ };
前两个元素主要用于网络通信,msg_name存数据包的目的地址,网络包指向struct sockaddr_in,msg_namelen值地址长度,一般为16。一般在UNIX域设置为NULL, 0。
接下来的两个元素我们可以指定一个或多个内存缓存区,第一个元素指向一个数据包缓存区的buff 。其中 iov_base指向数据包缓冲区,即参数buff,iov_len是buff的长度。msghdr中允许一次传递多buff,以数组的形式组织在 msg_iov中,msg_iovlen就记录数组的长度(即有多少个buff)。
struct iovec { /* Scatter/gather array items */ void *iov_base; /* Starting address */size_t iov_len; /* Number of bytes to transfer */ };
最后两个元素,msg_flags字段包含了描述接收到的消息的标志,如带外数据MSG_OOB等。mgs_controllen字段指向cmsghdr结构,用于控制信息字节数
struct cmsghdr {socklen_t cmsg_len; /* data byte count, including header */int cmsg_level; /* originating protocol */int cmsg_type; /* protocol-specific type *//* followed by unsigned char cmsg_data[]; */ };
为了发送文件描述符,需要将cmsg_len设置为cmsghdr结构的长度加一个文件描述符的长度,将cmg_level设计为SOL_SOCKET, cmsg_type字段设置为SCM_RIGHTS,用以表明传送访问权,描述符紧随cmsg_type字段之后储存,用CMSG_DATA宏获得该整型量的指针。
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);//获得指向与msghadr结构关联的第一个cmsghdr结构size_t CMSG_SPACE(size_t length);//计算 cmsghdr 头结构加上附属数据大小,并包括对其字段和可能的结尾填充字符size_t CMSG_LEN(size_t length);//计算 cmsghdr 头结构加上附属数据大小unsigned char *CMSG_DATA(struct cmsghdr *cmsg);//返回一个指针和cmsghdr结构关联的数据
我们可以通过控制这些宏对这些进行cmsghdr初始化,具体实现看下面。
/*** send_fd -向sock_fd 发送 fd* @sock_fd: 发送目标套接字* @fd: 发送套接字*/ void send_fd(int sock_fd, int fd) {int ret;struct msghdr msg;struct cmsghdr *p_cmsg;struct iovec vec;char cmsgbuf[CMSG_SPACE(sizeof(fd))]; //配置cmsgbuf的大小int *p_fds;char sendchar = 0;msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf);p_cmsg = CMSG_FIRSTHDR(&msg); //通过宏获得struct cmsghdr指针p_cmsg->cmsg_level = SOL_SOCKET; //指定是socket协议p_cmsg->cmsg_type = SCM_RIGHTS; //套接字控制信息,仅UNIX域可以传递该信息p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); //用宏储存fd所需的对象长度,一般是整型+ cmsghdr长度p_fds = (int*)CMSG_DATA(p_cmsg); //获得关联数据 即fd的指针*p_fds = fd; msg.msg_name = NULL; //UNIX域 初始化为NULLmsg.msg_namelen = 0;msg.msg_iov = &vec; //初始化缓冲区buffmsg.msg_iovlen = 1;msg.msg_flags = 0; vec.iov_base = &sendchar;vec.iov_len = sizeof(sendchar);ret = sendmsg(sock_fd, &msg, 0);if (ret != 1)ERR_EXIT("sendmsg"); } /*** send_fd -向sock_fd 发送 fd* @sock_fd: 接受目标套接字* 返回目标套接字*/ int recv_fd(const int sock_fd) {int ret;struct msghdr msg;char recvchar;struct iovec vec;int recv_fd;char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))];struct cmsghdr *p_cmsg;int *p_fd;vec.iov_base = &recvchar;vec.iov_len = sizeof(recvchar);msg.msg_name = NULL;msg.msg_namelen = 0;msg.msg_iov = &vec;msg.msg_iovlen = 1;msg.msg_control = cmsgbuf;msg.msg_controllen = sizeof(cmsgbuf);msg.msg_flags = 0; p_fd = (int*)CMSG_DATA(CMSG_FIRSTHDR(&msg));*p_fd = -1; ret = recvmsg(sock_fd, &msg, 0);if (ret != 1)ERR_EXIT("recvmsg"); p_cmsg = CMSG_FIRSTHDR(&msg); //通过宏获得信息头if (p_cmsg == NULL)ERR_EXIT("no passed fd"); p_fd = (int*)CMSG_DATA(p_cmsg); //通过宏获得传输数据recv_fd = *p_fd;if (recv_fd == -1)ERR_EXIT("no passed fd"); return recv_fd; }
ftp关键技术二:nobody进程创建和使用(一)相关推荐
- 【小项目关键技术二】UGV电机编码测速
测试程序 #define PinA 2 //外部中断0 #define PinZ 3 //外部中断1 #define PinB 9 //编码器的OUT_B信号连接到数字端口8 #define T 20 ...
- 一文读懂GaussDB(openGauss) 的六大关键技术特性
摘要:更为深入地介绍了GaussDB(openGauss)的关键特性.成功案例. GaussDB(openGauss)是深度融合华为在数据库领域多年的经验,结合企业级场景需求,推出的新一代企业级分布式 ...
- 【Linux 内核】进程管理 ( 进程状态 | 进程创建 | 进程终止 | 调用 exit 系统调用函数主动退出 | main 函数返回自动退出 | kill 杀死进程 | 执行异常退出 )
文章目录 一.进程状态 二.进程创建 三.进程终止 ( 调用 exit 系统调用函数主动退出 | main 函数返回自动退出 | kill 杀死进程 | 执行异常退出 ) 一.进程状态 Linux 进 ...
- Linux进程的创建和父子进程同步,操作系统实验报告_Linux进程创建与通信.doc
操作系统实验报告_Linux进程创建与通信 2011-2012学年第一学期 专 业: 班 级: 学 号: 姓 名:提交日期:2011年11月实验二 Linux进程创建与进程通信 [实验目的 1. 熟悉 ...
- 串口转以太网关键技术
1.概述 串口转以太网目前可以采用串口转以太网模块来实现,变得非常简单易用,但是在该技术中出现的一些新问题.使用误区需要引起注意.串口转以太网并不是简单传输媒介的变化,而是串口到TCP/IP的协议转化 ...
- 物联网的概念、分层及关键技术
概念 物联网(The Internet of Things)是实现物物相连的互联网络,其内涵包含两个方面:第一,物理网的核心和基础仍然是互联网,是在互联网基础上扩展和延伸的网络:第二,其用户端延伸和扩 ...
- 网格与云计算关键技术
网格与云计算关键技术 主要内容 一,网格计算关键技术 二,云计算关键技术 三,网格计算与云计算的区别 网格计算关键技术 网格调度技术 资源管理技术 网格安全技术 网格调度技术 网格调度过程分为三个阶段 ...
- 利用钩子技术控制进程创建(附源代码)
源码下载地址:http://download.csdn.net/detail/swanabin/6575541 一. 简介 最近,我了解到一个叫做Sanctuary的相当有趣的安全产品.它能够阻止任何 ...
- 利用钩子技术控制进程创建
05年的老文章了,今天才看到,一直想做而没有做出来的东西,差距啊... 修正原文一些翻译不恰当的地方 原英文地址: http://www.codeproject.com/KB/system/sovie ...
最新文章
- idea permission denied 权限分配问题解决方案
- oralce中级OCP考试时间和题量以及通过率
- 深入理解java虚拟机 精华总结(面试)
- ROADS POJ - 1724(限制条件的最短路)【邻接表+深搜】
- 在C++函数中使用__asm int 3和__debugbreak的调试
- R语言中package ‘xxxx’ is not available (for R version 3.2.5)解决
- 51nod 1092 回文字符串【LCS】
- php 0x80004005,解决Access出现Microsoft JET Database Engine (0x80004005)未指
- 打印纸张尺寸换算_常用纸张的尺寸大小对照表-纸张规格对照表.doc
- 社区人物志|李昊鹏:日拱一卒,功不唐捐
- 使用HTML写一个简历表格
- 国家计算机职业技能鉴定4级考试内容详解
- Linux命令之远程下载命令:wget
- Activiti流创建数据表的过程中报错 java.sql.SQLSyntaxErrorException: Table ‘activiti.act_ge_property‘ doesn‘t exis
- 计算机快捷方式后缀名,电脑快捷键-文件扩展名详解.doc
- HTML(表单标签<form>)
- 计算机视觉的科学计算,计算机视觉发展的四个主要阶段1:马尔计算视觉
- 20个常用的Linux工具命令
- 一看就会(废)的最小二乘法推导
- 免费网络管理流量监控软件大比拼