diff --git a/meson_options.txt b/meson_options.txt index 2b2783b1e44e95ba99d08c1479a87b4b40fd928d..7ec9c90469124dcb0340744a885069e50df4d24e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1 +1,2 @@ option('with-drm', type : 'feature', value : 'auto', description : 'Enable DRM backend') +option('systemd-password-agent', type : 'feature', value : 'auto', description : 'Build a systemd password agent for touchscreens') diff --git a/unl0kr/meson.build b/unl0kr/meson.build index ffc2727539d5acefaeae23c02f6a2894de77bf2e..633a2b31ff7d326f386687e16d70ed3b4b4a8b2c 100644 --- a/unl0kr/meson.build +++ b/unl0kr/meson.build @@ -30,8 +30,10 @@ man_files = [ 'doc/unl0kr.conf.5', ] +inih_dep = dependency('inih') + unl0kr_dependencies = [ - dependency('inih'), + inih_dep, dependency('libinput'), dependency('libudev'), dependency('xkbcommon'), @@ -55,6 +57,33 @@ executable( install: true ) +systemd_dep = dependency('systemd', required: get_option('systemd-password-agent')) +if systemd_dep.found() + executable( + 'unl0kr-agent', + sources: files('unl0kr-agent.c'), + dependencies: inih_dep, + c_args: '-DUNL0KR_BINARY=' + get_option('prefix') / get_option('bindir') / 'unl0kr', + install: true, + install_dir: get_option('libexecdir') + ) + + system_unit_dir = systemd_dep.get_variable('systemd_system_unit_dir') + + install_data( + 'unl0kr-agent.path', + install_dir: system_unit_dir + ) + + configure_file( + configuration: {'LIBEXECDIR': get_option('libexecdir')}, + input: 'unl0kr-agent.service.in', + output: 'unl0kr-agent.service', + install: true, + install_dir: system_unit_dir + ) +endif + scdoc = dependency('scdoc') scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native : true) sh = find_program('sh', native : true) diff --git a/unl0kr/unl0kr-agent.c b/unl0kr/unl0kr-agent.c new file mode 100644 index 0000000000000000000000000000000000000000..f94b5b675f83f68e448b732d9a2b621735e44d93 --- /dev/null +++ b/unl0kr/unl0kr-agent.c @@ -0,0 +1,317 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <dirent.h> +#include <errno.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define INI_STOP_ON_FIRST_ERROR 0 /* Ignore unknown keys */ + +#include "ini.h" + +struct Request +{ + uint64_t not_after; + char* socket; + char* message; + char* icon; + pid_t pid; + bool accept_cached; + bool echo; + bool silent; +}; + +void Request_init(struct Request* req) +{ + req->not_after = 0; + req->socket = NULL; + req->message = NULL; + req->icon = NULL; + req->pid = 0; + req->accept_cached = false; + req->echo = false; + req->silent = false; +} + +void Request_free(struct Request* req) +{ + if (req->socket) + free(req->socket); + if (req->message) + free(req->message); + if (req->icon) + free(req->icon); +} + +void Request_reset(struct Request* req) +{ + Request_free(req); + Request_init(req); +} + +struct Request request; + +static int send_password(const char *password) { + int socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); + if (socket_fd < 0) { + perror("socket() is failed"); + return errno; + } + + struct sockaddr_un address; + address.sun_family = AF_UNIX; + strncpy(address.sun_path, request.socket, sizeof(address.sun_path) - 1); + + ssize_t n = sendto(socket_fd, password, strlen(password), MSG_NOSIGNAL, (const struct sockaddr*) &address, sizeof(address)); + if (n < 0) { + perror("sendto() is failed"); + int error = errno; + (void) close(socket_fd); + return error; + } + + return 0; +} + +static bool to_bool(const char* value) +{ + if (strcmp(value, "true") == 0) { + return true; + } else if (strcmp(value, "1") == 0) { + return true; + } else if (strcmp(value, "yes") == 0) { + return true; + } else if (strcmp(value, "false") == 0) { + return false; + } else if (strcmp(value, "0") == 0) { + return false; + } else if (strcmp(value, "no") == 0) { + return false; + } else if (strcmp(value, "") == 0) { + return false; + } else { + fprintf(stderr, "The value '%s' is not a boolean", value); + return false; + } +} + +static int ini_parser(void* user, const char* section, const char* name, const char* value) +{ + struct Request* d = (struct Request*) user; + + if (strcmp(section, "Ask") != 0) { + fprintf(stderr, "The ini file contains unknown section: %s", section); + return 0; + } + + if (strcmp(name, "NotAfter") == 0) { + d->not_after = atol(value); + } else if (strcmp(name, "Socket") == 0) { + d->socket = strdup(value); + } else if (strcmp(name, "Message") == 0) { + d->message = strdup(value); + } else if (strcmp(name, "Icon") == 0) { + d->icon = strdup(value); + } else if (strcmp(name, "PID") == 0) { + d->pid = atoi(value); + } else if (strcmp(name, "AcceptCached") == 0) { + d->accept_cached = to_bool(value); + } else if (strcmp(name, "Echo") == 0) { + d->echo = to_bool(value); + } else if (strcmp(name, "Silent") == 0) { + d->silent = to_bool(value); + } else { + fprintf(stderr, "The ini file contains unknown key: %s", name); + return 0; + } + + return 1; +} + +int find_request(char** ret_file) +{ + const char* ask_folder = "/run/systemd/ask-password"; + const size_t ask_folder_length = strlen(ask_folder); + + DIR* dir = opendir(ask_folder); + if (!dir) { + if (errno != ENOENT) { + fprintf(stderr, "Can\'t open '%s': %s", ask_folder, strerror(errno)); + } + return errno; + } + + struct dirent* entry; + while (entry = readdir(dir)) { + if (entry->d_type != DT_REG) + continue; + + if (strncmp(entry->d_name, "ask.", 4) != 0) + continue; + + break; + } + if (!entry) { + closedir(dir); + return ENOENT; + } + + char* file = malloc(ask_folder_length + 1 + strlen(entry->d_name) + 1); + if (!file) { + closedir(dir); + fprintf(stderr, "Out of memory"); + return ENOMEM; + } + + strcpy(file, ask_folder); + strcpy(file + ask_folder_length, "/"); + strcpy(file + ask_folder_length + 1, entry->d_name); + + closedir(dir); + + *ret_file = file; + return 0; +} + +static int exec_unl0kr(char** ret_password) +{ + int fd[2]; + if (pipe(fd) != 0) { + perror("Can't create a pipe"); + return errno; + } + + pid_t pid = fork(); + switch (pid) { + case -1: { + perror("fork() is failed"); + int error = errno; + (void) close(fd[0]); + (void) close(fd[1]); + return error; + } + + case 0: { /* Child */ + (void) close(fd[0]); + + if (dup2(fd[1], STDOUT_FILENO) == -1) { + perror("dup2() is failed"); + return errno; + } + + execl("UNL0KR_BINARY", "unl0kr", (char*) 0); + + perror("exec() is failed"); + return errno; + } + + default: { /* Parent */ + int status; + if (waitpid(pid, &status, 0) == -1) { + perror("waitpid() is failed"); + int error = errno; + (void) close(fd[0]); + (void) close(fd[1]); + return error; + } + + if (!WIFEXITED(status)) { + fprintf(stderr, "unl0kr terminated abnormally"); + (void) close(fd[0]); + (void) close(fd[1]); + return ECHILD; + } + + int password_size; + if (ioctl(fd[0], FIONREAD, &password_size) == -1) { + perror("ioctl() is failed"); + int error = errno; + (void) close(fd[0]); + (void) close(fd[1]); + return error; + } + + char* password = malloc(password_size + 1); + if (!password) { + fprintf(stderr, "Out of memory"); + (void) close(fd[0]); + (void) close(fd[1]); + return ENOMEM; + } + + password[0] = '+'; + password_size = read(fd[0], password + 1, password_size); + if (password_size == -1) { + perror("read() is failed"); + int error = errno; + (void) close(fd[0]); + (void) close(fd[1]); + free(password); + return error; + } + + *ret_password = password; + return 0; + }} +} + +int main(/* int argc, char* argv[] */) +{ + Request_init(&request); + + int exit_code = EXIT_SUCCESS; + int r; + + for (;;) { + char* file; + r = find_request(&file); + if (r != 0) { + if (r != ENOENT) + exit_code = EXIT_FAILURE; + break; + } + + // start_tracking_file(file); + + Request_reset(&request); + + r = ini_parse(file, ini_parser, &request); + if (r < 0) { + fprintf(stderr, "The file '%s' can not be parsed: %d", file, r); + free(file); + exit_code = EXIT_FAILURE; + break; + } + + free(file); + + char* password; + r = exec_unl0kr(&password); + if (r != 0) { + exit_code = EXIT_FAILURE; + break; + } + + r = send_password(password); + if (r != 0) { + exit_code = EXIT_FAILURE; + break; + } + + // wait_for_file_removed(); + sleep(1000); + } + + Request_free(&request); + return exit_code; +} diff --git a/unl0kr/unl0kr-agent.path b/unl0kr/unl0kr-agent.path new file mode 100644 index 0000000000000000000000000000000000000000..d63497d9d43184cf82105428ae0566d3c8d6ff30 --- /dev/null +++ b/unl0kr/unl0kr-agent.path @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +[Unit] +Description=Dispatch Password Requests to unl0kr Directory Watch + +ConditionPathExists=!/run/plymouth/pid + +DefaultDependencies=no +After=plymouth-start.service +Before=paths.target cryptsetup.target +Conflicts=emergency.service +Before=emergency.service +Conflicts=shutdown.target +Before=shutdown.target + +[Path] +DirectoryNotEmpty=/run/systemd/ask-password +MakeDirectory=yes diff --git a/unl0kr/unl0kr-agent.service.in b/unl0kr/unl0kr-agent.service.in new file mode 100644 index 0000000000000000000000000000000000000000..e1f385081b9f5fd7b03b32f9f9f99062bd21db23 --- /dev/null +++ b/unl0kr/unl0kr-agent.service.in @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +[Unit] +Description=Dispatch Password Requests to unl0kr + +ConditionPathExists=!/run/plymouth/pid + +DefaultDependencies=no +After=plymouth-start.service +Conflicts=emergency.service +Before=emergency.service +Conflicts=shutdown.target initrd-switch-root.target +Before=shutdown.target initrd-switch-root.target + +[Service] +ExecStart=LIBEXECDIR/unl0kr-agent