上次已经实现了将当前目录打印出来的效果,这次则实现这些列表信息显示在FTP客户端中,先将测试代码注释掉:
在实现之前,还是先来看下vsftpd的效果:
这里先实现POST主动模式,上面就将目录列表显示给了FTP客户端,在显示目录列表之前,首先需要创建一个数据连接。主动模式是先发送一个PORT命令,紧接着是LIST的命令:
下面来照着实现,在实现之前,先来看下目前miniftpd的效果,先将站点配置成PORT主动模式:
再来连接:
【函数说明】:sscanf() - 从一个字符串中读进与指定格式相符的数据。
然后将这个地址信息先存起来,所以先在Session结构体中增加一个变量:
然后对其进行初始化:
接着将这个地址信息实例化:
接着来给客户端响应200:
这时do_port方法就实现完了,编译运行一下:
下面先来回顾一下PORT主动模式的步骤:
接着按着第三步来实现LIST命令,该函数的实现可以分为以下几步:
接着来实现创建数据连接的方法,第一步需要进行判断:
下面来实现port_active的判断:
接下来回到get_transfer_fd()函数:
其中服务器POST主动模式需要主动绑定20端口号,但是这里会存在一个问题,调用tcp_client(20)的是在FTP服务器进程,当一个用户,比如说webor2006登录时,会将这个进程的用户ID和组ID改变为webor2006所对应的UID和GID,这时它是没有权限绑定20端口号的,之前也描述过,一个客户端过来,应该采取两个进程的方式:nobody进程用于协助数据连接的创建,这里先以一个进程的方式实现,之后会改用两人进程,所以这里先不传端口号:
这时将这个数据套接字绑定到session当中,所以需要再新建一个变量:
接下来回到do_list()这个函数继续实现:
接下来需要传输列表:
实现也得修加修改:
int list_common(session_t *sess)
{
//打开当前目录
DIR *dir = opendir(".");
if (dir == NULL)
{
return 0;
}
//读取目录并进行遍历
struct dirent *dt;
struct stat sbuf;
while ((dt = readdir(dir)) != NULL)
{ //获取文件的状态
if (lstat(dt->d_name, &sbuf) < 0)
{
continue;
}
if (dt->d_name[0] == '.')
continue;
char perms[] = "----------";
perms[0] = '?';
//获取文件类型
mode_t mode = sbuf.st_mode;
switch (mode & S_IFMT)
{
case S_IFREG://普通文件
perms[0] = '-';
break;
case S_IFDIR://目录文件
perms[0] = 'd';
break;
case S_IFLNK://链接文件
perms[0] = 'l';
break;
case S_IFIFO://管道文件
perms[0] = 'p';
break;
case S_IFSOCK://套接字文件
perms[0] = 's';
break;
case S_IFCHR://字符设备文件
perms[0] = 'c';
break;
case S_IFBLK://块设备文件
perms[0] = 'b';
break;
}
//获取文件9个权限位
if (mode & S_IRUSR)
{
perms[1] = 'r';
}
if (mode & S_IWUSR)
{
perms[2] = 'w';
}
if (mode & S_IXUSR)
{
perms[3] = 'x';
}
if (mode & S_IRGRP)
{
perms[4] = 'r';
}
if (mode & S_IWGRP)
{
perms[5] = 'w';
}
if (mode & S_IXGRP)
{
perms[6] = 'x';
}
if (mode & S_IROTH)
{
perms[7] = 'r';
}
if (mode & S_IWOTH)
{
perms[8] = 'w';
}
if (mode & S_IXOTH)
{
perms[9] = 'x';
}
//获取特珠权限位
if (mode & S_ISUID)
{
perms[3] = (perms[3] == 'x') ? 's' : 'S';
}
if (mode & S_ISGID)
{
perms[6] = (perms[6] == 'x') ? 's' : 'S';
}
if (mode & S_ISVTX)
{
perms[9] = (perms[9] == 'x') ? 't' : 'T';
}
//格式化信息
char buf[1024] = {0};
int off = 0;
off += sprintf(buf, "%s ", perms);//连接权限位
off += sprintf(buf + off, " %3d %-8d %-8d ", sbuf.st_nlink, sbuf.st_uid, sbuf.st_gid);//连接连接数、uid、gid
off += sprintf(buf + off, "%8lu ", (unsigned long)sbuf.st_size);//连接文件大小,以8位的长度展现
const char *p_date_format = "%b %e %H:%M";
struct timeval tv;
gettimeofday(&tv, NULL);
time_t local_time = tv.tv_sec;
if (sbuf.st_mtime > local_time || (local_time - sbuf.st_mtime) > 60*60*24*182)
{
p_date_format = "%b %e %Y";
}
char datebuf[64] = {0};
struct tm* p_tm = localtime(&local_time);
strftime(datebuf, sizeof(datebuf), p_date_format, p_tm);
off += sprintf(buf + off, "%s ", datebuf);
if (S_ISLNK(sbuf.st_mode))
{
char tmp[1024] = {0};
readlink(dt->d_name, tmp, sizeof(tmp));
off += sprintf(buf + off, "%s -> %s\r\n", dt->d_name, tmp);
}
else
{
off += sprintf(buf + off, "%s\r\n", dt->d_name);
}
//printf("%s", buf);
writen(sess->data_fd, buf, strlen(buf));//由原来的打印在屏幕中,改为输出到数据套接字中
}
return 1;
}
另外在关闭数据套接字之后,将数据还原成默认值:
另外在get_transfer_fd函数中,有个内存需要释放一下:
至此PORT目录列表显示的代码已经写完,下面编译运行看下效果:
这里有几个是需要注意的:
①、如果没有关闭套接字,目录列表是没法显示滴:
所以需要注意,将代码还原。
②、目前无法绑定20端:
编译运行:
好了,这次先学到这,下次继续~~