slock/slock.c
FRIGN 3bb868e408 Refactor main()
- Add arg.h and fix usage
	Given slock is suid we don't want to have half-measures in place to
	parse the arguments in case the code is changed in the future with
	somebody not paying enough attention.

	Also, fix the usage string output to be more consistent across the
	suckless toolbase and make it reflect the manpage entry.

- Comments
	Use proper block comments and add/change them where necessary
	to help in studying the code.

- Error messages
	Consistently prepend them with "slock:" and fix wording and
	do a proper cleanup before quitting (XCloseDisplay and free
	the locks), making the die() semantics consistent with st's.

- getpwuid() error reporting
	Properly present an error message if getpwuid() fails.

- fork() error reporting
	Properly present an error message if fork() fails. If we cannot
	close the connection within the fork context we abort the
	operation and report an error.

- execvp() error handling
	If execvp fails, we cannot call die() afterwards as this implies
	calling exit(). We must use _exit() to prevent the libc from
	doing now "illegal" cleanup-work.
2016-08-22 23:22:20 +02:00

380 lines
8.6 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 <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>
#if HAVE_BSD_AUTH
#include <login_cap.h>
#include <bsd_auth.h>
#endif
#include "arg.h"
#include "util.h"
char *argv0;
enum {
INIT,
INPUT,
FAILED,
NUMCOLS
};
#include "config.h"
typedef struct {
int screen;
Window root, win;
Pixmap pmap;
unsigned long colors[NUMCOLS];
} Lock;
static Lock **locks;
static int nscreens;
static Bool running = True;
static Bool failure = False;
static Bool rr;
static int rrevbase;
static int rrerrbase;
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>
static void
dontkillme(void)
{
int fd;
fd = open("/proc/self/oom_score_adj", O_WRONLY);
if (fd < 0 && errno == ENOENT) {
return;
}
if (fd < 0 || write(fd, "-1000\n", (sizeof("-1000\n") - 1)) !=
(sizeof("-1000\n") - 1) || close(fd) != 0) {
die("can't tame the oom-killer. is suid or sgid set?\n");
}
}
#endif
#ifndef HAVE_BSD_AUTH
/* only run as root */
static const char *
getpw(void)
{
const char *rval;
struct passwd *pw;
errno = 0;
if (!(pw = getpwuid(getuid()))) {
if (errno)
die("getpwuid: %s\n", strerror(errno));
else
die("cannot retrieve password entry\n");
}
rval = pw->pw_passwd;
#if HAVE_SHADOW_H
if (rval[0] == 'x' && rval[1] == '\0') {
struct spwd *sp;
if (!(sp = getspnam(getenv("USER"))))
die("cannot retrieve shadow entry (make sure to suid or sgid slock)\n");
rval = sp->sp_pwdp;
}
#endif
/* drop privileges */
if (geteuid() == 0 &&
((getegid() != pw->pw_gid && setgid(pw->pw_gid) < 0) || setuid(pw->pw_uid) < 0))
die("cannot drop privileges\n");
return rval;
}
#endif
static void
#ifdef HAVE_BSD_AUTH
readpw(Display *dpy)
#else
readpw(Display *dpy, const char *pws)
#endif
{
char buf[32], passwd[256];
int num, screen;
unsigned int len, color;
KeySym ksym;
XEvent ev;
static int oldc = INIT;
len = 0;
running = True;
/* As "slock" stands for "Simple X display locker", the DPMS settings
* had been removed and you can set it with "xset" or some other
* utility. This way the user can easily set a customized DPMS
* timeout. */
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;
#ifdef HAVE_BSD_AUTH
running = !auth_userokay(getlogin(), NULL, "auth-xlock", passwd);
#else
running = !!strcmp(crypt(passwd, pws), pws);
#endif
if (running) {
XBell(dpy, 100);
failure = True;
}
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((int)buf[0]) && (len + num < sizeof(passwd))) {
memcpy(passwd + len, buf, num);
len += num;
}
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 && ev.type == rrevbase + RRScreenChangeNotify) {
XRRScreenChangeNotifyEvent *rre = (XRRScreenChangeNotifyEvent*)&ev;
for (screen = 0; screen < nscreens; screen++) {
if (locks[screen]->win == rre->window) {
XResizeWindow(dpy, locks[screen]->win, rre->width, rre->height);
XClearWindow(dpy, locks[screen]->win);
}
}
} else for (screen = 0; screen < nscreens; screen++)
XRaiseWindow(dpy, locks[screen]->win);
}
}
static void
unlockscreen(Display *dpy, Lock *lock)
{
if(dpy == NULL || lock == NULL)
return;
XUngrabPointer(dpy, CurrentTime);
XFreeColors(dpy, DefaultColormap(dpy, lock->screen), lock->colors, NUMCOLS, 0);
XFreePixmap(dpy, lock->pmap);
XDestroyWindow(dpy, lock->win);
free(lock);
}
static Lock *
lockscreen(Display *dpy, int screen)
{
char curs[] = {0, 0, 0, 0, 0, 0, 0, 0};
unsigned int len;
int i;
Lock *lock;
XColor color, dummy;
XSetWindowAttributes wa;
Cursor invisible;
if (!running || dpy == NULL || screen < 0 || !(lock = malloc(sizeof(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);
XMapRaised(dpy, lock->win);
if (rr)
XRRSelectInput(dpy, lock->win, RRScreenChangeNotifyMask);
/* Try to grab mouse pointer *and* keyboard, else fail the lock */
for (len = 1000; len; len--) {
if (XGrabPointer(dpy, lock->root, False, ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync, None, invisible, CurrentTime) == GrabSuccess)
break;
usleep(1000);
}
if (!len) {
fprintf(stderr, "slock: unable to grab mouse pointer for screen %d\n", screen);
} else {
for (len = 1000; len; len--) {
if (XGrabKeyboard(dpy, lock->root, True, GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess) {
/* everything fine, we grabbed both inputs */
XSelectInput(dpy, lock->root, SubstructureNotifyMask);
return lock;
}
usleep(1000);
}
fprintf(stderr, "slock: unable to grab keyboard for screen %d\n", screen);
}
/* grabbing one of the inputs failed */
running = 0;
unlockscreen(dpy, lock);
return NULL;
}
static void
usage(void)
{
die("usage: slock [-v | cmd [arg ...]]\n");
}
int
main(int argc, char **argv) {
#ifndef HAVE_BSD_AUTH
const char *pws;
#endif
Display *dpy;
int s, nlocks;
ARGBEGIN {
case 'v':
fprintf(stderr, "slock-"VERSION"\n");
return 0;
default:
usage();
} ARGEND
#ifdef __linux__
dontkillme();
#endif
/* Check if the current user has a password entry */
errno = 0;
if (!getpwuid(getuid())) {
if (errno == 0)
die("slock: no password entry for current user\n");
else
die("slock: getpwuid: %s\n", strerror(errno));
}
#ifndef HAVE_BSD_AUTH
pws = getpw();
#endif
if (!(dpy = XOpenDisplay(NULL)))
die("slock: cannot open display\n");
/* check for Xrandr support */
rr = XRRQueryExtension(dpy, &rrevbase, &rrerrbase);
/* get number of screens in display "dpy" and blank them */
nscreens = ScreenCount(dpy);
if (!(locks = malloc(sizeof(Lock *) * nscreens))) {
XCloseDisplay(dpy);
die("slock: out of memory\n");
}
for (nlocks = 0, s = 0; s < nscreens; s++) {
if ((locks[s] = lockscreen(dpy, s)) != NULL)
nlocks++;
}
XSync(dpy, 0);
/* did we actually manage to lock anything? */
if (nlocks == 0) {
/* nothing to protect */
free(locks);
XCloseDisplay(dpy);
return 1;
}
/* run post-lock command */
if (argc > 0) {
switch (fork()) {
case -1:
free(locks);
XCloseDisplay(dpy);
die("slock: fork failed: %s\n", strerror(errno));
case 0:
if (close(ConnectionNumber(dpy)) < 0)
die("slock: close: %s\n", strerror(errno));
execvp(argv[0], argv);
fprintf(stderr, "slock: execvp %s: %s\n", argv[0],
strerror(errno));
_exit(1);
}
}
/* everything is now blank. Wait for the correct password */
#ifdef HAVE_BSD_AUTH
readpw(dpy);
#else
readpw(dpy, pws);
#endif
/* password ok, unlock everything and quit */
for (s = 0; s < nscreens; s++)
unlockscreen(dpy, locks[s]);
free(locks);
XCloseDisplay(dpy);
return 0;
}