0%

vsftpd FTP服务器与django前端共用sqlite3数据库的示例

  • 本示例接合vsftpd与Django共用Sqlite3(不限于sqlite3,Mysql,Postgresql)中的数据表认证做一个Web端FTP管理的应用.可以支持多用户,Web端管理帐户,上传文件,Vsftpd端只可以下载当前用户目录下的内容,
  • Web端的上传目录与Vsftpd下载目录映射到服务器的同一个目录.

Vsftpd

  • 这里使用vsftpd-3.0.2版本,这里要对它的源做一小修改,确保每一个用户登录成功之后自动创建一个名为用户名目录.这个目录专属于这个用户,用户上传下载也只限于这个目录下.

  • 源代码位置在vsftpd-3.0.2/twoprocess.c 里的 common_do_login 函数中.如下面代码片段.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
385    static void
386 common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str,
387 int do_chroot, int anon)
388 {
389 int was_anon = anon;
390 const struct mystr* p_orig_user_str = p_user_str;
391 int newpid;
392 vsf_sysutil_install_null_sighandler(kVSFSysUtilSigCHLD);
393 /* Tells the pre-login child all is OK (it may exit in response) */
394 priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_OK);
395 if (!p_sess->control_use_ssl)
396 {
397 (void) vsf_sysutil_wait();
398 }
399 else
400 {
401 p_sess->ssl_slave_active = 1;
402 }
403 /* Handle loading per-user config options */
404 handle_per_user_config(p_user_str);
405 /* Set this before we fork */
406 p_sess->is_anonymous = anon;
407 priv_sock_close(p_sess);
408 priv_sock_init(p_sess);
409 vsf_sysutil_install_sighandler(kVSFSysUtilSigCHLD, handle_sigchld, 0, 1);
410 if (tunable_isolate_network && !tunable_port_promiscuous)
411 {
412 newpid = vsf_sysutil_fork_newnet();
413 }
414 else
415 {
416 newpid = vsf_sysutil_fork();
417 }
418 if (newpid == 0)
419 {
420 struct mystr guest_user_str = INIT_MYSTR;
421 struct mystr chroot_str = INIT_MYSTR;
422 struct mystr chdir_str = INIT_MYSTR;
423 struct mystr userdir_str = INIT_MYSTR;
424 unsigned int secutil_option = VSF_SECUTIL_OPTION_USE_GROUPS |
425 VSF_SECUTIL_OPTION_NO_PROCS;
426 /* Child - drop privs and start proper FTP! */
427 /* This PR_SET_PDEATHSIG doesn't work for all possible process tree setups.
428 * The other cases are taken care of by a shutdown() of the command
429 * connection in our SIGTERM handler.
430 */
431 vsf_set_die_if_parent_dies();
432 priv_sock_set_child_context(p_sess);
433 if (tunable_guest_enable && !anon)
434 {
435 p_sess->is_guest = 1;
436 /* Remap to the guest user */
437 if (tunable_guest_username)
438 {
439 str_alloc_text(&guest_user_str, tunable_guest_username);
440 }
441 p_user_str = &guest_user_str;
442 if (!tunable_virtual_use_local_privs)
443 {
444 anon = 1;
445 do_chroot = 1;
446 }
447 }
448 if (do_chroot)
449 {
450 secutil_option |= VSF_SECUTIL_OPTION_CHROOT;
451 }
452 if (!anon)
453 {
454 secutil_option |= VSF_SECUTIL_OPTION_CHANGE_EUID;
455 }
456 if (!was_anon && tunable_allow_writeable_chroot)
457 {
458 secutil_option |= VSF_SECUTIL_OPTION_ALLOW_WRITEABLE_ROOT;
459 }
460 calculate_chdir_dir(was_anon, &userdir_str, &chroot_str, &chdir_str,
461 p_user_str, p_orig_user_str);
462
463
/* 通过我的调式这里加下面这一句,用户通过FTP端户登录成功就会创建它自已的目录*/
464 str_mkdir(&chroot_str, 0777);
465
466 chown(str_getbuf(&chroot_str),p_sess->guest_user_uid,p_sess->guest_user_uid);
467
468
469 vsf_secutil_change_credentials(p_user_str, &userdir_str, &chroot_str,
470 0, secutil_option);
471 if (!str_isempty(&chdir_str))
472 {
473 (void) str_chdir(&chdir_str);
474 }
475 str_free(&guest_user_str);
476 str_free(&chroot_str);
477 str_free(&chdir_str);
478 str_free(&userdir_str);
479 p_sess->is_anonymous = anon;
480 seccomp_sandbox_init();
481 seccomp_sandbox_setup_postlogin(p_sess);
482 seccomp_sandbox_lockdown();
483
484 process_post_login(p_sess);
485 bug("should not get here: common_do_login");
486 }
487 /* Parent */
488 priv_sock_set_parent_context(p_sess);
489 if (tunable_ssl_enable)
490 {
491 ssl_comm_channel_set_producer_context(p_sess);
492 }
493 /* The seccomp sandbox lockdown for the priv parent is done inside here */
494 vsf_priv_parent_postlogin(p_sess);
495 bug("should not get here in common_do_login");
496 }

  • 参照vsftpd-3.0.2/INSTALL 直接编译安装.

