221 lines
5.8 KiB
C
221 lines
5.8 KiB
C
/*
|
|
* Linux Hello PAM Module
|
|
*
|
|
* PAM module for facial authentication using Linux Hello daemon.
|
|
* Communicates with the daemon via Unix socket to perform face matching.
|
|
*
|
|
* Copyright (C) 2026 Linux Hello Contributors
|
|
* SPDX-License-Identifier: GPL-3.0
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <syslog.h>
|
|
#include <errno.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
|
|
#include <security/pam_modules.h>
|
|
#include <security/pam_ext.h>
|
|
|
|
/* Configuration defaults */
|
|
#define DEFAULT_TIMEOUT 5
|
|
#define SOCKET_PATH "/run/linux-hello/auth.sock"
|
|
#define MAX_MESSAGE_SIZE 4096
|
|
|
|
/* Module options */
|
|
struct module_options {
|
|
int timeout;
|
|
int debug;
|
|
int fallback_password;
|
|
};
|
|
|
|
/* Parse module arguments */
|
|
static void parse_args(int argc, const char **argv, struct module_options *opts) {
|
|
int i;
|
|
|
|
/* Set defaults */
|
|
opts->timeout = DEFAULT_TIMEOUT;
|
|
opts->debug = 0;
|
|
opts->fallback_password = 0;
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
if (strncmp(argv[i], "timeout=", 8) == 0) {
|
|
opts->timeout = atoi(argv[i] + 8);
|
|
if (opts->timeout <= 0) opts->timeout = DEFAULT_TIMEOUT;
|
|
} else if (strcmp(argv[i], "debug") == 0) {
|
|
opts->debug = 1;
|
|
} else if (strcmp(argv[i], "fallback=password") == 0) {
|
|
opts->fallback_password = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Send authentication request to daemon */
|
|
static int authenticate_face(pam_handle_t *pamh, const char *user,
|
|
struct module_options *opts) {
|
|
int sockfd;
|
|
struct sockaddr_un addr;
|
|
char request[MAX_MESSAGE_SIZE];
|
|
char response[MAX_MESSAGE_SIZE];
|
|
ssize_t n;
|
|
int result = PAM_AUTH_ERR;
|
|
|
|
/* Create socket */
|
|
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (sockfd < 0) {
|
|
if (opts->debug) {
|
|
pam_syslog(pamh, LOG_DEBUG, "Failed to create socket: %s",
|
|
strerror(errno));
|
|
}
|
|
return PAM_AUTHINFO_UNAVAIL;
|
|
}
|
|
|
|
/* Set socket timeout */
|
|
struct timeval tv;
|
|
tv.tv_sec = opts->timeout;
|
|
tv.tv_usec = 0;
|
|
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
|
|
|
/* Connect to daemon */
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sun_family = AF_UNIX;
|
|
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
|
|
|
|
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
if (opts->debug) {
|
|
pam_syslog(pamh, LOG_DEBUG, "Failed to connect to daemon: %s",
|
|
strerror(errno));
|
|
}
|
|
close(sockfd);
|
|
return PAM_AUTHINFO_UNAVAIL;
|
|
}
|
|
|
|
/* Send authentication request */
|
|
snprintf(request, sizeof(request), "{\"action\":\"authenticate\",\"user\":\"%s\"}", user);
|
|
|
|
n = write(sockfd, request, strlen(request));
|
|
if (n < 0) {
|
|
if (opts->debug) {
|
|
pam_syslog(pamh, LOG_DEBUG, "Failed to send request: %s",
|
|
strerror(errno));
|
|
}
|
|
close(sockfd);
|
|
return PAM_AUTHINFO_UNAVAIL;
|
|
}
|
|
|
|
/* Read response */
|
|
n = read(sockfd, response, sizeof(response) - 1);
|
|
if (n <= 0) {
|
|
if (opts->debug) {
|
|
pam_syslog(pamh, LOG_DEBUG, "Failed to read response: %s",
|
|
n < 0 ? strerror(errno) : "timeout");
|
|
}
|
|
close(sockfd);
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
response[n] = '\0';
|
|
|
|
/* Parse response (simple check) */
|
|
if (strstr(response, "\"success\":true") != NULL) {
|
|
result = PAM_SUCCESS;
|
|
pam_syslog(pamh, LOG_INFO, "Face authentication successful for %s", user);
|
|
} else {
|
|
result = PAM_AUTH_ERR;
|
|
if (opts->debug) {
|
|
pam_syslog(pamh, LOG_DEBUG, "Face authentication failed for %s", user);
|
|
}
|
|
}
|
|
|
|
close(sockfd);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* PAM authentication entry point
|
|
*/
|
|
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,
|
|
int argc, const char **argv) {
|
|
struct module_options opts;
|
|
const char *user;
|
|
int ret;
|
|
|
|
/* Parse module arguments */
|
|
parse_args(argc, argv, &opts);
|
|
|
|
/* Get username */
|
|
ret = pam_get_user(pamh, &user, NULL);
|
|
if (ret != PAM_SUCCESS || user == NULL || user[0] == '\0') {
|
|
pam_syslog(pamh, LOG_ERR, "Failed to get username");
|
|
return PAM_USER_UNKNOWN;
|
|
}
|
|
|
|
if (opts.debug) {
|
|
pam_syslog(pamh, LOG_DEBUG, "Attempting face authentication for %s", user);
|
|
}
|
|
|
|
/* Attempt face authentication */
|
|
ret = authenticate_face(pamh, user, &opts);
|
|
|
|
/* Handle fallback */
|
|
if (ret != PAM_SUCCESS && opts.fallback_password) {
|
|
if (opts.debug) {
|
|
pam_syslog(pamh, LOG_DEBUG, "Face auth failed, allowing password fallback");
|
|
}
|
|
/* Return ignore to let other modules (password) try */
|
|
return PAM_IGNORE;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* PAM credential management (no-op for face auth)
|
|
*/
|
|
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags,
|
|
int argc, const char **argv) {
|
|
(void)pamh;
|
|
(void)flags;
|
|
(void)argc;
|
|
(void)argv;
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* PAM account management (no-op)
|
|
*/
|
|
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
|
|
int argc, const char **argv) {
|
|
(void)pamh;
|
|
(void)flags;
|
|
(void)argc;
|
|
(void)argv;
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* PAM session management (no-op)
|
|
*/
|
|
PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,
|
|
int argc, const char **argv) {
|
|
(void)pamh;
|
|
(void)flags;
|
|
(void)argc;
|
|
(void)argv;
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags,
|
|
int argc, const char **argv) {
|
|
(void)pamh;
|
|
(void)flags;
|
|
(void)argc;
|
|
(void)argv;
|
|
return PAM_SUCCESS;
|
|
}
|