本实验使用了VMware来搭建虚拟化环境,在其之上,安装Ubuntu16.04LTS操作系统作为实验平台。
实验设计了一种Linux操作系统的动态登陆验证方式,在常规的密码验证之前先进行动态验证。实验使用向个人邮箱(QQ邮箱)发送实时验证码的方式,来实现动态验证。验证码由6至8位字符组成,字符均从数字、大小写字母、'$'、'@'中随机选取。如果连续3次错误输入验证码,系统会自动重发一个新的验证码到邮箱;验证码的有效时间为3分钟,若验证码超时失效,当用户输入失效验证码后,系统会自动重发新的验证码到邮箱。实验还针对不同用户登录,设置了使用不同的邮箱来进行验证登录,使用root用户设置用户对应的邮箱地址,普通用户无权限进行修改。实验使用的编程语言是Linux环境下的C语言。
1. 环境/工具配置
1.1 pam编译环境配置
使用命令sudo apt-get install libpam0g-dev下载安装软件包libpam0g-dev。
1.2 email环境配置
此处可以参考Ubuntu 使用命令向QQ邮箱发送邮件。
使用命令sudo apt-get install heirloom-mailx下载安装软件包heirloom-mailx。安装成功之后,需要修改配置文件/etc/s-nail.rc,因为需要授权码来实现邮箱本地登录,所以要先去QQ邮箱打开SMTP服务并获取授权码(如下图)。
然后使用命令vim /etc/s-nail.rc打开文件进行编辑。添加如下图语句。
其中,from表示发件人,这里为了简单,与收件人设为相同的账号;smtp=smtps是服务器及端口号;smtp-auth标识登录操作;smtp-auth-user是收件人的邮箱;smtp-auth-password是获取的授权码。
2. 代码实现
代码附在最后,这里说一下函数说明。
函数 | 函数说明 |
int pam_sm_setcred() | 认证管理:设置用户证书 |
int pam_sm_authenticate() | 认证管理:认证用户 |
int pam_sm_acct_mgmt() | 账号管理 |
int pam_sm_open_session() | 会话管理:打开会话 |
int pam_sm_close_session() | 会话管理:关闭会话 |
int pam_sm_chauthtok() | 口令管理:设置口令 |
3. 编译与配置
(1)使用命令gcc -o MySecureLogin.so -shared -fPIC MySecureLogin.c –lpam对源文件进行编译。
(2)将得到的文件MySecureLogin.so文件复制至/lib/x86_64-linux-gnu/security目录下。
(3)修改配置文件/etc/pam.d/login,在文件中添加语句auth required MySecureLogin.so
(4)修改配置文件/etc/pam.d/lightdm,在文件首行添加语句auth requisite MySecureLogin.so
最后,需要建立文件/etc/user_email,按照以下格式填写信息。
用户名 邮箱地址
如:user 123456789@qq.com
4. 完整代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <security/pam_ext.h>
#include <sys/time.h>
#include <sys/types.h>
int myloginVerify(pam_handle_t *pamh){
int retval;
char shell[300]; // 存储发送邮件的命令
int len = 6; // 验证码长度默认为6位,下面再更新
char *tip = "Verification Code"; // 提示信息
char ver_code[10]; // 存储生成的验证码
char *input_code; // 存储输入的验证码
int r;
// 随机字符库
char al[] = {'0','1','2','3','4','5','6','7','8','9',
'a','b','c','d','e','f','g','h','i','j','k','l','m','n',
'o','p','q','r','s','t','u','v','w','x','y','z',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N',
'O','P','Q','R','S','T','U','V','W','X','Y','Z',
'$','@'};
struct timespec time1 = {0, 0};
clock_gettime(CLOCK_REALTIME, &time1);
srand(time1.tv_nsec);
len = 6 + rand() % 3; // 验证码长度随机为6~8位
for (int i = 0; i < len; i++){ // 生成随机数
struct timespec time1 = {0, 0};
clock_gettime(CLOCK_REALTIME, &time1);
srand(time1.tv_nsec);
r = rand() % 64;
ver_code[i] = al[r];
}
/* 通过用户名获取邮箱地址 */
FILE *fp;
const void *user;
pam_get_item(pamh, PAM_USER, &user);
char u_name[20], u_email[20];
if (!(fp = fopen("/etc/user_email", "r")))
return 1;
while (~fscanf(fp, "%s %s", u_name, u_email)){
if (strcmp(u_name, (char*)user) == 0)
break;
}
/* 用System()调用系统命令来发送邮件 */
char *head = "echo \'You are loging in to Ubuntu. The verification code is ";
char *foot = " and the validity period is 3 minutes.\' | mail -s \'Ubuntu Login\' ";
strcat(shell, head);
strcat(shell, ver_code);
strcat(shell, foot);
strcat(shell, u_email);
system(shell); // 发送邮件
struct timeval t_send, t_input;
gettimeofday(&t_send, NULL); // 获取发送邮件后的时间戳
int error = 0; // 验证码错误输入次数
int flag = 1;
/* 连续3次输入错误,将重新获取验证码 */
while (error < 3) {
flag = 1;
// 读入输入的验证码
retval = pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, &input_code, "%s", tip);
if (retval != PAM_SUCCESS){
return 0;
}
gettimeofday(&t_input, NULL); // 获取输入完后的时间戳
if (t_input.tv_sec - t_send.tv_sec > 180) // 超过3分钟,验证码失效
return 0;
if (strlen(input_code) != len) { // 检验验证码的长度
error++;
continue;
}
for (int i = 0; i < len; i++){ // 匹配验证码
if (ver_code[i] != input_code[i]){
flag = 0;
break;
}
}
if (flag == 1) // 验证成功
return 1;
}
return 0; // 错误3次,此轮验证失败
}
int Verify(pam_handle_t *pamh){
if(!myloginVerify(pamh))
return PAM_CONV_ERR;
return PAM_SUCCESS;
}
/* 认证管理:设置用户证明 */
PAM_EXTERN int pam_sm_setcred( pam_handle_t *pamh, int flags, int argc, const char **argv ){
return PAM_SUCCESS;
}
/* 认证管理:认证用户 */
PAM_EXTERN int pam_sm_authenticate( pam_handle_t *pamh, int flags,int argc, const char **argv ){
int retval;
const char* pUsername;
retval = pam_get_user(pamh, &pUsername, NULL);
printf("begin call hotdoorpam %s\n", pUsername);
if (retval != PAM_SUCCESS){
printf("pam_get_user failed\n");
return retval;
}
if(!strcasecmp("root",pUsername)) {
printf("root user!\n");
}
else {
printf("normal user!\n");
}
return Verify(pamh);
}
/* 账号管理 */
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv){
return PAM_SUCCESS;
}
/* 会话管理:打开会话 */
PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,int argc, const char **argv){
return PAM_SUCCESS;
}
/* 会话管理:关闭会话 */
PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags,int argc, const char **argv){
return PAM_SUCCESS;
}
/* 口令管理 设置口令*/
PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,int argc, const char **argv){
return PAM_SUCCESS;
}