ukui-biometric-auth/pam-biometric/pam_biometric.c

730 lines
23 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2023 KylinSoftCo., Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
**/
#define PAM_SM_AUTH
#include "generic.h"
#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
#include <syslog.h>
#include <security/pam_modules.h>
#include <security/_pam_macros.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <security/pam_ext.h>
#include <signal.h>
#include <errno.h>
#include <glib.h>
#include <string.h>
#define USER_CONFIG_FILE "/home/%s/.biometric_auth/ukui_biometric.conf"
/* Declare log function */
extern int pam_enable_debug;
extern char *pam_log_prefix;
extern int pam_logger(char *format, ...);
static int ukui_biometric_lock = 0;
int enable_biometric_authentication(pam_handle_t *pamh);
int enable_qrcode_authentication(pam_handle_t *pamh);
int enable_biometric_auth_double();
/* GUI child process alive status */
static int child_alive = 1;
/* Signal handler */
static void signal_handler(int signo)
{
if (signo == SIGUSR1)
child_alive = 0; /* GUI child process has terminated */
pam_logger("signal_handler is triggered\n");
}
int enable_biometric_authentication_app()
{
char conf_file[] = GET_STR(CONFIG_FILE);
FILE *file;
char line[1024];
int i;
int is_enable = 0;
if((file = fopen(conf_file, "r")) == NULL){
pam_logger("open configure file failed: %s\n", strerror(errno));
return 1;
}
while(fgets(line, sizeof(line), file)) {
i = sscanf(line, "EnableAuthApp=%d\n", &is_enable);
if(i > 0) {
pam_logger("EnableAuthApp=%d\n", is_enable);
break;
}
}
fclose(file);
return is_enable;
}
/*
* Check if the service should use biometric authentication
*/
int service_filter(char *service)
{
//int is_enable = enable_biometric_authentication_app();
//syslog(LOG_INFO,"is_enable = %d service = %s\n",is_enable,service);
if (strcmp(service, "lightdm") == 0) {
//if(is_enable & 1 == 0)
// return 0;
return 1;
}
if (strcmp(service, "ukui-screensaver-qt") == 0){
//if((is_enable & (1<<1)) == 0)
// return 0;
return 1;
}
if (strcmp(service, "polkit-1") == 0){
//if((is_enable & (1<<2)) == 0)
// return 0;
return 1;
}
if (strcmp(service, "sudo") == 0){
//if((is_enable & (1<<3)) == 0)
// return 0;
return 1;
}
if (strcmp(service, "su") == 0){
//if((is_enable & (1<<4)) == 0)
// return 0;
return 1;
}
if (strcmp(service, "login") == 0){
//if((is_enable & (1<<5)) == 0)
// return 0;
return 1;
}
#ifdef ENABLE_BIOTEST
if (strcmp(service, "biotest") == 0)
return 1;
#endif
return 0;
}
/*
* Invoke the PAM conversation function
*/
int call_conversation(pam_handle_t *pamh, int msg_style, char *msg, char *resp)
{
/* PAM data structures used by conversation */
const struct pam_message *message[1] = {0};
struct pam_message *message_tmp = 0;
struct pam_response *response = 0;
const struct pam_conv *conv_struct = 0;
int status = -1;
status = pam_get_item(pamh, PAM_CONV, (const void **)&conv_struct);
if (status != PAM_SUCCESS)
return PAM_SYSTEM_ERR;
message_tmp = (struct pam_message *)malloc(sizeof(struct pam_message));
message_tmp->msg_style = msg_style;
message_tmp->msg = msg;
message[0] = message_tmp;
pam_logger("Call conv callback function\n");
status = conv_struct->conv(1, message, &response, conv_struct->appdata_ptr);
pam_logger("Finish conv callback function\n");
if (resp && response->resp)
strcpy(resp, response->resp);
/* Use typecast to suppress gcc warnings */
free((void *)message[0]);
if (response->resp)
free(response->resp);
free(response);
return status;
}
/* GUI child process */
void child(char *service, char *username, char *xdisp)
{
pam_logger("Child process will be replaced.\n");
int fd = open("/dev/null", O_WRONLY);
dup2(fd, 2);
execl("/usr/bin/bioauth", "bioauth",
"--service", service,
"--user", username,
// "--display", xdisp,
pam_enable_debug ? "--debug" : "",
(char *)0);
/*
* execl almost always succeed as long as the GUI executable file exists.
* Though invoking GUI under console will exit with error, the GUI child
* process won't reach here. Therefore, the following code won't be
* executed in general.
*/
pam_logger("Fatal error: execl(gui) failed in child process. "
"This is an extremely rare condition. Please ensure that the "
"biometric-authentication executable file exists.\n");
pam_logger("Use password as a fallback\n");
pam_logger("Child _exit with BIO_IGNORE\n");
/* Child process exits */
_exit(BIO_IGNORE);
}
void handler()
{
return;
}
/* PAM parent process */
int parent(int pid, pam_handle_t *pamh, int need_call_conv)
{
pam_logger("Parent process continue running.\n");
int child_status = -1;
/*
* 1. If calling conversation function is not needed, wait the child
* until it exits.
* 2. Otherwise, send a text info to application at first and then
* call the conversation function to request the password. The returned
* password is unused.
* Note: During requesting the password, screensaver won't display the
* prompting message, while greeter will display it into the password
* entry. If there is a "\n" at the end of the message, it will be
* displayed on the Label above the password entry.
*/
if (need_call_conv) {
char *lang = getenv("LANG");
char *msg1, *msg2;
if (lang && !strncmp(lang, "zh_CN", 5))
msg1 = "请进行生物识别或点击“切换到密码登录”\n";
else
msg1 = "Use biometric authentication or click "
"\"Switch to password\"\n";
#ifdef EMPTY_PAM_PWD_PROMPT
msg2 = "";
#else
msg2 = "pam_biometric.so needs a fake ENTER:";
#endif
if (signal(SIGUSR1, signal_handler) == SIG_ERR)
pam_logger("Fatal Error. Can't catch SIGUSR1\n");
reinvoke:
call_conversation(pamh, PAM_TEXT_INFO, msg1, NULL);
call_conversation(pamh, PAM_PROMPT_ECHO_OFF, msg2, NULL);
/* GUI child process is still alive. This enter is typed by user. */
if (child_alive == 1)
goto reinvoke;
signal(SIGUSR1, SIG_DFL);
waitpid(pid, &child_status, 0);
} else {
pam_logger("Waiting for the GUI child process to exit...\n");
//由于sudo命令在进入pam认证时会阻塞来自终端的SIGINT以及SIGQUIT信号导致使用
//pam认证时按下Ctrl+C无反应认证完成后sudo会退出这里为了简单取消了阻塞
//信号,捕获信号但不做处理,在认证完成后,恢复原本阻塞状态
sigset_t mask;
sigprocmask(SIG_BLOCK,NULL,&mask);
sigprocmask(SIG_UNBLOCK,&mask,NULL);
signal(SIGINT,handler);
waitpid(pid, &child_status, 0);
pam_logger("GUI child process has exited.\n");
sigprocmask(SIG_SETMASK,&mask,NULL);
}
/*
* Determine the return value of PAM according to the return value of
* child process.
*/
int bio_result = BIO_ERROR; /* biometric result code */
if (WIFEXITED(child_status))
bio_result = WEXITSTATUS(child_status);
else /* This may be because the GUI child process is invoked under console. */
pam_logger("The GUI-Child process terminate abnormally.\n");
if (bio_result == BIO_SUCCESS) {
if(!enable_biometric_authentication(pamh) && !enable_qrcode_authentication(pamh)) {
pam_logger("disable biometric authentication.\n");
return PAM_SYSTEM_ERR;
}
pam_logger("pam_biometric.so return PAM_SUCCESS\n");
return PAM_SUCCESS;
} else if (bio_result == BIO_IGNORE) {
/* Override msg1 to empty the label. We are ready to enter the password module. */
call_conversation(pamh, PAM_TEXT_INFO, "", NULL);
ukui_biometric_lock = 1;
pam_logger("pam_biometric.so return PAM_IGNORE\n");
return PAM_IGNORE;
} else {
pam_logger("pam_biometric.so return PAM_SYSTEM_ERR\n");
ukui_biometric_lock = 1;
return PAM_SYSTEM_ERR;
}
}
/* Set environment variables related to displaying */
void check_and_set_env(pam_handle_t *pamh, char **xdisp, char **xauth)
{
*xdisp=getenv("DISPLAY");
*xauth=getenv("XAUTHORITY");
if (*xdisp == 0){
pam_get_item(pamh, PAM_XDISPLAY, (const void **)xdisp);
if (*xdisp)
setenv("DISPLAY",*xdisp,-1);
}
if (*xauth == 0)
setenv("XAUTHORITY", "/var/run/lightdm/root/:0", -1);
*xdisp=getenv("DISPLAY");
*xauth=getenv("XAUTHORITY");
if (*xdisp == 0)
pam_logger("Warning: DISPLAY env is still empty, "
"this is not an error if you are using terminal\n");
if (*xauth == 0)
pam_logger("Warning: XAUTHORITY env is still empty, "
"this is not an error if you are using terminal\n");
}
/* Biometric processing function for generic purpose */
int biometric_auth_independent(pam_handle_t *pamh , char *service, int need_call_conv)
{
/* Get the username */
char *username = 0;
pam_get_item(pamh, PAM_USER, (const void **)&username);
/* Check and set environment variables */
char *xdisp, *xauth;
check_and_set_env(pamh, &xdisp, &xauth);
/* Detach child process */
unsigned int pid;
pid = fork();
if (pid < 0) {
pam_logger("Fork Error!\n");
return PAM_SYSTEM_ERR;
} else if (pid != 0) {
return parent(pid, pamh, need_call_conv);
} else {
child(service, username, xdisp);
pam_logger("Should never reach here.\n");
return PAM_SYSTEM_ERR;
}
}
/* Biometric processing function fot polkit-1 */
int biometric_auth_polkit()
{
pam_logger("Current service is polkit-1\n");
const char *fifo_name = "/tmp/bio.fifo";
if(access(fifo_name, F_OK) == -1) {
int res = mkfifo(fifo_name, 0777);
if(res != 0) {
pam_logger("Can't create FIFO file\n");
return PAM_SYSTEM_ERR;
}
}
int fifo_rd = open(fifo_name, O_RDONLY);
if (fifo_rd == -1)
return PAM_SYSTEM_ERR;
pam_logger("Before reading FIFO\n");
char buffer[8] = {0};
if(read(fifo_rd, buffer, 8) == -1)
return PAM_SYSTEM_ERR;
pam_logger("After reading FIFO\n");
int result_code;
sscanf(buffer, "%d", &result_code);
remove(fifo_name);
if (result_code == BIO_SUCCESS) {
pam_logger("pam_biometric.so return PAM_SUCCESS\n");
return PAM_SUCCESS;
} else if (result_code == BIO_IGNORE) {
pam_logger("pam_biometric.so return PAM_IGNORE\n");
return PAM_IGNORE;
} else {
pam_logger("pam_biometric.so return PAM_SYSTEM_ERR\n");
return PAM_SYSTEM_ERR;
}
}
int biometric_auth_embeded(pam_handle_t *pamh)
{
/*
* By convention, PAM module sends a string "BIOMETRIC_PAM" to
* lightdm and this message will be forwarded to greeter. After
* the authentication is completed, greeter will send a string
* "BIOMETRIC_IGNORE"/"BIOMETRIC_SUCCESS" to lightdm and then it
* will be forwarded to PAM module. We can get the authentication
* status by comparing strings.
*/
char resp[96] = {0};
if(enable_biometric_auth_double())
call_conversation(pamh, PAM_PROMPT_ECHO_OFF, BIOMETRIC_PAM_DOUBLE, resp);
else
call_conversation(pamh, PAM_PROMPT_ECHO_OFF, BIOMETRIC_PAM, resp);
if (strcmp(resp, BIOMETRIC_IGNORE) == 0)
return PAM_IGNORE;
else if (strcmp(resp, BIOMETRIC_SUCCESS) == 0){
if(!enable_biometric_authentication(pamh) && !enable_qrcode_authentication(pamh)) {
pam_logger("disable biometric authentication.\n");
return PAM_SYSTEM_ERR;
}
return PAM_SUCCESS;
}
else if (strcmp(resp, BIOMETRIC_FAILED) == 0)
return PAM_AUTH_ERR;
else
return PAM_SYSTEM_ERR;
}
void get_greeter_session(char buf[], int len)
{
GPtrArray *args_ps = NULL;
gchar *stdout_ps = NULL;
gchar *greeter_name = NULL;
args_ps = g_ptr_array_new ();
g_ptr_array_add(args_ps, (gpointer)"/usr/bin/ps");
g_ptr_array_add(args_ps, (gpointer)"-aux");
g_ptr_array_add(args_ps, (gpointer)"--columns");
g_ptr_array_add(args_ps, (gpointer)"256");
g_ptr_array_add(args_ps, NULL);
if (!g_spawn_sync(NULL, (char**)args_ps->pdata, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, &stdout_ps, NULL, NULL, NULL)) {
if (stdout_ps)
g_free(stdout_ps);
g_ptr_array_free (args_ps, TRUE);
return ;
}
if (stdout_ps) {
gchar **lines = NULL;
lines = g_strsplit(stdout_ps, "\n", 0);
for (int n = 0; lines[n] != NULL; n++) {
if (lines[n][0] == '\0')
continue;
gchar *res = g_strstr_len(lines[n], 1024, "greeter-session");
if (res) {
gchar **greeter_args = g_strsplit(res, " ", 0);
int arg_index = 0;
for (int m = 0; greeter_args[m] != NULL; m++) {
if (greeter_args[m][0] == '\0')
continue;
// greeter-session arg1
if (arg_index == 1) {
gchar *greeter = g_strrstr_len(greeter_args[m], 256, "/");
if (greeter && strlen(greeter) > 1) {
greeter_name = g_strdup(greeter + 1);
break;
}
}
arg_index ++;
}
g_strfreev(greeter_args);
if (greeter_name) {
break;
}
}
}
g_strfreev(lines);
g_free(stdout_ps);
}
g_ptr_array_free (args_ps, TRUE);
if (greeter_name) {
g_strlcpy(buf, greeter_name, len);
g_free(greeter_name);
} else {
GPtrArray *args_pidof = NULL;
gchar *stdout_pidof = NULL;
args_pidof = g_ptr_array_new ();
g_ptr_array_add(args_pidof, (gpointer)"/usr/bin/pidof");
g_ptr_array_add(args_pidof, (gpointer)"ukui-greeter");
g_ptr_array_add(args_pidof, NULL);
if (!g_spawn_sync(NULL, (char**)args_pidof->pdata, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, &stdout_pidof, NULL, NULL, NULL)) {
if (stdout_pidof)
g_free(stdout_pidof);
g_ptr_array_free (args_pidof, TRUE);
//如果ukui-greeter进程不存在进而判断ukui-screensaver-backend进程
args_pidof = g_ptr_array_new ();
g_ptr_array_add(args_pidof, (gpointer)"/usr/bin/pidof");
g_ptr_array_add(args_pidof, (gpointer)"ukui-screensaver-backend");
g_ptr_array_add(args_pidof, NULL);
if (!g_spawn_sync(NULL, (char**)args_pidof->pdata, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, &stdout_pidof, NULL, NULL, NULL)) {
if (stdout_pidof)
g_free(stdout_pidof);
g_ptr_array_free (args_pidof, TRUE);
}
if (stdout_pidof) {
if (strlen(stdout_pidof) > 0 && atoi(stdout_pidof) > 0) {
g_strlcpy(buf, "ukui-screensaver-backend", len);
}
g_free(stdout_pidof);
}
g_ptr_array_free (args_pidof, TRUE);
return ;
}
if (stdout_pidof) {
if (strlen(stdout_pidof) > 0 && atoi(stdout_pidof) > 0) {
g_strlcpy(buf, "ukui-greeter", len);
}
g_free(stdout_pidof);
}
g_ptr_array_free (args_pidof, TRUE);
}
}
int enable_by_polkit()
{
FILE *file;
char buf[1024];
if( (file = fopen(BIO_COM_FILE, "r")) == NULL) {
pam_logger("open communication file failed: %s\n", strerror(errno));
return 0;
}
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), file);
fclose(file);
if(remove(BIO_COM_FILE) < 0)
pam_logger("remove communication file failed: %s\n", strerror(errno));
pam_logger("%s\n", buf);
if(strcmp(buf, "polkit-ukui-authentication-agent-1") == 0)
return 1;
return 0;
}
int enable_biometric_authentication(pam_handle_t *pamh)
{
char *username = NULL;
int is_found = 0;
int is_auth_enable = 0;
pam_get_item(pamh, PAM_USER, (const void **)&username);
if (username) {
char conf_file_user[256];
snprintf(conf_file_user, 255, USER_CONFIG_FILE, username);
FILE *file = NULL;
char line[1024], is_enable[16];
int i;
if((file = fopen(conf_file_user, "r")) == NULL){
pam_logger("open configure file failed: %s\n", strerror(errno));
} else {
while(fgets(line, sizeof(line), file)) {
i = sscanf(line, "EnableAuth=%15s\n", is_enable);
if(i > 0) {
pam_logger("EnableAuth=%s\n", is_enable);
is_found = 1;
break;
}
}
fclose(file);
if(!strcmp(is_enable, "true"))
is_auth_enable = 1;
}
}
if (is_found != 0) {
return is_auth_enable;
}
char conf_file[] = GET_STR(CONFIG_FILE);
FILE *file;
char line[1024], is_enable[16];
int i;
if((file = fopen(conf_file, "r")) == NULL){
pam_logger("open configure file failed: %s\n", strerror(errno));
return 0;
}
while(fgets(line, sizeof(line), file)) {
i = sscanf(line, "EnableAuth=%15s\n", is_enable);
if(i > 0) {
pam_logger("EnableAuth=%s\n", is_enable);
break;
}
}
fclose(file);
if(!strcmp(is_enable, "true"))
return 1;
return 0;
}
int enable_qrcode_authentication(pam_handle_t *pamh)
{
char *username = NULL;
int is_found = 0;
int is_auth_enable = 0;
pam_get_item(pamh, PAM_USER, (const void **)&username);
if (username) {
char conf_file_user[256];
snprintf(conf_file_user, 255, USER_CONFIG_FILE, username);
FILE *file = NULL;
char line[1024], is_enable[16];
int i;
if((file = fopen(conf_file_user, "r")) == NULL){
pam_logger("open configure file failed: %s\n", strerror(errno));
} else {
while(fgets(line, sizeof(line), file)) {
i = sscanf(line, "EnableQRCode=%15s\n", is_enable);
if(i > 0) {
pam_logger("EnableQRCode=%s\n", is_enable);
is_found = 1;
break;
}
}
fclose(file);
if(!strcmp(is_enable, "true"))
is_auth_enable = 1;
}
}
if (is_found != 0) {
return is_auth_enable;
}
char conf_file[] = GET_STR(CONFIG_FILE);
FILE *file;
char line[1024], is_enable[16];
int i;
if((file = fopen(conf_file, "r")) == NULL){
pam_logger("open configure file failed: %s\n", strerror(errno));
return 0;
}
while(fgets(line, sizeof(line), file)) {
i = sscanf(line, "EnableQRCode=%15s\n", is_enable);
if(i > 0) {
pam_logger("EnableQRCode=%s\n", is_enable);
break;
}
}
fclose(file);
if(!strcmp(is_enable, "true"))
return 1;
return 0;
}
int enable_biometric_auth_double()
{
char conf_file[] = GET_STR(CONFIG_FILE);
FILE *file;
char line[1024], is_enable[16];
int i;
if((file = fopen(conf_file, "r")) == NULL){
pam_logger("open configure file failed: %s\n", strerror(errno));
return 0;
}
while(fgets(line, sizeof(line), file)) {
i = sscanf(line, "DoubleAuth=%s\n", is_enable);
if(i > 0) {
pam_logger("DoubleAuth=%s\n", is_enable);
break;
}
}
fclose(file);
if(!strcmp(is_enable, "true"))
return 1;
return 0;
}
/*
* SPI interface
*/
int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
const char **argv)
{
for(int i = 0; i < argc; i++) {
if(strcmp(argv[i], "debug") == 0) {
pam_enable_debug = 1;
pam_log_prefix = "PAM_BIO";
}
}
pam_logger("Invoke libpam_biometric.so module\n");
char *service = 0;
if((!enable_biometric_authentication(pamh) && !enable_qrcode_authentication(pamh)) || ukui_biometric_lock) {
pam_logger("disable biometric authentication.\n");
return PAM_IGNORE;
}
pam_logger("enable biometric authentication.\n");
pam_get_item(pamh, PAM_SERVICE, (const void **)&service);
/* Service filter */
if (!service_filter(service)){
pam_logger("Service <%s> should not use biometric-authentication\n", service);
return PAM_IGNORE;
}
/* Different services use different processing function */
if (strcmp(service, "lightdm") == 0) {
char buf[128];
get_greeter_session(buf, sizeof(buf));
pam_logger("current greeter: %s\n", buf);
if(strcmp(buf, "ukui-greeter") == 0 || strcmp(buf, "ukui-greeter-wayland") == 0 || strcmp(buf, "ukui-session") == 0 || strcmp(buf,"ukui-screensaver-backend"))
return biometric_auth_embeded(pamh);
// else
// return biometric_auth_independent(pamh, "lightdm", 1);
}
else if (strcmp(service, "ukui-screensaver-qt")==0)
return biometric_auth_embeded(pamh);
else if (strcmp(service, "polkit-1") == 0){
if(enable_by_polkit())
return biometric_auth_embeded(pamh);
else
pam_logger("[PAM_BIOMETRIC]: It's not polkit-ukui-authentication-agent-1.\n");
}
else if (strcmp(service, "sudo") == 0)
return biometric_auth_independent(pamh, "sudo", 0);
else if (strcmp(service, "login") == 0)
return biometric_auth_independent(pamh, "login", 0);
else if (strcmp(service, "su") == 0)
return biometric_auth_independent(pamh, "su", 0);
// else if (strcmp(service, "mate-screensaver") == 0)
// return biometric_auth_independent(pamh, "mate-screensaver", 1);
#ifdef ENABLE_BIOTEST
else if (strcmp(service, "biotest") == 0)
return biometric_auth_independent(pamh, "biotest", 1);
#endif
else
pam_logger("Service <%s> slip through the service filter\n", service);
return PAM_IGNORE;
}
int
pam_sm_setcred (pam_handle_t *pamh, int flags ,
int argc , const char **argv )
{
return PAM_SUCCESS;
}