From 89bd1cb350d03bbe312f6a1c94984e45540c5826 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Sat, 19 Jan 2019 19:39:38 -0800 Subject: Authentication now works --- meson.build | 6 ++- src/auth.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/auth.h | 35 ++++++++++++++++ src/main.c | 78 ++++++++++++++++++++++++++++++++++- src/render.c | 3 +- src/render.h | 7 ++++ 6 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 src/auth.c create mode 100644 src/auth.h diff --git a/meson.build b/meson.build index 3b5a780..fab674f 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,10 @@ project('buzzlocker', 'c') gnome = import('gnome') # for gresources +cc = meson.get_compiler('c') + sources = [ + 'src/auth.c', 'src/main.c', 'src/render.c', 'src/x11_support.c', @@ -14,6 +17,8 @@ dependencies = [ dependency('librsvg-2.0'), dependency('pangocairo'), dependency('gio-2.0'), + cc.find_library('pam', required: true), + cc.find_library('pthread', required: true), ] # Resources @@ -27,6 +32,5 @@ resources = gnome.compile_resources( executable('buzzlocker', sources: sources + resources, dependencies: dependencies, - c_std: 'c11', install: true ) \ No newline at end of file diff --git a/src/auth.c b/src/auth.c new file mode 100644 index 0000000..97e934f --- /dev/null +++ b/src/auth.c @@ -0,0 +1,130 @@ +/* + * auth.c + * + * Created by buzzert 2019-01-19 + */ + +#include "auth.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct auth_handle_t { + void *context; + auth_callbacks_t callbacks; + + sem_t prompt_semaphore; + auth_prompt_response_t *prompt_response; +}; + +int process_message(const struct pam_message *msg, struct pam_response *resp, struct auth_handle_t *handle) +{ + switch (msg->msg_style) { + case PAM_PROMPT_ECHO_ON: + case PAM_PROMPT_ECHO_OFF: { + handle->callbacks.prompt_handler(msg->msg, handle->context); + + sem_wait(&handle->prompt_semaphore); + + auth_prompt_response_t *response = handle->prompt_response; + resp->resp = response->response_buffer; + resp->resp_retcode = response->response_code; + break; + } + case PAM_ERROR_MSG: + handle->callbacks.error_handler(msg->msg, handle->context); + break; + case PAM_TEXT_INFO: + handle->callbacks.info_handler(msg->msg, handle->context); + break; + } + + return PAM_SUCCESS; +} + +int perform_conversation(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *data) +{ + *resp = calloc(num_msg, sizeof(struct pam_response)); + + struct auth_handle_t *handle = (struct auth_handle_t *)data; + for (unsigned i = 0; i < num_msg; ++i) { + process_message(msg[i], resp[i], handle); + } + + return PAM_SUCCESS; +} + +static void* auth_thread_main(void *arg) +{ + struct pam_conv conv; + conv.conv = perform_conversation; + conv.appdata_ptr = arg; + + // Get current username + struct passwd *pwd = getpwuid(getuid()); + const char *username = pwd->pw_name; + if (username == NULL || strlen(username) == 0) { + fprintf(stderr, "Couldn't get name for the current user\n"); + // todo: report to callback + } + + // Start PAM authentication + pam_handle_t *pam = NULL; + pam_start( + "login", + username, + &conv, + &pam + ); + + bool authenticating = true; + struct auth_handle_t *handle = (struct auth_handle_t *)arg; + while (authenticating) { + int status = pam_authenticate(pam, 0); + handle->callbacks.result_handler(status, handle->context); + + if (status == PAM_SUCCESS) { + authenticating = false; + } + } + + pam_end(pam, 0); + + return NULL; +} + +struct auth_handle_t* auth_begin_authentication(auth_callbacks_t callbacks, void *context) +{ + struct auth_handle_t *handle = malloc(sizeof(struct auth_handle_t)); + handle->callbacks = callbacks; + handle->context = context; + handle->prompt_response = NULL; + sem_init(&handle->prompt_semaphore, 0, 0); + + pthread_t auth_thread; + if (pthread_create(&auth_thread, NULL, auth_thread_main, handle)) { + fprintf(stderr, "Error creating auth thread\n"); + } + + return handle; +} + + +void auth_attempt_authentication(struct auth_handle_t *handle, auth_prompt_response_t response) +{ + if (handle->prompt_response == NULL) { + handle->prompt_response = malloc(sizeof(auth_prompt_response_t)); + } + handle->prompt_response = memcpy(handle->prompt_response, &response, sizeof(auth_prompt_response_t)); + + sem_post(&handle->prompt_semaphore); +} + + diff --git a/src/auth.h b/src/auth.h new file mode 100644 index 0000000..462e77b --- /dev/null +++ b/src/auth.h @@ -0,0 +1,35 @@ +/* + * auth.h + * + * Handles all authentication events via PAM + * Created by buzzert 2019-01-19 + */ + +#pragma once + +typedef struct { + char *response_buffer; + int response_code; +} auth_prompt_response_t; + +// NOTE: These callbacks are called on a separate thread +typedef void(*ShowInfo)(const char *info_msg, void *context); +typedef void(*ShowError)(const char *error_msg, void *context); +typedef void(*PromptUser)(const char *prompt, void *context); +typedef void(*AuthenticationResult)(int result, void *context); + +typedef struct { + ShowInfo info_handler; + ShowError error_handler; + PromptUser prompt_handler; + AuthenticationResult result_handler; +} auth_callbacks_t; + +struct auth_handle_t; + +// Starts an authentication thread and returns immediately +struct auth_handle_t* auth_begin_authentication(auth_callbacks_t callbacks, void *context); + +// Perform an authentication attempt +void auth_attempt_authentication(struct auth_handle_t *handle, auth_prompt_response_t response); + diff --git a/src/main.c b/src/main.c index ffbc098..5edb881 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ * Created 2019-01-16 by James Magahern */ +#include "auth.h" #include "render.h" #include "x11_support.h" @@ -15,6 +16,11 @@ static const size_t kMaxPasswordLength = 128; +static inline saver_state_t* saver_state(void *c) +{ + return (saver_state_t *)c; +} + static void accept_password(saver_state_t *state); /* @@ -31,6 +37,8 @@ static void window_changed_size(saver_state_t *state, XConfigureEvent *event) static void handle_key_event(saver_state_t *state, XKeyEvent *event) { + if (!state->input_allowed) return; + KeySym key; char keybuf[8]; XLookupString(event, keybuf, sizeof(keybuf), &key, NULL); @@ -47,7 +55,7 @@ static void handle_key_event(saver_state_t *state, XKeyEvent *event) } else if (strlen(keybuf) > 0) { size_t add_len = strlen(keybuf); if ( (length + add_len) < state->password_buffer_len - 1 ) { - strncpy(password_buf + length, keybuf, add_len); + strncpy(password_buf + length, keybuf, add_len + 1); } } } @@ -91,6 +99,55 @@ static int poll_events(saver_state_t *state) static void accept_password(saver_state_t *state) { state->cursor_animating = false; + + size_t pw_length = strlen(state->password_buffer); + char *password_buf = malloc(pw_length); + strncpy(password_buf, state->password_buffer, pw_length + 1); + + auth_prompt_response_t response; + response.response_buffer = password_buf; + response.response_code = 0; + auth_attempt_authentication(state->auth_handle, response); + + // Block input until we hear back from the auth thread + state->input_allowed = false; +} + +/* + * Auth callbacks + */ + +void callback_show_info(const char *info_msg, void *context) +{ + saver_state(context)->password_prompt = info_msg; +} + +void callback_show_error(const char *error_msg, void *context) +{ + saver_state(context)->password_prompt = error_msg; +} + +void callback_prompt_user(const char *prompt, void *context) +{ + size_t prompt_len = strlen(prompt); + char *new_prompt = malloc(prompt_len); + strncpy(new_prompt, prompt, prompt_len + 1); + + saver_state_t *state = saver_state(context); + state->password_prompt = new_prompt; + state->cursor_animating = true; + state->input_allowed = true; +} + +void callback_authentication_result(int result, void *context) +{ + saver_state_t *state = saver_state(context); + if (result == 0) { + state->is_authenticated = true; + } else { + // Try again + state->password_buffer[0] = '\0'; + } } /* @@ -147,7 +204,19 @@ static int runloop(cairo_surface_t *surface) state.status_font = status_font; state.password_buffer = calloc(1, kMaxPasswordLength); state.password_buffer_len = kMaxPasswordLength; - state.cursor_animating = true; + state.cursor_animating = false; + state.input_allowed = false; + state.password_prompt = ""; + state.is_authenticated = false; + + auth_callbacks_t callbacks = { + .info_handler = callback_show_info, + .error_handler = callback_show_error, + .prompt_handler = callback_prompt_user, + .result_handler = callback_authentication_result + }; + + state.auth_handle = auth_begin_authentication(callbacks, &state); // Main run loop struct timespec sleep_time = { 0, 5000000 }; @@ -164,6 +233,11 @@ static int runloop(cairo_surface_t *surface) cairo_surface_flush(surface); nanosleep(&sleep_time, NULL); + + if (state.is_authenticated) { + // We're done here + break; + } } // Cleanup diff --git a/src/render.c b/src/render.c index 214af8b..7860189 100644 --- a/src/render.c +++ b/src/render.c @@ -82,9 +82,10 @@ void draw_password_field(saver_state_t *state) cairo_t *cr = state->ctx; // Draw status text + const char *prompt = (state->password_prompt ?: "???"); cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); pango_layout_set_font_description(state->pango_layout, state->status_font); - pango_layout_set_text(state->pango_layout, "Password: ", -1); + pango_layout_set_text(state->pango_layout, prompt, -1); int t_width, t_height; pango_layout_get_size(state->pango_layout, &t_width, &t_height); diff --git a/src/render.h b/src/render.h index 5d387e6..902763d 100644 --- a/src/render.h +++ b/src/render.h @@ -6,6 +6,8 @@ #pragma once +#include "auth.h" + #include #include #include @@ -25,12 +27,17 @@ typedef struct { int canvas_width; int canvas_height; + bool input_allowed; bool cursor_animating; double cursor_opacity; double cursor_fade_direction; + bool is_authenticated; + const char *password_prompt; char *password_buffer; size_t password_buffer_len; + + struct auth_handle_t *auth_handle; } saver_state_t; // The purple sidebar -- cgit v1.2.3