文章目录

  • ​​1.实现客户端发送过来的某些应答​​

1.实现客户端发送过来的某些应答

  • eg:
    ftpproto.h
#ifndef _FTP_PROTO_H_
#define _FTP_PROTO_H_

#include "session.h"

void handle_child(session_t *sess);

#endif /* _FTP_PROTO_H_ */

ftpproto.c

#include "ftpproto.h"
#include "sysutil.h"
#include "str.h"
#include "ftpcodes.h"

void ftp_reply(session_t *sess, int status, const char *text);
static void do_user(session_t *sess);
static void do_pass(session_t *sess);

void handle_child(session_t *sess)
{
ftp_reply(sess, FTP_GREET, "(miniftpd 0.1)");
int ret;
while (1)
{
memset(sess->cmdline, 0, sizeof(sess->cmdline));
memset(sess->cmd, 0, sizeof(sess->cmd));
memset(sess->arg, 0, sizeof(sess->arg));
ret = readline(sess->ctrl_fd, sess->cmdline, MAX_COMMAND_LINE);
if (ret == -1)
ERR_EXIT("readline");
else if (ret == 0)
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 ftp_reply(session_t *sess, int status, const char *text)
{
char buf[1024] = {0};
sprintf(buf, "%d %s\r\n", status, text);
writen(sess->ctrl_fd, buf, strlen(buf));
}

//static表面:该函数只能用于当前模块
static void do_user(session_t *sess)
{
//USER jjl
//getpwnam依据用户名,可以获取到密码的结构体
struct passwd *pw = getpwnam(sess->arg);//man getpwnam,这与cat /etc/passwd文件相对应
if (pw == NULL)//获取失败
{
// 用户不存在
ftp_reply(sess, FTP_LOGINERR, "Login 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
//getpwuid根据uid得到passwd结构体
struct passwd *pw = getpwuid(sess->uid);//man getpwuid
if (pw == NULL)
{
// 用户不存在
ftp_reply(sess, FTP_LOGINERR, "Login incorrect.");
return;
}

printf("name=[%s]\n", pw->pw_name);
//getspnam依据用户名,来获取影子passwd文件信息
//pw->pw_name是用户名
struct spwd *sp = getspnam(pw->pw_name);//获取/etc/passwd文件信息
if (sp == NULL)
{
ftp_reply(sess, FTP_LOGINERR, "Login incorrect.");
return;
}

// 将明文进行加密
//加密crypt(要加密的明文,种子),将加密过的密码做为种子,得到一个加密的密码
char *encrypted_pass = crypt(sess->arg, sp->sp_pwdp);
// 验证密码
if (strcmp(encrypted_pass, sp->sp_pwdp) != 0)
{
ftp_reply(sess, FTP_LOGINERR, "Login incorrect.");
return;
}

//登录成功后,需要左的动作,下面的参数可以从man getpwnam中获取到
setegid(pw->pw_gid);
seteuid(pw->pw_uid);
chdir(pw->pw_dir);
ftp_reply(sess, FTP_LOGINOK, "Login successful.");
}

session.h

#ifndef _SESSION_H_
#define _SESSION_H_

#include "common.h"

typedef struct session
{
// 控制连接
uid_t uid;
int ctrl_fd;
char cmdline[MAX_COMMAND_LINE];
char cmd[MAX_COMMAND];
char arg[MAX_ARG];

// 父子进程通道
int parent_fd;
int child_fd;
} session_t;

void begin_session(session_t *sess);

#endif /* _SESSION_H_ */

session.c

#include "common.h"
#include "session.h"
#include "ftpproto.h"
#include "privparent.h"

void begin_session(session_t *sess)
{
int sockfds[2];
if (socketpair(PF_UNIX, SOCK_STREAM, 0, sockfds) < 0)
ERR_EXIT("socketpair");


pid_t pid;
pid = fork();
if (pid < 0)
ERR_EXIT("fork");

if (pid == 0)
{
// ftp服务进程
close(sockfds[0]);
sess->child_fd = sockfds[1];
handle_child(sess);
}
else
{
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");
// nobody进程
close(sockfds[1]);
sess->parent_fd = sockfds[0];
handle_parent(sess);
}
}

main.c

#include "common.h"
#include "sysutil.h"
#include "session.h"
#include "str.h"
#include "tunable.h"
#include "parseconf.h"

/*
typedef struct session
{
// 控制连接
char cmdline[MAX_COMMAND_LINE];
char cmd[MAX_COMMAND];
char arg[MAX_ARG];

// 父子进程通道
int parent_fd;
int child_fd;
} session_t;
*/


int main(void)
{
// 字符串测试
/*
char *str1 = " a b";
char *str2 = " ";

if (str_all_space(str1))
printf("str1 all space\n");
else
printf("str1 not all space\n");

if (str_all_space(str2))
printf("str2 all space\n");
else
printf("str2 not all space\n");

//char *str3 = "abcDef"; // 指针指向一个字符串常量,常量不能被修改
char str3[] = "abcDef";
str_upper(str3);
printf("str3=%s\n", str3);

long long result = str_to_longlong("12345678901234");
printf("result = %lld\n", result);


int n = str_octal_to_uint("711");
printf("n=%d\n", n);
*/

parseconf_load_file(MINIFTP_CONF);

printf("tunable_pasv_enable=%d\n", tunable_pasv_enable);
printf("tunable_port_enable=%d\n", tunable_port_enable);

printf("tunable_listen_port=%u\n", tunable_listen_port);
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);
printf("tunable_connect_timeout=%u\n", tunable_connect_timeout);
printf("tunable_idle_session_timeout=%u\n", tunable_idle_session_timeout);
printf("tunable_data_connection_timeout=%u\n", tunable_data_connection_timeout);
printf("tunable_local_umask=0%o\n", tunable_local_umask);
printf("tunable_upload_max_rate=%u\n", tunable_upload_max_rate);
printf("tunable_download_max_rate=%u\n", tunable_download_max_rate);

if (tunable_listen_address == NULL)
printf("tunable_listen_address=NULL\n");
else
printf("tunable_listen_address=%s\n", tunable_listen_address);


if (getuid() != 0)
{
fprintf(stderr, "miniftpd: must be started as root\n");
exit(EXIT_FAILURE);
}

/*
typedef struct session
{
// 控制连接
uid_t uid;
int ctrl_fd;
char cmdline[MAX_COMMAND_LINE];
char cmd[MAX_COMMAND];
char arg[MAX_ARG];

// 父子进程通道
int parent_fd;
int child_fd;
} session_t;
*/
session_t sess =
{
/* 控制连接 */
0, -1, "", "", "",
/* 父子进程通道 */
-1, -1
};

int listenfd = tcp_server(tunable_listen_address, tunable_listen_port);
int conn;
pid_t pid;

while (1)
{
conn = accept_timeout(listenfd, NULL, 0);
if (conn == -1)
ERR_EXIT("accept_tinmeout");

pid = fork();
if (pid == -1)
ERR_EXIT("fork");

if (pid == 0)
{
close(listenfd);
sess.ctrl_fd = conn;
begin_session(&sess);
}
else
close(conn);
}
return 0;
}

ftpcodes.h

#ifndef _FTP_PROTO_H_
#define _FTP_PROTO_H_

#include "session.h"

void handle_child(session_t *sess);

#endif /* _FTP_PROTO_H_ */

ftpproto.c

#include "ftpproto.h"
#include "sysutil.h"
#include "str.h"
#include "ftpcodes.h"

void ftp_reply(session_t *sess, int status, const char *text);
static void do_user(session_t *sess);
static void do_pass(session_t *sess);

void handle_child(session_t *sess)
{
ftp_reply(sess, FTP_GREET, "(miniftpd 0.1)");
int ret;
while (1)
{
memset(sess->cmdline, 0, sizeof(sess->cmdline));
memset(sess->cmd, 0, sizeof(sess->cmd));
memset(sess->arg, 0, sizeof(sess->arg));
ret = readline(sess->ctrl_fd, sess->cmdline, MAX_COMMAND_LINE);
if (ret == -1)
ERR_EXIT("readline");
else if (ret == 0)
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 ftp_reply(session_t *sess, int status, const char *text)
{
char buf[1024] = {0};
sprintf(buf, "%d %s\r\n", status, text);
writen(sess->ctrl_fd, buf, strlen(buf));
}

//static表面:该函数只能用于当前模块
static void do_user(session_t *sess)
{
//USER jjl
//getpwnam依据用户名,可以获取到密码的结构体
struct passwd *pw = getpwnam(sess->arg);//man getpwnam,这与cat /etc/passwd文件相对应
if (pw == NULL)//获取失败
{
// 用户不存在
ftp_reply(sess, FTP_LOGINERR, "Login 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
//getpwuid根据uid得到passwd结构体
struct passwd *pw = getpwuid(sess->uid);//man getpwuid
if (pw == NULL)
{
// 用户不存在
ftp_reply(sess, FTP_LOGINERR, "Login incorrect.");
return;
}

printf("name=[%s]\n", pw->pw_name);
//getspnam依据用户名,来获取影子passwd文件信息
//pw->pw_name是用户名
struct spwd *sp = getspnam(pw->pw_name);//获取/etc/passwd文件信息
if (sp == NULL)
{
ftp_reply(sess, FTP_LOGINERR, "Login incorrect.");
return;
}

// 将明文进行加密
//加密crypt(要加密的明文,种子),将加密过的密码做为种子,得到一个加密的密码
char *encrypted_pass = crypt(sess->arg, sp->sp_pwdp);
// 验证密码
if (strcmp(encrypted_pass, sp->sp_pwdp) != 0)
{
ftp_reply(sess, FTP_LOGINERR, "Login incorrect.");
return;
}

//登录成功后,需要左的动作,下面的参数可以从man getpwnam中获取到
setegid(pw->pw_gid);
seteuid(pw->pw_uid);
chdir(pw->pw_dir);
ftp_reply(sess, FTP_LOGINOK, "Login successful.");
}

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_ */

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)

下面的文件与上一节一样,不再列出
privparent.h
privparent.c
tunable.h
tunable.c
sysutil.h
sysutil.c
str.h
str.c
miniftpd.conf

  • 测试
    客户端连接miniftpd

服务端,当前的进程状况是:

第一个是root进程,

第二个进程是:父进程是nobody进程,

第三个进程是:ftpd服务进程

(P07)miniftpd项目实战:实现客户端发送过来的某些应答_#include


(P07)miniftpd项目实战:实现客户端发送过来的某些应答_#define_02