Merge pull request #123 from jasperla/bsdauth

Add support for bsd_auth(3) as authentication backend
pull/125/head
Michael Stapelberg 2017-04-17 17:00:39 +02:00 committed by GitHub
commit 0bed914e8e
6 changed files with 92 additions and 46 deletions

View File

@ -1,4 +1,5 @@
TOPDIR=$(shell pwd) TOPDIR=$(shell pwd)
UNAME=$(shell uname)
INSTALL=install INSTALL=install
PREFIX=/usr PREFIX=/usr
@ -16,10 +17,14 @@ CFLAGS += -Wall
CPPFLAGS += -D_GNU_SOURCE CPPFLAGS += -D_GNU_SOURCE
CFLAGS += $(shell $(PKG_CONFIG) --cflags cairo xcb-composite xcb-xinerama xcb-atom xcb-image xcb-xkb xkbcommon xkbcommon-x11) CFLAGS += $(shell $(PKG_CONFIG) --cflags cairo xcb-composite xcb-xinerama xcb-atom xcb-image xcb-xkb xkbcommon xkbcommon-x11)
LIBS += $(shell $(PKG_CONFIG) --libs cairo xcb-composite xcb-xinerama xcb-atom xcb-image xcb-xkb xkbcommon xkbcommon-x11) LIBS += $(shell $(PKG_CONFIG) --libs cairo xcb-composite xcb-xinerama xcb-atom xcb-image xcb-xkb xkbcommon xkbcommon-x11)
LIBS += -lpam
LIBS += -lev LIBS += -lev
LIBS += -lm LIBS += -lm
# OpenBSD lacks PAM, use bsd_auth(3) instead.
ifneq ($(UNAME),OpenBSD)
LIBS += -lpam
endif
FILES:=$(wildcard *.c) FILES:=$(wildcard *.c)
FILES:=$(FILES:.c=.o) FILES:=$(FILES:.c=.o)

View File

@ -16,6 +16,7 @@ Many little improvements have been made to i3lock over time:
- You can specify whether i3lock should bell upon a wrong password. - You can specify whether i3lock should bell upon a wrong password.
- i3lock uses PAM and therefore is compatible with LDAP etc. - i3lock uses PAM and therefore is compatible with LDAP etc.
On OpenBSD i3lock uses the bsd_auth(3) framework.
Requirements Requirements
------------ ------------
@ -37,6 +38,9 @@ Running i3lock
Simply invoke the 'i3lock' command. To get out of it, enter your password and Simply invoke the 'i3lock' command. To get out of it, enter your password and
press enter. press enter.
On OpenBSD the `i3lock` binary needs to be setgid `auth` to call the
authentication helpers, e.g. `/usr/libexec/auth/login_passwd`.
Upstream Upstream
-------- --------
Please submit pull requests to https://github.com/i3/i3lock Please submit pull requests to https://github.com/i3/i3lock

View File