配置vsftpd

  • /etc/vsftpd.conf 修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
grep -v '^#' /etc/vsftpd.conf 
cmds_allowed=ABOR,CWD,LIST,NLST,PWD,QUIT,RETR,SIZE,TYPE,USER,ACCT,CDUP,MODE,SYST,FEAT,PASV
listen=YES
listen_ipv6=NO
anonymous_enable=NO
local_enable=YES
write_enable=NO

dirmessage_enable=YES
use_localtime=YES
xferlog_enable=NO
connect_from_port_20=YES
idle_session_timeout=600
data_connection_timeout=120
nopriv_user=vsftpd
chroot_local_user=YES
pam_service_name=vsftpd
user_sub_token=$USER
guest_enable=YES
guest_username=vsftpd
local_root=/opt/data/ftproot/$USER #所有用户目录会在这个目录下.
chroot_local_user=YES
allow_writeable_chroot=YES
chroot_list_enable=NO
virtual_use_local_privs=YES
file_open_mode=0770
anon_mkdir_write_enable=NO
ssl_enable=NO
max_per_ip=65536
log_ftp_protocol=YES
  • /etc/pam.d/vsftpd 修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Standard behaviour for ftpd(8).
#auth required pam_listfile.so item=user sense=deny file=/etc/ftpusers onerr=succeed

# Note: vsftpd handles anonymous logins on its own. Do not enable pam_ftp.so.

# Standard pam includes
#@include common-account
#@include common-session
#@include common-auth
#auth required pam_shells.so
# 如果使用其它数据库如:postgresql ,把pam_sqlite3.so替换掉
auth required /lib/x86_64-linux-gnu/security/pam_sqlite3.so
account required /lib/x86_64-linux-gnu/security/pam_sqlite3.so
password required /lib/x86_64-linux-gnu/security/pam_sqlite3.so

pam_sqlite-0.3

  • 在github上找到两个项目**libpam-sqlite,dtjm/pam_sqlite3**是关于pam_sqlite3认证的,两个项目都很老了,
  • 这两个代码应该是大同小异,我没有对比,这里用是用libpam-sqlite.也修了源码才可以使用.pam_sqlite3没有测试,应该也是可以的.
1
$ git clone git@github.com:sangeeths/libpam-sqlite.git
  • 这里的修改不一定是通用的可能要根据调试结果来决定,我这里修改是为了迎合后面的Django项目登录认证的.修改的源代如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
