Linux网络编程综合运用之MiniFtp实现(七)
上节中实现了配置文件的解析,这节来实现用户登录的验证,首先用客户端来登录vsftpd来演示登录的过程:
接着再连接miniftpd,来看下目前的效果:
接下来实现它,与协议相关的模块都是在ftpproto.c中完成的,目前的代码如下:
#include "ftpproto.h"
#include "sysutil.h"
#include "str.h" void handle_child(session_t *sess)
{
writen(sess->ctrl_fd, "220 (miniftpd 0.1)\r\n", strlen("220 (miniftpd 0.1)\r\n"));
int ret;
while ()
{
memset(sess->cmdline, , sizeof(sess->cmdline));
memset(sess->cmd, , sizeof(sess->cmd));
memset(sess->arg, , sizeof(sess->arg));
ret = readline(sess->ctrl_fd, sess->cmdline, MAX_COMMAND_LINE);
if (ret == -)
ERR_EXIT("readline");
else if (ret == )
exit(EXIT_SUCCESS); printf("cmdline=[%s]\n", sess->cmdline);
// 去除\r\n
str_trim_crlf(sess->cmdline);
printf("cmdline=[%s]\n", sess->cmdline);
// 解析FTP命令与参数
str_split(sess->cmdline, sess->cmd, sess->arg, ' ');
printf("cmd=[%s] arg=[%s]\n", sess->cmd, sess->arg);
// 将命令转换为大写
str_upper(sess->cmd);
// 处理FTP命令
}
}
下面则开始编写逻辑:
#include "ftpproto.h"
#include "sysutil.h"
#include "str.h" void do_user(session_t *sess);
void do_pass(session_t *sess); void handle_child(session_t *sess)
{
writen(sess->ctrl_fd, "220 (miniftpd 0.1)\r\n", strlen("220 (miniftpd 0.1)\r\n"));
int ret;
while ()
{
memset(sess->cmdline, , sizeof(sess->cmdline));
memset(sess->cmd, , sizeof(sess->cmd));
memset(sess->arg, , sizeof(sess->arg));
ret = readline(sess->ctrl_fd, sess->cmdline, MAX_COMMAND_LINE);
if (ret == -)
ERR_EXIT("readline");
else if (ret == )
exit(EXIT_SUCCESS); printf("cmdline=[%s]\n", sess->cmdline);
// 去除\r\n
str_trim_crlf(sess->cmdline);
printf("cmdline=[%s]\n", sess->cmdline);
// 解析FTP命令与参数
str_split(sess->cmdline, sess->cmd, sess->arg, ' ');
printf("cmd=[%s] arg=[%s]\n", sess->cmd, sess->arg);
// 将命令转换为大写
str_upper(sess->cmd);
// 处理FTP命令
if (strcmp("USER", sess->cmd) == 0)
{
do_user(sess);
}
else if (strcmp("PASS", sess->cmd) == 0)
{
do_pass(sess);
}
}
} void do_user(session_t *sess)
{
//USER jjl
} void do_pass(session_t *sess)
{
// PASS 123456
}
由于do_user和do_pass函数只为ftpproto.c内部使用,所以可以将其声明为static,如下:
#include "ftpproto.h"
#include "sysutil.h"
#include "str.h" static void do_user(session_t *sess);
static void do_pass(session_t *sess); void handle_child(session_t *sess)
{
writen(sess->ctrl_fd, "220 (miniftpd 0.1)\r\n", strlen("220 (miniftpd 0.1)\r\n"));
int ret;
while ()
{
memset(sess->cmdline, , sizeof(sess->cmdline));
memset(sess->cmd, , sizeof(sess->cmd));
memset(sess->arg, , sizeof(sess->arg));
ret = readline(sess->ctrl_fd, sess->cmdline, MAX_COMMAND_LINE);
if (ret == -)
ERR_EXIT("readline");
else if (ret == )
exit(EXIT_SUCCESS); printf("cmdline=[%s]\n", sess->cmdline);
// 去除\r\n
str_trim_crlf(sess->cmdline);
printf("cmdline=[%s]\n", sess->cmdline);
// 解析FTP命令与参数
str_split(sess->cmdline, sess->cmd, sess->arg, ' ');
printf("cmd=[%s] arg=[%s]\n", sess->cmd, sess->arg);
// 将命令转换为大写
str_upper(sess->cmd);
// 处理FTP命令
if (strcmp("USER", sess->cmd) == 0)
{
do_user(sess);
}
else if (strcmp("PASS", sess->cmd) == 0)
{
do_pass(sess);
}
}
} static void do_user(session_t *sess)
{
//USER jjl
} static void do_pass(session_t *sess)
{
// PASS 123456
}
对于这两个命令的处理,则按vsftpd的来实现:
static void do_user(session_t *sess)
{
//USER jjl
writen(sess->ctrl_fd, "331 Please specify the password.\r\n", strlen("331 Please specify the password.\r\n"));
}
编译运行看下效果:
接下来处理do_pass(),继续来看一下正常的登录流程:
static void do_pass(session_t *sess)
{
// PASS 123456
writen(sess->ctrl_fd, "230 Login successful.\r\n", strlen("230 Login successful.\r\n"));
}
编译运行:
当然这里还没有对用户名和密码进行真实的校验,下面则开始实现,先看下密码出错,vsftpd会有什么反馈:
如果故意将用户名输错呢?
依照上面的结果下面来进行用户名与密码的校验:
首先校验用户名,得根据用户名来获取系统相关信息,可以通过如下函数获取:
通过这个函数就可以判断用户存不存在,具体判断如下:
在上面这段代码中,我们发现代码不够精简灵活,每次服务端响应给客户端时,都要加一段字符如下:
像上面这些代码是有规律的,有必要封装成一个方法,一个参数传递代码,如:530;一个参数传递文本,如:Login incorrect.,然后在方法里面来生成上面的那些代码,所以声明一个方法:
#include "ftpproto.h"
#include "sysutil.h"
#include "str.h" static void ftp_reply(session_t *sess, int status, const char *text);//由于也是内部使用,所以声明成static
static void do_user(session_t *sess);
static void do_pass(session_t *sess); void handle_child(session_t *sess)
{
writen(sess->ctrl_fd, "220 (miniftpd 0.1)\r\n", strlen("220 (miniftpd 0.1)\r\n"));
int ret;
while ()
{
memset(sess->cmdline, , sizeof(sess->cmdline));
memset(sess->cmd, , sizeof(sess->cmd));
memset(sess->arg, , sizeof(sess->arg));
ret = readline(sess->ctrl_fd, sess->cmdline, MAX_COMMAND_LINE);
if (ret == -)
ERR_EXIT("readline");
else if (ret == )
exit(EXIT_SUCCESS); printf("cmdline=[%s]\n", sess->cmdline);
// 去除\r\n
str_trim_crlf(sess->cmdline);
printf("cmdline=[%s]\n", sess->cmdline);
// 解析FTP命令与参数
str_split(sess->cmdline, sess->cmd, sess->arg, ' ');
printf("cmd=[%s] arg=[%s]\n", sess->cmd, sess->arg);
// 将命令转换为大写
str_upper(sess->cmd);
// 处理FTP命令
if (strcmp("USER", sess->cmd) == )
{
do_user(sess);
}
else if (strcmp("PASS", sess->cmd) == )
{
do_pass(sess);
}
}
} static void do_user(session_t *sess)
{
//USER jjl
struct passwd *pw = getpwnam(sess->arg);
if (pw == NULL)
{
// 用户不存在
writen(sess->ctrl_fd, "530 Login incorrect.\r\n", strlen("530 Login incorrect.\r\n"));
return;
}
writen(sess->ctrl_fd, "331 Please specify the password.\r\n", strlen("331 Please specify the password.\r\n"));
} static void do_pass(session_t *sess)
{
// PASS 123456
writen(sess->ctrl_fd, "230 Login successful.\r\n", strlen("230 Login successful.\r\n"));
} static void ftp_reply(session_t *sess, int status, const char *text)
{ }
具体实现如下:
static void ftp_reply(session_t *sess, int status, const char *text)
{
char buf[] = {};
sprintf(buf, "%d %s\r\n", status, text);
writen(sess->ctrl_fd, buf, strlen(buf));
}
接着用这个方法来将代码进行替换,如下:
而对于上面的状态码,可以定义成常量,对于FTP有很多状态码,所以这里可以统一定义在一个头文件中,然后以后用它进行替换:
ftpcodes.h:
#ifndef _FTP_CODES_H_
#define _FTP_CODES_H_ #define FTP_DATACONN 150 #define FTP_NOOPOK 200
#define FTP_TYPEOK 200
#define FTP_PORTOK 200
#define FTP_EPRTOK 200
#define FTP_UMASKOK 200
#define FTP_CHMODOK 200
#define FTP_EPSVALLOK 200
#define FTP_STRUOK 200
#define FTP_MODEOK 200
#define FTP_PBSZOK 200
#define FTP_PROTOK 200
#define FTP_OPTSOK 200
#define FTP_ALLOOK 202
#define FTP_FEAT 211
#define FTP_STATOK 211
#define FTP_SIZEOK 213
#define FTP_MDTMOK 213
#define FTP_STATFILE_OK 213
#define FTP_SITEHELP 214
#define FTP_HELP 214
#define FTP_SYSTOK 215
#define FTP_GREET 220
#define FTP_GOODBYE 221
#define FTP_ABOR_NOCONN 225
#define FTP_TRANSFEROK 226
#define FTP_ABOROK 226
#define FTP_PASVOK 227
#define FTP_EPSVOK 229
#define FTP_LOGINOK 230
#define FTP_AUTHOK 234
#define FTP_CWDOK 250
#define FTP_RMDIROK 250
#define FTP_DELEOK 250
#define FTP_RENAMEOK 250
#define FTP_PWDOK 257
#define FTP_MKDIROK 257 #define FTP_GIVEPWORD 331
#define FTP_RESTOK 350
#define FTP_RNFROK 350 #define FTP_IDLE_TIMEOUT 421
#define FTP_DATA_TIMEOUT 421
#define FTP_TOO_MANY_USERS 421
#define FTP_IP_LIMIT 421
#define FTP_IP_DENY 421
#define FTP_TLS_FAIL 421
#define FTP_BADSENDCONN 425
#define FTP_BADSENDNET 426
#define FTP_BADSENDFILE 451 #define FTP_BADCMD 500
#define FTP_BADOPTS 501
#define FTP_COMMANDNOTIMPL 502
#define FTP_NEEDUSER 503
#define FTP_NEEDRNFR 503
#define FTP_BADPBSZ 503
#define FTP_BADPROT 503
#define FTP_BADSTRU 504
#define FTP_BADMODE 504
#define FTP_BADAUTH 504
#define FTP_NOSUCHPROT 504
#define FTP_NEEDENCRYPT 522
#define FTP_EPSVBAD 522
#define FTP_DATATLSBAD 522
#define FTP_LOGINERR 530
#define FTP_NOHANDLEPROT 536
#define FTP_FILEFAIL 550
#define FTP_NOPERM 550
#define FTP_UPLOADFAIL 553 #endif /* _FTP_CODES_H_ */
然后包含该头文件:
将其中写死的状态码改成常量:
再来编译运行:
接下来则继续做用户名和密码的验证:
这时可以将passwd中的user ID字段保存到session结构体中,便于验证密码时使用:
所以需要修改session结构体:
同时得修改对session的初始化:
然后在用户名验证成功之后则将值保存在session中:
接着来验证密码,再一次对用户名进行验证,根据刚才保存的uid,通过如下函数:
具体如下:
接下来需要获取用户所对应的密码,而它是保存在影子文件当中的,这里可以通过另外一个系统函数获得:
而其中的参数name就是传递用户名:
所以代码如下:
接下来需要用客户端传过来的用户密码跟影子文件中的密码进行比较:
而FTP客户端发过来的密码是明文的,所以在比较之前首先需要对密码进行加密,这里需要用到另外一个系统函数:
具体代码如下:
接下来则就可以进行密码匹对了:
接下来编译运行一下:
将头文件放到common.h中:
再次编辑运行:
原因是:
所以需要修改一下Makefile文件:
.PHONY:clean
CC=gcc
CFLAGS=-Wall -g
BIN=miniftpd
OBJS=main.o sysutil.o session.o ftpproto.o privparent.o str.o tunable.o parseconf.o
LIBS=-lcrypt $(BIN):$(OBJS)
$(CC) $(CFLAGS) $^ -o $@ $(LIBS)
%.o:%.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f *.o $(BIN)
这时再编译运行一下:
由于输出530的地方有多处,所以先打一个log来定位一下看是哪出现的530:
static void do_user(session_t *sess)
{
//USER jjl
struct passwd *pw = getpwnam(sess->arg);
if (pw == NULL)
{
// 用户不存在
ftp_reply(sess, FTP_LOGINERR, "1Login incorrect.");
return;
}
sess->uid = pw->pw_uid;
ftp_reply(sess, FTP_GIVEPWORD, "Please specify the password.");
} static void do_pass(session_t *sess)
{
// PASS 123456
struct passwd *pw = getpwuid(sess->uid);
if (pw == NULL)
{
// 用户不存在
ftp_reply(sess, FTP_LOGINERR, "2Login incorrect.");
return;
} printf("name=[%s]\n", pw->pw_name);
struct spwd *sp = getspnam(pw->pw_name);
if (sp == NULL)
{ // 没有找到影子文件,则代表登录也是失败的
ftp_reply(sess, FTP_LOGINERR, "3Login incorrect.");
return;
} // 将明文进行加密
char *encrypted_pass = crypt(sess->arg, sp->sp_pwdp);
// 验证密码
if (strcmp(encrypted_pass, sp->sp_pwdp) != )
{
ftp_reply(sess, FTP_LOGINERR, "4Login incorrect.");
return;
}
ftp_reply(sess, FTP_LOGINOK, "Login successful.");
}
再来运行看输出结果:
也就是这段代码出问题了:
而从输出结果来看,其pw->pw_name也是正常的:
这是为什么呢?猜有可能是没有权限访问getspnam函数来获得影子文件,因为我们是用root用户执行miniftpd程序,照道理是有权限访问,所以这里需要从头来查找原因,下面来从头将流程梳理一下:
进而转到session模块来了,往下看,这里貌似就发现问题了:
在正式解决之前,咱们先来看一下目前进程分布:
这样肯定就没法得到影子文件了,所以接下来解决它,方法其实很简单:
接下来再编译运行:
而如果故意将密码输错,会提示登录失败么?
这时看效果:
这时可再查看下进程状态:
而vsftpd的进程状态呢?
而我们的FTP服务进程是root用户,这是不对的,应该变成实际登录用户的进程,所以需要调整一下代码,在调整之前需要将调试log恢复:
再次编译运行,查看miniftpd的进程状态:
这次先学到这,里面用到很多系统函数,需要熟悉一下,下回继续~
Linux网络编程综合运用之MiniFtp实现(七)的更多相关文章
- Linux网络编程综合运用之MiniFtp实现(一)
春节过后,万物复苏,在这元宵佳节的前一天,决定继续开启新年的学习计划,生命在于运动,提高源于学习,在经过漫长的Linux网络编程学习后,接下来会以一个综合的小项目来将所学的知识点综合运用,首先是对项目 ...
- Linux网络编程综合运用之MiniFtp实现(四)
从今天开始,正式进入MiniFtp的代码编写阶段了,好兴奋,接下来很长一段时间会将整个实现过程从无到有一点点实现出来,达到综合应用的效果,话不多说正入正题: 这节主要是将基础代码框架搭建好,基于上节介 ...
- Linux网络编程综合运用之MiniFtp实现(九)
上次中实现了FTP命令的映射来避免很多if....else的判断,这次主要是开始实现目录列表的传输,先看一下目前实现的: 数据连接创建好之后则开始进行目录列表的传输了,而要传输目录列表,首先要将目录列 ...
- Linux网络编程综合运用之MiniFtp实现(五)
转眼兴奋的五一小长假就要到来了,在放假前夕还是需要保持一颗淡定的心,上次中已经对miniFTP有基础框架进行了搭建,这次继续进行往上加代码,这次主要还是将经历投射到handle_child()服务进程 ...
- Linux网络编程综合运用之MiniFtp实现(八)
上节中实现了"USER"和"PASS"命令,如下: 事实上FTP是有很多命令组成的,如果就采用上面的这种方法来实现的话,就会有很多if...else if语句, ...
- Linux网络编程综合运用之MiniFtp实现(六)
间隔了一周时间没写了,由于今年的股势行情貌似不错的样子,对于对股市完全不懂的我也在蠢蠢欲动,所以最近一周业余时间在“不务正业”-----学习炒股.发现学习它其实挺费神的,满脑子都是走势图,而且是神经有 ...
- Linux网络编程综合运用之MiniFtp实现(三)
前面已经对FTP相关的一些概念有了基本的认识,接下来就要进入代码编写阶段了,也是非常兴奋的阶段,在开启这个它之前先对项目需求进行一个梳理,对其我们要实现的FTP服务器是一个什么样子. ftp命令列表 ...
- 【深入浅出Linux网络编程】 "开篇 -- 知其然,知其所以然"
[深入浅出Linux网络编程]是一个连载博客,内容源于本人的工作经验,旨在给读者提供靠谱高效的学习途径,不必在零散的互联网资源中浪费精力,快速的掌握Linux网络编程. 连载包含4篇,会陆续编写发出, ...
- 【linux草鞋应用编程系列】_5_ Linux网络编程
一.网络通信简介 第一部分内容,暂时没法描述,内容实在太多,待后续专门的系列文章. 二.linux网络通信 在linux中继承了Unix下“一切皆文件”的思想, 在linux中要实现网 ...
随机推荐
- 16、vue引入echarts,划中国地图
vue引入echarts npm install echarts --save main.js引入 import echarts from 'echarts' Vue.prototype.$echar ...
- hadoop(四)MapReduce
如果将 Hadoop 比做一头大象,那么 MapReduce 就是那头大象的电脑.MapReduce 是 Hadoop 核心编程模型.在 Hadoop 中,数据处理核心就是 MapReduce 程序设 ...
- RocketMQ控制台命令
本文未完成,有空再补充,不小心发布了,抱歉 Rocket版本:4.3.0 这几天在整RocketMQ,可谓是困难重重,其中关于控制台的命令,网上的都是一半一半的, 所以我打算直接用整一个完整的官方的命 ...
- NAT的配置
实验的拓扑图如下所示 首先我们对路由器进行基础的地址配置 我们先在R2路由器上设置一条连接外网的静态路由 然后我们给PC1设置一个静态NAT 然后使得PC1 通过202.169.10.5 地址访问外网 ...
- 使用Docker-Compose编排发布.Net Core+Redis应用两个镜像到Docker
对于刚刚完成的Alipay支的Demo, 我想要把它部署到Docker中去, 下面我来演示相关步骤. 创建配置文件 配置文件的重中之重是Dockerfile, 他的内容如下: # 第一部分是编译并发布 ...
- C++实现2048小游戏
代码如下: #define _CRT_SECURE_NO_WARNINGS//去掉编译器内部扩增问题 #include<stdio.h> #include<stdlib.h> ...
- 17 SUMIF函数、countif函数、averagif函数
情景 按买家求他们的消费各是多少. 可以考虑使用分类汇总来做,但这里我们使用函数sumif来做. SUMIF函数 格式:=SUMIF(条件列表,匹配条件,数据区) 该函数这样理解:按照匹配条件,从条件 ...
- WUSTOJ 1346: DARK SOULS(Java)并查集
题目链接:1346: DARK SOULS 并查集系列:WUSTOJ 1319: 球(Java)并查集 Description CQ最近在玩一款游戏:DARK SOULS,这是一款以高难度闻名的硬派动 ...
- C++语言类之间的关系
在c++中通过类定义对象,而类与类之间也有着复杂的关系,所以题外话,我能理解到c++的编写者可能就想通过计算机语言去模拟世界万物之间的关系,这篇帖子主要从横向和纵向去讨论类之间关系 而在一个类中想要使 ...
- flex左右布局 左边固定 右侧自适应
flex左右布局 左边固定 右侧自适应 想要保证自适应内容不超出容器怎么办. 通过为自适应的一侧设置width: 0;或者overflow: hidden;解决. 首先实现标题的布局,也很简单: &l ...