@ -18,7 +18,11 @@
#include <xcb/xkb.h> #include <xcb/xkb.h>
#include <err.h> #include <err.h>
#include <assert.h> #include <assert.h>
#ifdef __OpenBSD__
#include <bsd_auth.h>
#else
#include <security/pam_appl.h> #include <security/pam_appl.h>
#endif
#include <getopt.h> #include <getopt.h>
#include <string.h> #include <string.h>
#include <ev.h> #include <ev.h>
@ -28,6 +32,9 @@
#include <xkbcommon/xkbcommon-x11.h> #include <xkbcommon/xkbcommon-x11.h>
#include <cairo.h> #include <cairo.h>
#include <cairo/cairo-xcb.h> #include <cairo/cairo-xcb.h>
#ifdef __OpenBSD__
#include <strings.h> /* explicit_bzero(3) */
#endif
#include "i3lock.h" #include "i3lock.h"
#include "xcb.h" #include "xcb.h"
@ -49,7 +56,9 @@ char color[7] = "ffffff";
uint32_t last_resolution[2]; uint32_t last_resolution[2];
xcb_window_t win; xcb_window_t win;
static xcb_cursor_t cursor; static xcb_cursor_t cursor;
#ifndef __OpenBSD__
static pam_handle_t *pam_handle; static pam_handle_t *pam_handle;
#endif
int input_position = 0; int input_position = 0;
/* Holds the password you enter (in UTF-8). */ /* Holds the password you enter (in UTF-8). */
static char password[512]; static char password[512];
@ -59,11 +68,11 @@ bool unlock_indicator = true;
char *modifier_string = NULL; char *modifier_string = NULL;
static bool dont_fork = false; static bool dont_fork = false;
struct ev_loop *main_loop; struct ev_loop *main_loop;
static struct ev_timer *clear_pam_wrong_timeout; static struct ev_timer *clear_auth_wrong_timeout;
static struct ev_timer *clear_indicator_timeout; static struct ev_timer *clear_indicator_timeout;
static struct ev_timer *discard_passwd_timeout; static struct ev_timer *discard_passwd_timeout;
extern unlock_state_t unlock_state; extern unlock_state_t unlock_state;
extern pam_state_t pam_state; extern auth_state_t auth_state;
int failed_attempts = 0; int failed_attempts = 0;
bool show_failed_attempts = false; bool show_failed_attempts = false;
bool retry_verification = false; bool retry_verification = false;
@ -158,6 +167,11 @@ static bool load_compose_table(const char *locale) {
* *
*/ */
static void clear_password_memory(void) { static void clear_password_memory(void) {
#ifdef __OpenBSD__
/* Use explicit_bzero(3) which was explicitly designed not to be
* optimized out by the compiler. */
explicit_bzero(password, strlen(password));
#else
/* A volatile pointer to the password buffer to prevent the compiler from /* A volatile pointer to the password buffer to prevent the compiler from
* optimizing this out. */ * optimizing this out. */
volatile char *vpassword = password; volatile char *vpassword = password;
@ -167,6 +181,7 @@ static void clear_password_memory(void) {
* compiler from optimizing the calls away, since the value of 'beep' * compiler from optimizing the calls away, since the value of 'beep'
* is not known at compile-time. */ * is not known at compile-time. */
vpassword[c] = c + (int)beep; vpassword[c] = c + (int)beep;
#endif
} }
ev_timer *start_timer(ev_timer *timer_obj, ev_tstamp timeout, ev_callback_t callback) { ev_timer *start_timer(ev_timer *timer_obj, ev_tstamp timeout, ev_callback_t callback) {
@ -206,13 +221,13 @@ static void finish_input(void) {
} }
/* /*
* Resets pam_state to STATE_PAM_IDLE 2 seconds after an unsuccessful * Resets auth_state to STATE_AUTH_IDLE 2 seconds after an unsuccessful
* authentication event. * authentication event.
* *
*/ */
static void clear_pam_wrong(EV_P_ ev_timer *w, int revents) { static void clear_auth_wrong(EV_P_ ev_timer *w, int revents) {
DEBUG("clearing pam wrong\n"); DEBUG("clearing auth wrong\n");
pam_state = STATE_PAM_IDLE; auth_state = STATE_AUTH_IDLE;
redraw_screen(); redraw_screen();
/* Clear modifier string. */ /* Clear modifier string. */
@ -222,9 +237,9 @@ static void clear_pam_wrong(EV_P_ ev_timer *w, int revents) {
} }
/* Now free this timeout. */ /* Now free this timeout. */
STOP_TIMER(clear_pam_wrong_timeout); STOP_TIMER(clear_auth_wrong_timeout);
/* retry with input done during pam verification */ /* retry with input done during auth verification */
if (retry_verification) { if (retry_verification) {
retry_verification = false; retry_verification = false;
finish_input(); finish_input();
@ -248,11 +263,24 @@ static void discard_passwd_cb(EV_P_ ev_timer *w, int revents) {
} }
static void input_done(void) { static void input_done(void) {
STOP_TIMER(clear_pam_wrong_timeout); STOP_TIMER(clear_auth_wrong_timeout);
pam_state = STATE_PAM_VERIFY; auth_state = STATE_AUTH_VERIFY;
unlock_state = STATE_STARTED; unlock_state = STATE_STARTED;
redraw_screen(); redraw_screen();
#ifdef __OpenBSD__
struct passwd *pw;
if (!(pw = getpwuid(getuid())))
errx(1, "unknown uid %u.", getuid());
if (auth_userokay(pw->pw_name, NULL, NULL, password) != 0) {
DEBUG("successfully authenticated\n");
clear_password_memory();
exit(0);
}
#else
if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) { if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) {
DEBUG("successfully authenticated\n"); DEBUG("successfully authenticated\n");
clear_password_memory(); clear_password_memory();
@ -266,12 +294,13 @@ static void input_done(void) {
exit(0); exit(0);
} }
#endif
if (debug_mode) if (debug_mode)
fprintf(stderr, "Authentication failure\n"); fprintf(stderr, "Authentication failure\n");
/* Get state of Caps and Num lock modifiers, to be displayed in /* Get state of Caps and Num lock modifiers, to be displayed in
* STATE_PAM_WRONG state */ * STATE_AUTH_WRONG state */
xkb_mod_index_t idx, num_mods; xkb_mod_index_t idx, num_mods;
const char *mod_name; const char *mod_name;
@ -305,7 +334,7 @@ static void input_done(void) {
} }
} }
pam_state = STATE_PAM_WRONG; auth_state = STATE_AUTH_WRONG;
failed_attempts += 1; failed_attempts += 1;
clear_input(); clear_input();
if (unlock_indicator) if (unlock_indicator)
@ -314,7 +343,7 @@ static void input_done(void) {
/* Clear this state after 2 seconds (unless the user enters another /* Clear this state after 2 seconds (unless the user enters another
* password during that time). */ * password during that time). */
ev_now_update(main_loop); ev_now_update(main_loop);
START_TIMER(clear_pam_wrong_timeout, TSTAMP_N_SECS(2), clear_pam_wrong); START_TIMER(clear_auth_wrong_timeout, TSTAMP_N_SECS(2), clear_auth_wrong);
/* Cancel the clear_indicator_timeout, it would hide the unlock indicator /* Cancel the clear_indicator_timeout, it would hide the unlock indicator
* too early. */ * too early. */
@ -393,7 +422,7 @@ static void handle_key_press(xcb_key_press_event_t *event) {
if ((ksym == XKB_KEY_j || ksym == XKB_KEY_m) && !ctrl) if ((ksym == XKB_KEY_j || ksym == XKB_KEY_m) && !ctrl)
break; break;
if (pam_state == STATE_PAM_WRONG) { if (auth_state == STATE_AUTH_WRONG) {
retry_verification = true; retry_verification = true;
return; return;
} }
@ -597,6 +626,7 @@ void handle_screen_resize(void) {
redraw_screen(); redraw_screen();
} }
#ifndef __OpenBSD__
/* /*
* Callback function for PAM. We only react on password request callbacks. * Callback function for PAM. We only react on password request callbacks.
* *
@ -627,6 +657,7 @@ static int conv_callback(int num_msg, const struct pam_message **msg,
return 0; return 0;
} }
#endif
/* /*
* This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
@ -725,7 +756,7 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
/* /*
* This function is called from a fork()ed child and will raise the i3lock * This function is called from a fork()ed child and will raise the i3lock
* window when the window is obscured, even when the main i3lock process is * window when the window is obscured, even when the main i3lock process is
* blocked due to PAM. * blocked due to the authentication backend.
* *
*/ */
static void raise_loop(xcb_window_t window) { static void raise_loop(xcb_window_t window) {
@ -782,8 +813,10 @@ int main(int argc, char *argv[]) {
struct passwd *pw; struct passwd *pw;
char *username; char *username;
char *image_path = NULL; char *image_path = NULL;
#ifndef __OpenBSD__
int ret; int ret;
struct pam_conv conv = {conv_callback, NULL}; struct pam_conv conv = {conv_callback, NULL};
#endif
int curs_choice = CURS_NONE; int curs_choice = CURS_NONE;
int o; int o;
int optind = 0; int optind = 0;
@ -877,17 +910,21 @@ int main(int argc, char *argv[]) {
* the unlock indicator upon keypresses. */ * the unlock indicator upon keypresses. */
srand(time(NULL)); srand(time(NULL));
#ifndef __OpenBSD__
/* Initialize PAM */ /* Initialize PAM */
if ((ret = pam_start("i3lock", username, &conv, &pam_handle)) != PAM_SUCCESS) if ((ret = pam_start("i3lock", username, &conv, &pam_handle)) != PAM_SUCCESS)
errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret)); errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret));
if ((ret = pam_set_item(pam_handle, PAM_TTY, getenv("DISPLAY"))) != PAM_SUCCESS) if ((ret = pam_set_item(pam_handle, PAM_TTY, getenv("DISPLAY"))) != PAM_SUCCESS)
errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret)); errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret));
#endif
/* Using mlock() as non-super-user seems only possible in Linux. Users of other /* Using mlock() as non-super-user seems only possible in Linux and OpenBSD.
* operating systems should use encrypted swap/no swap (or remove the ifdef and * Users of other operating systems should use encrypted swap/no swap
* run i3lock as super-user). */ * (or remove the ifdef and run i3lock as super-user).
#if defined(__linux__) * NB: Alas, swap is encrypted by default on OpenBSD so swapping out
* is not necessarily an issue. */
#if defined(__linux__) || defined(__OpenBSD__)
/* Lock the area where we store the password in memory, we dont want it to /* Lock the area where we store the password in memory, we dont want it to
* be swapped to disk. Since Linux 2.6.9, this does not require any * be swapped to disk. Since Linux 2.6.9, this does not require any
* privileges, just enough bytes in the RLIMIT_MEMLOCK limit. */ * privileges, just enough bytes in the RLIMIT_MEMLOCK limit. */
@ -985,7 +1022,7 @@ int main(int argc, char *argv[]) {
cursor = create_cursor(conn, screen, win, curs_choice); cursor = create_cursor(conn, screen, win, curs_choice);
/* Display the "locking…" message while trying to grab the pointer/keyboard. */ /* Display the "locking…" message while trying to grab the pointer/keyboard. */
pam_state = STATE_PAM_LOCK; auth_state = STATE_AUTH_LOCK;
grab_pointer_and_keyboard(conn, screen, cursor); grab_pointer_and_keyboard(conn, screen, cursor);
pid_t pid = fork(); pid_t pid = fork();
@ -1012,7 +1049,7 @@ int main(int argc, char *argv[]) {
errx(EXIT_FAILURE, "Could not initialize libev. Bad LIBEV_FLAGS?\n"); errx(EXIT_FAILURE, "Could not initialize libev. Bad LIBEV_FLAGS?\n");
/* Explicitly call the screen redraw in case "locking…" message was displayed */ /* Explicitly call the screen redraw in case "locking…" message was displayed */
pam_state = STATE_PAM_IDLE; auth_state = STATE_AUTH_IDLE;
redraw_screen(); redraw_screen();
struct ev_io *xcb_watcher = calloc(sizeof(struct ev_io), 1); struct ev_io *xcb_watcher = calloc(sizeof(struct ev_io), 1);

View File

@ -78,7 +78,7 @@ static xcb_visualtype_t *vistype;
/* Maintain the current unlock/PAM state to draw the appropriate unlock /* Maintain the current unlock/PAM state to draw the appropriate unlock
* indicator. */ * indicator. */
unlock_state_t unlock_state; unlock_state_t unlock_state;
pam_state_t pam_state; auth_state_t auth_state;
/* /*
* Returns the scaling factor of the current screen. E.g., on a 227 DPI MacBook * Returns the scaling factor of the current screen. E.g., on a 227 DPI MacBook
@ -141,7 +141,7 @@ xcb_pixmap_t draw_image(uint32_t *resolution) {
} }
if (unlock_indicator && if (unlock_indicator &&
(unlock_state >= STATE_KEY_PRESSED || pam_state > STATE_PAM_IDLE)) { (unlock_state >= STATE_KEY_PRESSED || auth_state > STATE_AUTH_IDLE)) {
cairo_scale(ctx, scaling_factor(), scaling_factor()); cairo_scale(ctx, scaling_factor(), scaling_factor());
/* Draw a (centered) circle with transparent background. */ /* Draw a (centered) circle with transparent background. */
cairo_set_line_width(ctx, 10.0); cairo_set_line_width(ctx, 10.0);
@ -154,12 +154,12 @@ xcb_pixmap_t draw_image(uint32_t *resolution) {
/* Use the appropriate color for the different PAM states /* Use the appropriate color for the different PAM states
* (currently verifying, wrong password, or default) */ * (currently verifying, wrong password, or default) */
switch (pam_state) { switch (auth_state) {
case STATE_PAM_VERIFY: case STATE_AUTH_VERIFY:
case STATE_PAM_LOCK: case STATE_AUTH_LOCK:
cairo_set_source_rgba(ctx, 0, 114.0 / 255, 255.0 / 255, 0.75); cairo_set_source_rgba(ctx, 0, 114.0 / 255, 255.0 / 255, 0.75);
break; break;
case STATE_PAM_WRONG: case STATE_AUTH_WRONG:
case STATE_I3LOCK_LOCK_FAILED: case STATE_I3LOCK_LOCK_FAILED:
cairo_set_source_rgba(ctx, 250.0 / 255, 0, 0, 0.75); cairo_set_source_rgba(ctx, 250.0 / 255, 0, 0, 0.75);
break; break;
@ -169,16 +169,16 @@ xcb_pixmap_t draw_image(uint32_t *resolution) {
} }
cairo_fill_preserve(ctx); cairo_fill_preserve(ctx);
switch (pam_state) { switch (auth_state) {
case STATE_PAM_VERIFY: case STATE_AUTH_VERIFY:
case STATE_PAM_LOCK: case STATE_AUTH_LOCK:
cairo_set_source_rgb(ctx, 51.0 / 255, 0, 250.0 / 255); cairo_set_source_rgb(ctx, 51.0 / 255, 0, 250.0 / 255);
break; break;
case STATE_PAM_WRONG: case STATE_AUTH_WRONG:
case STATE_I3LOCK_LOCK_FAILED: case STATE_I3LOCK_LOCK_FAILED:
cairo_set_source_rgb(ctx, 125.0 / 255, 51.0 / 255, 0); cairo_set_source_rgb(ctx, 125.0 / 255, 51.0 / 255, 0);
break; break;
case STATE_PAM_IDLE: case STATE_AUTH_IDLE:
cairo_set_source_rgb(ctx, 51.0 / 255, 125.0 / 255, 0); cairo_set_source_rgb(ctx, 51.0 / 255, 125.0 / 255, 0);
break; break;
} }
@ -205,14 +205,14 @@ xcb_pixmap_t draw_image(uint32_t *resolution) {
cairo_set_source_rgb(ctx, 0, 0, 0); cairo_set_source_rgb(ctx, 0, 0, 0);
cairo_select_font_face(ctx, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_select_font_face(ctx, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(ctx, 28.0); cairo_set_font_size(ctx, 28.0);
switch (pam_state) { switch (auth_state) {
case STATE_PAM_VERIFY: case STATE_AUTH_VERIFY:
text = "verifying…"; text = "verifying…";
break; break;
case STATE_PAM_LOCK: case STATE_AUTH_LOCK:
text = "locking…"; text = "locking…";
break; break;
case STATE_PAM_WRONG: case STATE_AUTH_WRONG:
text = "wrong!"; text = "wrong!";
break; break;
case STATE_I3LOCK_LOCK_FAILED: case STATE_I3LOCK_LOCK_FAILED:
@ -245,7 +245,7 @@ xcb_pixmap_t draw_image(uint32_t *resolution) {
cairo_close_path(ctx); cairo_close_path(ctx);
} }
if (pam_state == STATE_PAM_WRONG && (modifier_string != NULL)) { if (auth_state == STATE_AUTH_WRONG && (modifier_string != NULL)) {
cairo_text_extents_t extents; cairo_text_extents_t extents;
double x, y; double x, y;
@ -334,7 +334,7 @@ xcb_pixmap_t draw_image(uint32_t *resolution) {
* *
*/ */
void redraw_screen(void) { void redraw_screen(void) {
DEBUG("redraw_screen(unlock_state = %d, pam_state = %d)\n", unlock_state, pam_state); DEBUG("redraw_screen(unlock_state = %d, auth_state = %d)\n", unlock_state, auth_state);
xcb_pixmap_t bg_pixmap = draw_image(last_resolution); xcb_pixmap_t bg_pixmap = draw_image(last_resolution);
xcb_change_window_attributes(conn, win, XCB_CW_BACK_PIXMAP, (uint32_t[1]){bg_pixmap}); xcb_change_window_attributes(conn, win, XCB_CW_BACK_PIXMAP, (uint32_t[1]){bg_pixmap});
/* XXX: Possible optimization: Only update the area in the middle of the /* XXX: Possible optimization: Only update the area in the middle of the

View File

@ -11,12 +11,12 @@ typedef enum {
} unlock_state_t; } unlock_state_t;
typedef enum { typedef enum {
STATE_PAM_IDLE = 0, /* no PAM interaction at the moment */ STATE_AUTH_IDLE = 0, /* no authenticator interaction at the moment */
STATE_PAM_VERIFY = 1, /* currently verifying the password via PAM */ STATE_AUTH_VERIFY = 1, /* currently verifying the password via authenticator */
STATE_PAM_LOCK = 2, /* currently locking the screen */ STATE_AUTH_LOCK = 2, /* currently locking the screen */
STATE_PAM_WRONG = 3, /* the password was wrong */ STATE_AUTH_WRONG = 3, /* the password was wrong */
STATE_I3LOCK_LOCK_FAILED = 4 /* i3lock failed to load */ STATE_I3LOCK_LOCK_FAILED = 4 /* i3lock failed to load */
} pam_state_t; } auth_state_t;
xcb_pixmap_t draw_image(uint32_t* resolution); xcb_pixmap_t draw_image(uint32_t* resolution);
void redraw_screen(void); void redraw_screen(void);

4
xcb.c
View File

@ -24,7 +24,7 @@
#include "cursors.h" #include "cursors.h"
#include "unlock_indicator.h" #include "unlock_indicator.h"
extern pam_state_t pam_state; extern auth_state_t auth_state;
xcb_connection_t *conn; xcb_connection_t *conn;
xcb_screen_t *screen; xcb_screen_t *screen;
@ -262,7 +262,7 @@ void grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb
/* After trying for 10000 times, i3lock will display an error message /* After trying for 10000 times, i3lock will display an error message
* for 2 sec prior to terminate. */ * for 2 sec prior to terminate. */
if (tries <= 0) { if (tries <= 0) {
pam_state = STATE_I3LOCK_LOCK_FAILED; auth_state = STATE_I3LOCK_LOCK_FAILED;
redraw_screen(); redraw_screen();
sleep(1); sleep(1);
errx(EXIT_FAILURE, "Cannot grab pointer/keyboard"); errx(EXIT_FAILURE, "Cannot grab pointer/keyboard");