~/libpam-sqlite# git diff
diff --git a/pam_sqlite3.c b/pam_sqlite3.c
index 565ce30..f2545a4 100644
--- a/pam_sqlite3.c
+++ b/pam_sqlite3.c
@@ -455,7 +455,9 @@ static int auth_verify_password(const char *un,
}

/* get the encrypted password from the database */
+ //const char *stored_pwd = (char*)sqlite3_column_text(stmt, 0);
const char *stored_pwd = (char *)sqlite3_column_text(stmt, 0);
+ stored_pwd = stored_pwd +7; // skip the crypt$$

result = PAM_AUTH_ERR;
switch(options->pw_type) {
@@ -581,7 +583,7 @@ PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh,
/* if new password is required then newtok_column = 'y' or '1' */
if(options->newtok_column || options->sql_check_newtok) {
query = format_query(options->sql_check_newtok ? options->sql_check_newtok :
- "SELECT 1 FROM %Ot WHERE %Ou='%U' AND (%On='y' OR %On='1')",
+ "SELECT 1 FROM %Ot WHERE %Ou='%U' AND (%On='y' OR %On='0')",
options, un, NULL);
DBGLOG("query: %s", query);

  • 参照源码目录下的README编译与设置

pam_sqlite.conf

1
2
3
4
5
6
7
$cat /etc/pam_sqlite.conf 
database = /opt/data/db.sqlite3
table = ftp_ftpuser
user_column = email
pwd_column = password
newtok_column = is_staff
pw_type = crypt

Django配置

Sqlite3表结构

1
2
3
sqlite> .schema ftp_ftpuser
CREATE TABLE "ftp_ftpuser" ("password" varchar(128) NOT NULL, "last_login" datetime NULL, "is_superuser" bool NOT NULL, "name" varchar(256) NOT NULL, "email" varchar(254) NOT NULL PRIMARY KEY, "phone_num" varchar(15) NOT NULL, "reg_ip" char(39) NOT NULL, "is_active" bool NOT NULL, "is_staff" bool NOT NULL, "date_joined" datetime NOT NULL, "last_ip" char(39) NOT NULL);
CREATE UNIQUE INDEX "ftp_ftpuser_name_2294ce6a_uniq" ON "ftp_ftpuser" ("name", "email");

Settings

  • 在Django项目里的settings.py 添加如下代码片段.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Application definition
PASSWORD_HASHERS = (
'django.contrib.auth.hashers.CryptPasswordHasher',
)

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), #这里的**db.sqlite3** 应该与**/etc/pam_sqlite.conf**里对应
}
}


# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#authassword-validators

AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]

#AUTHENTICATION_BACKENDS = ['ftp.auth.CustomBackEnd']
AUTH_USER_MODEL='ftp.FtpUser'
MEDIA_ROOT='/opt/data/ftproot' #这里的路径与vsftpd.conf是对应的.这里Django 程序创建的目录就会在这个目录下面

  • Django 程序创建目录的代码片段如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def get_upload_dir(req,filename):
tname = req.target_name
pname = tname.product_name
fdir = '/'.join([settings.MEDIA_ROOT,pname.ftp_user.email,pname.name,tname.name,req.ver_name])
#fdir = '/'.join([settings.MEDIA_ROOT,product.ftp_user.email,req.name.name,req.target_name.target_name,req.ver_name])
fdir = unicode(fdir)
if os.path.exists(fdir):
try:
os.makedirs(fdir)
except OSError:
pass
filepath = '/'.join([fdir,req.file_name.name])
if os.path.isfile(filepath):
os.remove(filepath)
return filepath

总结

  • 这里的数据库表结构都是通过Django的模型创建的,通过Django端注册用户,设置相应权限(如是否允许该用户登录等.),FTP端就可以使用该用户登录服务器进行下载.具体应用要根据需求灵活改变,这里只是做了一个简单示例,
  • 也没有添加SSL加密连接.后续加入SSL支持.

谢谢支持