diff --git a/Makefile.am b/Makefile.am index 31c7185..6c904ec 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,8 +8,8 @@ bin_PROGRAMS = i3lock dist_man1_MANS = i3lock.1 pamddir = $(sysconfdir)/pam.d -pamd_files = pam/i3lock -pamd_DATA = $(pamd_files) +pamd_files = +pamd_DATA = AM_CPPFLAGS = \ @AX_EXTEND_SRCDIR_CPPFLAGS@ diff --git a/configure.ac b/configure.ac index 2639bf9..6d2b28b 100644 --- a/configure.ac +++ b/configure.ac @@ -82,7 +82,7 @@ case "$host" in # Nothing yet. ;; *) - AC_SEARCH_LIBS([pam_authenticate], [pam]) + AC_SEARCH_LIBS([crypt], [crypt]) ;; esac diff --git a/i3lock.1 b/i3lock.1 index d5c5587..eb54640 100644 --- a/i3lock.1 +++ b/i3lock.1 @@ -43,8 +43,6 @@ i3lock forks, so you can combine it with an alias to suspend to RAM (run "i3lock You can specify either a background color or a PNG image which will be displayed while your screen is locked. .IP \[bu] You can specify whether i3lock should bell upon a wrong password. -.IP \[bu] -i3lock uses PAM and therefore is compatible with LDAP, etc. .SH OPTIONS @@ -66,8 +64,7 @@ like when opening your laptop in a boring lecture. .B \-u, \-\-no-unlock-indicator Disable the unlock indicator. i3lock will by default show an unlock indicator after pressing keys. This will give feedback for every keypress and it will -show you the current PAM state (whether your password is currently being -verified or whether it is wrong). +show you whether your password is currently being verified or whether it is wrong. .TP .BI \-i\ path \fR,\ \fB\-\-image= path @@ -95,7 +92,7 @@ friends by using a screenshot of a Windows desktop as a locking-screen). .TP .B \-e, \-\-ignore-empty-password When an empty password is provided by the user, do not validate -it. Without this option, the empty password will be provided to PAM +it. Without this option, the empty password will be validated and, if invalid, the user will have to wait a few seconds before another try. This can be useful if the XF86ScreenSaver key is used to put a laptop to sleep and bounce on resume or if you happen to wake up diff --git a/i3lock.c b/i3lock.c index 2578b09..d54c6d3 100644 --- a/i3lock.c +++ b/i3lock.c @@ -31,7 +31,9 @@ #ifdef __OpenBSD__ #include #else -#include +#include +#include +#include #endif #include #include @@ -186,7 +188,7 @@ uint32_t last_resolution[2]; xcb_window_t win; static xcb_cursor_t cursor; #ifndef __OpenBSD__ -static pam_handle_t *pam_handle; +const char *hash = NULL; #endif int input_position = 0; /* Holds the password you enter (in UTF-8). */ @@ -256,6 +258,37 @@ char bar_expr[32] = "0\0"; bool bar_bidirectional = false; bool bar_reversed = false; +/* + * Shamelessly stolen from slock. See LICENSE-slock. + * This adjusts the process' out of memory score, + * so it isn't killed by the kernel under any circumstances. + */ +#ifdef __linux__ +#include +#include + +static void +dontkillme(void) +{ + FILE *f; + const char oomfile[] = "/proc/self/oom_score_adj"; + + if (!(f = fopen(oomfile, "w"))) { + if (errno == ENOENT) + return; + errx(EXIT_FAILURE, "fopen %s: %s", oomfile, strerror(errno)); + } + fprintf(f, "%d", OOM_SCORE_ADJ_MIN); + if (fclose(f)) { + if (errno == EACCES) + errx(EXIT_FAILURE, "unable to disable OOM killer. " + "Make sure to suid or sgid i3lock."); + else + errx(EXIT_FAILURE, "fclose %s: %s", oomfile, strerror(errno)); + } +} +#endif + /* isutf, u8_dec © 2005 Jeff Bezanson, public domain */ #define isutf(c) (((c)&0xC0) != 0x80) @@ -511,16 +544,16 @@ static void input_done(void) { return; } #else - if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) { - DEBUG("successfully authenticated\n"); - clear_password_memory(); + /* + * Shamelessly stolen from slock. See LICENSE-slock. + */ + char *inputhash; - /* PAM credentials should be refreshed, this will for example update any kerberos tickets. - * Related to credentials pam_end() needs to be called to cleanup any temporary - * credentials like kerberos /tmp/krb5cc_pam_* files which may of been left behind if the - * refresh of the credentials failed. */ - pam_setcred(pam_handle, PAM_REFRESH_CRED); - pam_end(pam_handle, PAM_SUCCESS); + if (!(inputhash = crypt(password, hash))) + fprintf(stderr, "crypt: %s", strerror(errno)); + else if (!strcmp(inputhash, hash)) { + DEBUG("successfully authenticated"); + clear_password_memory(); ev_break(EV_DEFAULT, EVBREAK_ALL); return; @@ -915,39 +948,6 @@ static bool verify_png_image(const char *image_path) { return true; } -#ifndef __OpenBSD__ -/* - * Callback function for PAM. We only react on password request callbacks. - * - */ -static int conv_callback(int num_msg, const struct pam_message **msg, - struct pam_response **resp, void *appdata_ptr) { - if (num_msg == 0) - return 1; - - /* PAM expects an array of responses, one for each message */ - if ((*resp = calloc(num_msg, sizeof(struct pam_response))) == NULL) { - perror("calloc"); - return 1; - } - - for (int c = 0; c < num_msg; c++) { - if (msg[c]->msg_style != PAM_PROMPT_ECHO_OFF && - msg[c]->msg_style != PAM_PROMPT_ECHO_ON) - continue; - - /* return code is currently not used but should be set to zero */ - resp[c]->resp_retcode = 0; - if ((resp[c]->resp = strdup(password)) == NULL) { - perror("strdup"); - return 1; - } - } - - return 0; -} -#endif - /* * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop @@ -1055,12 +1055,13 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { * */ static void raise_loop(xcb_window_t window) { - xcb_connection_t *conn; xcb_generic_event_t *event; - int screens; - if (xcb_connection_has_error((conn = xcb_connect(NULL, &screens))) > 0) +#ifdef __OpenBSD__ + xcb_connection_t *conn; + if (xcb_connection_has_error((conn = xcb_connect(NULL, NULL))) > 0) errx(EXIT_FAILURE, "Cannot open display\n"); +#endif /* We need to know about the window being obscured or getting destroyed. */ xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, @@ -1176,8 +1177,11 @@ int main(int argc, char *argv[]) { char *username; char *image_path = NULL; #ifndef __OpenBSD__ - int ret; - struct pam_conv conv = {conv_callback, NULL}; + struct passwd *pwd; + struct group *grp; + uid_t duid; + gid_t dgid; + xcb_connection_t *raise_conn; #endif int curs_choice = CURS_NONE; int o; @@ -1312,6 +1316,65 @@ int main(int argc, char *argv[]) { if ((username = pw->pw_name) == NULL) errx(EXIT_FAILURE, "pw->pw_name is NULL.\n"); +#ifndef __OpenBSD__ + /* + * Shamelessly stolen from slock. See LICENSE-slock. + * + * Slock has code to make it run as nobody:nogroup, which has the added + * security that the locker can only be killed by root. + * It causes problems with the xcb_connect in raise_loop, and the main + * xcb_connect, however. + * Because of that, both xcb_connect are ran as root, before dropping the + * privileges to the user, much like is being done with XOpenDisplay + * in slock. + * I'm unsure of any security implications that may have, as it seems to + * run fine, otherwise. + * Please contact me if it's something I _really_ shouldn't do. + */ + + /* If the nobody:nogroup don't exist, just use the password's user */ + duid = pw->pw_uid; + if ((pwd = getpwnam("nobody"))) + duid = pwd->pw_uid; + dgid = pw->pw_gid; + if ((grp = getgrnam("nogroup"))) + dgid = grp->gr_gid; + +#ifdef __linux__ + dontkillme(); +#endif + + hash = pw->pw_passwd; + + if (!strcmp(hash, "x")) { + struct spwd *sp; + if (!(sp = getspnam(pw->pw_name))) + errx(EXIT_FAILURE, "getspnam: cannot retrieve shadow entry. " + "Make sure to suid or sgid i3lock."); + hash = sp->sp_pwdp; + } + + errno = 0; + if (!crypt("", hash)) + errx(EXIT_FAILURE, "crypt: %s", strerror(errno)); + + /* Create the necessary connections before dropping privileges */ + if ((conn = xcb_connect(NULL, NULL)) == NULL || + xcb_connection_has_error(conn)) + errx(EXIT_FAILURE, "Could not connect to X11, maybe you need to set DISPLAY?"); + if ((raise_conn = xcb_connect(NULL, NULL)) == NULL || + xcb_connection_has_error(raise_conn)) + errx(EXIT_FAILURE, "Cannot open display\n"); + + /* drop privileges */ + if (setgroups(0, NULL) < 0) + errx(EXIT_FAILURE, "setgroups: %s", strerror(errno)); + if (setgid(dgid) < 0) + errx(EXIT_FAILURE, "setgid: %s", strerror(errno)); + if (setuid(duid) < 0) + errx(EXIT_FAILURE, "setuid: %s", strerror(errno)); +#endif + char *optstring = "hvnbdc:p:ui:teI:frsS:kB:m"; char *arg = NULL; int opt = 0; @@ -1838,15 +1901,6 @@ int main(int argc, char *argv[]) { * the unlock indicator upon keypresses. */ srand(time(NULL)); -#ifndef __OpenBSD__ - /* Initialize PAM */ - if ((ret = pam_start("i3lock", username, &conv, &pam_handle)) != PAM_SUCCESS) - errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret)); - - if ((ret = pam_set_item(pam_handle, PAM_TTY, getenv("DISPLAY"))) != PAM_SUCCESS) - 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 operating systems should use encrypted swap/no swap * (or remove the ifdef and run i3lock as super-user). @@ -1860,12 +1914,12 @@ int main(int argc, char *argv[]) { err(EXIT_FAILURE, "Could not lock page in memory, check RLIMIT_MEMLOCK"); #endif +#ifdef __OpenBSD__ /* Double checking that connection is good and operatable with xcb */ - int screennr; - if ((conn = xcb_connect(NULL, &screennr)) == NULL || + if ((conn = xcb_connect(NULL, NULL)) == NULL || xcb_connection_has_error(conn)) errx(EXIT_FAILURE, "Could not connect to X11, maybe you need to set DISPLAY?"); - +#endif if (xkb_x11_setup_xkb_extension(conn, @@ -2044,10 +2098,16 @@ int main(int argc, char *argv[]) { if (pid == 0) { /* Child */ close(xcb_get_file_descriptor(conn)); +#ifndef __OpenBSD__ + conn = raise_conn; +#endif maybe_close_sleep_lock_fd(); raise_loop(win); exit(EXIT_SUCCESS); } +#ifndef __OpenBSD__ + close(xcb_get_file_descriptor(raise_conn)); +#endif /* Load the keymap again to sync the current modifier state. Since we first * loaded the keymap, there might have been changes, but starting from now, diff --git a/pam/i3lock b/pam/i3lock deleted file mode 100644 index 6eb8e68..0000000 --- a/pam/i3lock +++ /dev/null @@ -1,6 +0,0 @@ -# -# PAM configuration file for the i3lock screen locker. By default, it includes -# the 'login' configuration file (see /etc/pam.d/login) -# - -auth include login