+++ LICENSE-slock @@ -0,0 +1,24 @@ +MIT/X Consortium License + +© 2015-2016 Markus Teich +© 2014 Dimitris Papastamos +© 2006-2014 Anselm R Garbe +© 2014-2016 Laslo Hunhold + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +++ Makefile @@ -22,7 +22,7 @@ # OpenBSD lacks PAM, use bsd_auth(3) instead. ifneq ($(UNAME),OpenBSD) - LIBS += -lpam + LIBS += -lcrypt endif FILES:=$(wildcard *.c) @@ -50,9 +50,7 @@ install: all $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -d $(DESTDIR)$(SYSCONFDIR)/pam.d $(INSTALL) -m 755 i3lock $(DESTDIR)$(PREFIX)/bin/i3lock - $(INSTALL) -m 644 i3lock.pam $(DESTDIR)$(SYSCONFDIR)/pam.d/i3lock uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/i3lock @@ -61,7 +59,7 @@ [ ! -d i3lock-${VERSION} ] || rm -rf i3lock-${VERSION} [ ! -e i3lock-${VERSION}.tar.bz2 ] || rm i3lock-${VERSION}.tar.bz2 mkdir i3lock-${VERSION} - cp *.c *.h i3lock.1 i3lock.pam Makefile LICENSE README.md CHANGELOG i3lock-${VERSION} + cp *.c *.h i3lock.1 Makefile LICENSE README.md CHANGELOG i3lock-${VERSION} sed -e 's/^\s*I3LOCK_VERSION:=\(.*\)/I3LOCK_VERSION:=$(shell /bin/echo '${I3LOCK_VERSION}' | sed 's/\\/\\\\/g')/g;s/^VERSION:=\(.*\)/VERSION:=${VERSION}/g' Makefile > i3lock-${VERSION}/Makefile tar cfj i3lock-${VERSION}.tar.bz2 i3lock-${VERSION} rm -rf i3lock-${VERSION} +++ i3lock.1 @@ -43,8 +43,6 @@ 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 @@ .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 @@ .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 +++ i3lock.c @@ -21,7 +21,9 @@ #ifdef __OpenBSD__ #include #else -#include +#include +#include +#include #endif #include #include @@ -59,7 +61,7 @@ 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). */ @@ -93,6 +95,37 @@ bool ignore_empty_password = false; bool skip_repeated_empty_password = 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) @@ -285,16 +318,16 @@ 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; @@ -626,39 +659,6 @@ redraw_screen(); } -#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 @@ -766,13 +766,15 @@ * */ static void raise_loop(xcb_window_t window) { - xcb_connection_t *conn; xcb_generic_event_t *event; - int screens; - if ((conn = xcb_connect(NULL, &screens)) == NULL || +#ifdef __OpenBSD__ + xcb_connection_t *conn; + + if ((conn = xcb_connect(NULL, NULL)) == NULL || xcb_connection_has_error(conn)) 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, @@ -820,8 +822,11 @@ 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; @@ -848,6 +853,65 @@ 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:f"; while ((o = getopt_long(argc, argv, optstring, longopts, &longoptind)) != -1) { switch (o) { @@ -916,15 +980,6 @@ * 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). @@ -938,11 +993,12 @@ 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, XKB_X11_MIN_MAJOR_XKB_VERSION, @@ -1056,10 +1112,16 @@ 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, +++ i3lock.pam @@ -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