mirror of
https://git.suckless.org/slock
synced 2026-09-30 13:02:29 +07:00
This command was used for example to suspend-to-ram (man page example). On Linux running this command inherits OOM killer immunity. This could be abused by an other local user on the same machine (abusing resources). To keep the use-case for suspend-to-ram an user could write a wrapper script for it, outside the scope of slock. Simplify argument handling, because the post-lock command parameter is removed. Reported by: Acts1631 <acts1631kjv@proton.me>
383 lines
9.3 KiB
C
383 lines
9.3 KiB
C
/* See LICENSE file for license details. */
|
|
#define _XOPEN_SOURCE 500
|
|
#if HAVE_SHADOW_H
|
|
#include <shadow.h>
|
|
#endif
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <X11/extensions/Xrandr.h>
|
|
#include <X11/keysym.h>
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
|
|
#include "util.h"
|
|
|
|
char *argv0;
|
|
|
|
enum {
|
|
INIT,
|
|
INPUT,
|
|
FAILED,
|
|
NUMCOLS
|
|
};
|
|
|
|
struct lock {
|
|
int screen;
|
|
Window root, win;
|
|
Pixmap pmap;
|
|
unsigned long colors[NUMCOLS];
|
|
};
|
|
|
|
struct xrandr {
|
|
int active;
|
|
int evbase;
|
|
int errbase;
|
|
};
|
|
|
|
#include "config.h"
|
|
|
|
static void
|
|
die(const char *errstr, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, errstr);
|
|
vfprintf(stderr, errstr, ap);
|
|
va_end(ap);
|
|
exit(1);
|
|
}
|
|
|
|
#ifdef __linux__
|
|
#include <fcntl.h>
|
|
#include <linux/oom.h>
|
|
|
|
static void
|
|
dontkillme(void)
|
|
{
|
|
FILE *f;
|
|
const char oomfile[] = "/proc/self/oom_score_adj";
|
|
|
|
if (!(f = fopen(oomfile, "w"))) {
|
|
if (errno == ENOENT)
|
|
return;
|
|
die("slock: fopen %s: %s\n", oomfile, strerror(errno));
|
|
}
|
|
fprintf(f, "%d", OOM_SCORE_ADJ_MIN);
|
|
if (fclose(f)) {
|
|
if (errno == EACCES)
|
|
die("slock: unable to disable OOM killer. "
|
|
"Make sure to suid or sgid slock.\n");
|
|
else
|
|
die("slock: fclose %s: %s\n", oomfile, strerror(errno));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static const char *
|
|
gethash(void)
|
|
{
|
|
const char *hash;
|
|
struct passwd *pw;
|
|
|
|
/* Check if the current user has a password entry */
|
|
errno = 0;
|
|
if (!(pw = getpwuid(getuid()))) {
|
|
if (errno)
|
|
die("slock: getpwuid: %s\n", strerror(errno));
|
|
else
|
|
die("slock: cannot retrieve password entry\n");
|
|
}
|
|
hash = pw->pw_passwd;
|
|
|
|
#if HAVE_SHADOW_H
|
|
if (!strcmp(hash, "x")) {
|
|
struct spwd *sp;
|
|
if (!(sp = getspnam(pw->pw_name)))
|
|
die("slock: getspnam: cannot retrieve shadow entry. "
|
|
"Make sure to suid or sgid slock.\n");
|
|
hash = sp->sp_pwdp;
|
|
}
|
|
#else
|
|
if (!strcmp(hash, "*")) {
|
|
#ifdef __OpenBSD__
|
|
if (!(pw = getpwuid_shadow(getuid())))
|
|
die("slock: getpwnam_shadow: cannot retrieve shadow entry. "
|
|
"Make sure to suid or sgid slock.\n");
|
|
hash = pw->pw_passwd;
|
|
#else
|
|
die("slock: getpwuid: cannot retrieve shadow entry. "
|
|
"Make sure to suid or sgid slock.\n");
|
|
#endif /* __OpenBSD__ */
|
|
}
|
|
#endif /* HAVE_SHADOW_H */
|
|
|
|
return hash;
|
|
}
|
|
|
|
static void
|
|
readpw(Display *dpy, struct xrandr *rr, struct lock **locks, int nscreens,
|
|
const char *hash)
|
|
{
|
|
XRRScreenChangeNotifyEvent *rre;
|
|
char buf[32], passwd[256], *inputhash;
|
|
int num, screen, running, failure, oldc;
|
|
unsigned int len, color;
|
|
KeySym ksym;
|
|
XEvent ev;
|
|
|
|
len = 0;
|
|
running = 1;
|
|
failure = 0;
|
|
oldc = INIT;
|
|
|
|
while (running && !XNextEvent(dpy, &ev)) {
|
|
if (ev.type == KeyPress) {
|
|
explicit_bzero(&buf, sizeof(buf));
|
|
num = XLookupString(&ev.xkey, buf, sizeof(buf), &ksym, 0);
|
|
if (IsKeypadKey(ksym)) {
|
|
if (ksym == XK_KP_Enter)
|
|
ksym = XK_Return;
|
|
else if (ksym >= XK_KP_0 && ksym <= XK_KP_9)
|
|
ksym = (ksym - XK_KP_0) + XK_0;
|
|
}
|
|
if (IsFunctionKey(ksym) ||
|
|
IsKeypadKey(ksym) ||
|
|
IsMiscFunctionKey(ksym) ||
|
|
IsPFKey(ksym) ||
|
|
IsPrivateKeypadKey(ksym))
|
|
continue;
|
|
switch (ksym) {
|
|
case XK_Return:
|
|
passwd[len] = '\0';
|
|
errno = 0;
|
|
if (!(inputhash = crypt(passwd, hash)))
|
|
fprintf(stderr, "slock: crypt: %s\n", strerror(errno));
|
|
else
|
|
running = !!strcmp(inputhash, hash);
|
|
if (running) {
|
|
XBell(dpy, 100);
|
|
failure = 1;
|
|
}
|
|
explicit_bzero(&passwd, sizeof(passwd));
|
|
len = 0;
|
|
break;
|
|
case XK_Escape:
|
|
explicit_bzero(&passwd, sizeof(passwd));
|
|
len = 0;
|
|
break;
|
|
case XK_BackSpace:
|
|
if (len)
|
|
passwd[--len] = '\0';
|
|
break;
|
|
default:
|
|
if (num && !iscntrl((unsigned char)buf[0]) &&
|
|
(len + num < sizeof(passwd))) {
|
|
memcpy(passwd + len, buf, num);
|
|
len += num;
|
|
} else if (buf[0] == '\025') { /* ctrl-u clears input */
|
|
explicit_bzero(&passwd, sizeof(passwd));
|
|
len = 0;
|
|
}
|
|
break;
|
|
}
|
|
color = len ? INPUT : ((failure || failonclear) ? FAILED : INIT);
|
|
if (running && oldc != color) {
|
|
for (screen = 0; screen < nscreens; screen++) {
|
|
XSetWindowBackground(dpy,
|
|
locks[screen]->win,
|
|
locks[screen]->colors[color]);
|
|
XClearWindow(dpy, locks[screen]->win);
|
|
}
|
|
oldc = color;
|
|
}
|
|
} else if (rr->active && ev.type == rr->evbase + RRScreenChangeNotify) {
|
|
rre = (XRRScreenChangeNotifyEvent*)&ev;
|
|
for (screen = 0; screen < nscreens; screen++) {
|
|
if (locks[screen]->win == rre->window) {
|
|
if (rre->rotation == RR_Rotate_90 ||
|
|
rre->rotation == RR_Rotate_270)
|
|
XResizeWindow(dpy, locks[screen]->win,
|
|
rre->height, rre->width);
|
|
else
|
|
XResizeWindow(dpy, locks[screen]->win,
|
|
rre->width, rre->height);
|
|
XClearWindow(dpy, locks[screen]->win);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for (screen = 0; screen < nscreens; screen++)
|
|
XRaiseWindow(dpy, locks[screen]->win);
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct lock *
|
|
lockscreen(Display *dpy, struct xrandr *rr, int screen)
|
|
{
|
|
char curs[] = {0, 0, 0, 0, 0, 0, 0, 0};
|
|
int i, ptgrab, kbgrab;
|
|
struct lock *lock;
|
|
XColor color, dummy;
|
|
XSetWindowAttributes wa;
|
|
Cursor invisible;
|
|
|
|
if (dpy == NULL || screen < 0 || !(lock = malloc(sizeof(struct lock))))
|
|
return NULL;
|
|
|
|
lock->screen = screen;
|
|
lock->root = RootWindow(dpy, lock->screen);
|
|
|
|
for (i = 0; i < NUMCOLS; i++) {
|
|
XAllocNamedColor(dpy, DefaultColormap(dpy, lock->screen),
|
|
colorname[i], &color, &dummy);
|
|
lock->colors[i] = color.pixel;
|
|
}
|
|
|
|
/* init */
|
|
wa.override_redirect = 1;
|
|
wa.background_pixel = lock->colors[INIT];
|
|
lock->win = XCreateWindow(dpy, lock->root, 0, 0,
|
|
DisplayWidth(dpy, lock->screen),
|
|
DisplayHeight(dpy, lock->screen),
|
|
0, DefaultDepth(dpy, lock->screen),
|
|
CopyFromParent,
|
|
DefaultVisual(dpy, lock->screen),
|
|
CWOverrideRedirect | CWBackPixel, &wa);
|
|
lock->pmap = XCreateBitmapFromData(dpy, lock->win, curs, 8, 8);
|
|
invisible = XCreatePixmapCursor(dpy, lock->pmap, lock->pmap,
|
|
&color, &color, 0, 0);
|
|
XDefineCursor(dpy, lock->win, invisible);
|
|
|
|
/* Try to grab mouse pointer *and* keyboard for 600ms, else fail the lock */
|
|
for (i = 0, ptgrab = kbgrab = -1; i < 6; i++) {
|
|
if (ptgrab != GrabSuccess) {
|
|
ptgrab = XGrabPointer(dpy, lock->root, False,
|
|
ButtonPressMask | ButtonReleaseMask |
|
|
PointerMotionMask, GrabModeAsync,
|
|
GrabModeAsync, None, invisible, CurrentTime);
|
|
}
|
|
if (kbgrab != GrabSuccess) {
|
|
kbgrab = XGrabKeyboard(dpy, lock->root, True,
|
|
GrabModeAsync, GrabModeAsync, CurrentTime);
|
|
}
|
|
|
|
/* input is grabbed: we can lock the screen */
|
|
if (ptgrab == GrabSuccess && kbgrab == GrabSuccess) {
|
|
XMapRaised(dpy, lock->win);
|
|
if (rr->active)
|
|
XRRSelectInput(dpy, lock->win, RRScreenChangeNotifyMask);
|
|
|
|
XSelectInput(dpy, lock->root, SubstructureNotifyMask);
|
|
return lock;
|
|
}
|
|
|
|
/* retry on AlreadyGrabbed but fail on other errors */
|
|
if ((ptgrab != AlreadyGrabbed && ptgrab != GrabSuccess) ||
|
|
(kbgrab != AlreadyGrabbed && kbgrab != GrabSuccess))
|
|
break;
|
|
|
|
usleep(100000);
|
|
}
|
|
|
|
/* we couldn't grab all input: fail out */
|
|
if (ptgrab != GrabSuccess)
|
|
fprintf(stderr, "slock: unable to grab mouse pointer for screen %d\n",
|
|
screen);
|
|
if (kbgrab != GrabSuccess)
|
|
fprintf(stderr, "slock: unable to grab keyboard for screen %d\n",
|
|
screen);
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
die("usage: slock [-v]\n");
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv) {
|
|
struct xrandr rr;
|
|
struct lock **locks;
|
|
struct passwd *pwd;
|
|
struct group *grp;
|
|
uid_t duid;
|
|
gid_t dgid;
|
|
const char *hash;
|
|
Display *dpy;
|
|
int s, nlocks, nscreens;
|
|
|
|
if (argc > 1 && !strcmp(argv[1], "-v")) {
|
|
puts("slock-"VERSION);
|
|
return 0;
|
|
} else if (argc > 1) {
|
|
usage();
|
|
}
|
|
|
|
/* validate drop-user and -group */
|
|
errno = 0;
|
|
if (!(pwd = getpwnam(user)))
|
|
die("slock: getpwnam %s: %s\n", user,
|
|
errno ? strerror(errno) : "user entry not found");
|
|
duid = pwd->pw_uid;
|
|
errno = 0;
|
|
if (!(grp = getgrnam(group)))
|
|
die("slock: getgrnam %s: %s\n", group,
|
|
errno ? strerror(errno) : "group entry not found");
|
|
dgid = grp->gr_gid;
|
|
|
|
#ifdef __linux__
|
|
dontkillme();
|
|
#endif
|
|
|
|
hash = gethash();
|
|
errno = 0;
|
|
if (!crypt("", hash))
|
|
die("slock: crypt: %s\n", strerror(errno));
|
|
|
|
if (!(dpy = XOpenDisplay(NULL)))
|
|
die("slock: cannot open display\n");
|
|
|
|
/* drop privileges */
|
|
if (setgroups(0, NULL) < 0)
|
|
die("slock: setgroups: %s\n", strerror(errno));
|
|
if (setgid(dgid) < 0)
|
|
die("slock: setgid: %s\n", strerror(errno));
|
|
if (setuid(duid) < 0)
|
|
die("slock: setuid: %s\n", strerror(errno));
|
|
|
|
/* check for Xrandr support */
|
|
rr.active = XRRQueryExtension(dpy, &rr.evbase, &rr.errbase);
|
|
|
|
/* get number of screens in display "dpy" and blank them */
|
|
nscreens = ScreenCount(dpy);
|
|
if (!(locks = calloc(nscreens, sizeof(struct lock *))))
|
|
die("slock: out of memory\n");
|
|
for (nlocks = 0, s = 0; s < nscreens; s++) {
|
|
if ((locks[s] = lockscreen(dpy, &rr, s)) != NULL)
|
|
nlocks++;
|
|
else
|
|
break;
|
|
}
|
|
XSync(dpy, 0);
|
|
|
|
/* did we manage to lock everything? */
|
|
if (nlocks != nscreens)
|
|
return 1;
|
|
|
|
/* everything is now blank. Wait for the correct password */
|
|
readpw(dpy, &rr, locks, nscreens, hash);
|
|
|
|
return 0;
|
|
}
|