slock

git clone git://mattcarlson.org/repos/slock.git
Log | Files | Refs

slock.c (14583B)


      1 /* See LICENSE file for license details. */
      2 #define _XOPEN_SOURCE 500
      3 #if HAVE_SHADOW_H
      4 #include <shadow.h>
      5 #endif
      6 
      7 #include <ctype.h>
      8 #include <errno.h>
      9 #include <math.h>
     10 #include <grp.h>
     11 #include <pwd.h>
     12 #include <stdarg.h>
     13 #include <stdlib.h>
     14 #include <stdio.h>
     15 #include <string.h>
     16 #include <unistd.h>
     17 #include <sys/types.h>
     18 #include <fontconfig/fontconfig.h>
     19 #include <X11/extensions/Xrandr.h>
     20 #include <X11/extensions/Xinerama.h>
     21 #include <X11/keysym.h>
     22 #include <X11/Xlib.h>
     23 #include <X11/Xutil.h>
     24 #include <X11/XKBlib.h>
     25 #include <X11/Xresource.h>
     26 #include <X11/Xft/Xft.h>
     27 
     28 #include "arg.h"
     29 #include "util.h"
     30 
     31 char *argv0;
     32 
     33 /* global count to prevent repeated error messages */
     34 int count_error = 0;
     35 
     36 enum {
     37 	INIT,
     38 	INPUT,
     39 	FAILED,
     40     CAPS,
     41 	NUMCOLS
     42 };
     43 
     44 struct lock {
     45 	int screen;
     46 	Window root, win;
     47 	Pixmap pmap;
     48 	unsigned long colors[NUMCOLS];
     49 };
     50 
     51 struct xrandr {
     52 	int active;
     53 	int evbase;
     54 	int errbase;
     55 };
     56 
     57 /* Xresources preferences */
     58 enum resource_type {
     59     STRING = 0,
     60     INTEGER = 1,
     61     FLOAT = 2
     62 };
     63 
     64 typedef struct {
     65     char *name;
     66     enum resource_type type;
     67     void *dst;
     68 } ResourcePref;
     69 
     70 #include "config.h"
     71 
     72 static void
     73 die(const char *errstr, ...)
     74 {
     75 	va_list ap;
     76 
     77 	va_start(ap, errstr);
     78 	vfprintf(stderr, errstr, ap);
     79 	va_end(ap);
     80 	exit(1);
     81 }
     82 
     83 #ifdef __linux__
     84 #include <fcntl.h>
     85 #include <linux/oom.h>
     86 
     87 static void
     88 dontkillme(void)
     89 {
     90 	FILE *f;
     91 	const char oomfile[] = "/proc/self/oom_score_adj";
     92 
     93 	if (!(f = fopen(oomfile, "w"))) {
     94 		if (errno == ENOENT)
     95 			return;
     96 		die("slock: fopen %s: %s\n", oomfile, strerror(errno));
     97 	}
     98 	fprintf(f, "%d", OOM_SCORE_ADJ_MIN);
     99 	if (fclose(f)) {
    100 		if (errno == EACCES)
    101 			die("slock: unable to disable OOM killer. "
    102 			    "Make sure to suid or sgid slock.\n");
    103 		else
    104 			die("slock: fclose %s: %s\n", oomfile, strerror(errno));
    105 	}
    106 }
    107 #endif
    108 
    109 static void
    110 writemessage(Display *dpy, Window win, int screen)
    111 {
    112     int len, line_len, width, height, s_width, s_height, i, j, k, tab_replace, tab_size;
    113     XftFont *fontinfo;
    114     XftColor xftcolor;
    115     XftDraw *xftdraw;
    116     XGlyphInfo ext_msg, ext_space;
    117     XineramaScreenInfo *xsi;
    118     xftdraw = XftDrawCreate(dpy, win, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));
    119     fontinfo = XftFontOpenName(dpy, screen, font_name);
    120     XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), text_color, &xftcolor);
    121 
    122     if (fontinfo == NULL) {
    123         if (count_error == 0) {
    124             fprintf(stderr, "slock: Unable to load font \"%s\"\n", font_name);
    125             count_error++;
    126         }
    127         return;
    128     }
    129 
    130     XftTextExtentsUtf8(dpy, fontinfo, (XftChar8 *) " ", 1, &ext_space);
    131     tab_size = 8 * ext_space.width;
    132 
    133     /*  To prevent "Uninitialized" warnings. */
    134     xsi = NULL;
    135 
    136     /*
    137      * Start formatting and drawing text
    138      */
    139 
    140     len = strlen(message);
    141 
    142     /* Max max line length (cut at '\n') */
    143     line_len = 0;
    144     k = 0;
    145     for (i = j = 0; i < len; i++) {
    146         if (message[i] == '\n') {
    147             if (i - j > line_len)
    148                 line_len = i - j;
    149             k++;
    150             i++;
    151             j = i;
    152         }
    153     }
    154     /* If there is only one line */
    155     if (line_len == 0)
    156         line_len = len;
    157 
    158     if (XineramaIsActive(dpy)) {
    159         xsi = XineramaQueryScreens(dpy, &i);
    160         s_width = xsi[0].width;
    161         s_height = xsi[0].height;
    162     } else {
    163         s_width = DisplayWidth(dpy, screen);
    164         s_height = DisplayHeight(dpy, screen);
    165     }
    166 
    167     XftTextExtentsUtf8(dpy, fontinfo, (XftChar8 *)message, line_len, &ext_msg);
    168     height = s_height*3/7 - (k*20)/3;
    169     width  = (s_width - ext_msg.width)/2;
    170 
    171     /* Look for '\n' and print the text between them. */
    172     for (i = j = k = 0; i <= len; i++) {
    173         /* i == len is the special case for the last line */
    174         if (i == len || message[i] == '\n') {
    175             tab_replace = 0;
    176             while (message[j] == '\t' && j < i) {
    177                 tab_replace++;
    178                 j++;
    179             }
    180 
    181             XftDrawStringUtf8(xftdraw, &xftcolor, fontinfo, width + tab_size*tab_replace, height + 20*k, (XftChar8 *)(message + j), i - j);
    182             while (i < len && message[i] == '\n') {
    183                 i++;
    184                 j = i;
    185                 k++;
    186             }
    187         }
    188     }
    189 
    190     /* xsi should not be NULL anyway if Xinerama is active, but to be safe */
    191     if (XineramaIsActive(dpy) && xsi != NULL)
    192         XFree(xsi);
    193 
    194     XftFontClose(dpy, fontinfo);
    195     XftColorFree(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), &xftcolor);
    196     XftDrawDestroy(xftdraw);
    197 }
    198 
    199 static const char *
    200 gethash(void)
    201 {
    202 	const char *hash;
    203 	struct passwd *pw;
    204 
    205 	/* Check if the current user has a password entry */
    206 	errno = 0;
    207 	if (!(pw = getpwuid(getuid()))) {
    208 		if (errno)
    209 			die("slock: getpwuid: %s\n", strerror(errno));
    210 		else
    211 			die("slock: cannot retrieve password entry\n");
    212 	}
    213 	hash = pw->pw_passwd;
    214 
    215 #if HAVE_SHADOW_H
    216 	if (!strcmp(hash, "x")) {
    217 		struct spwd *sp;
    218 		if (!(sp = getspnam(pw->pw_name)))
    219 			die("slock: getspnam: cannot retrieve shadow entry. "
    220 			    "Make sure to suid or sgid slock.\n");
    221 		hash = sp->sp_pwdp;
    222 	}
    223 #else
    224 	if (!strcmp(hash, "*")) {
    225 #ifdef __OpenBSD__
    226 		if (!(pw = getpwuid_shadow(getuid())))
    227 			die("slock: getpwnam_shadow: cannot retrieve shadow entry. "
    228 			    "Make sure to suid or sgid slock.\n");
    229 		hash = pw->pw_passwd;
    230 #else
    231 		die("slock: getpwuid: cannot retrieve shadow entry. "
    232 		    "Make sure to suid or sgid slock.\n");
    233 #endif /* __OpenBSD__ */
    234 	}
    235 #endif /* HAVE_SHADOW_H */
    236 
    237 	return hash;
    238 }
    239 
    240 static void
    241 readpw(Display *dpy, struct xrandr *rr, struct lock **locks, int nscreens,
    242        const char *hash)
    243 {
    244 	XRRScreenChangeNotifyEvent *rre;
    245 	char buf[32], passwd[256], *inputhash;
    246     int caps, num, screen, running, failure, oldc;
    247     unsigned int len, color, indicators;
    248 	KeySym ksym;
    249 	XEvent ev;
    250 
    251 	len = 0;
    252     caps = 0;
    253 	running = 1;
    254 	failure = 0;
    255 	oldc = INIT;
    256 
    257     if (!XkbGetIndicatorState(dpy, XkbUseCoreKbd, &indicators))
    258         caps = indicators & 1;
    259 
    260 	while (running && !XNextEvent(dpy, &ev)) {
    261 		if (ev.type == KeyPress) {
    262 			explicit_bzero(&buf, sizeof(buf));
    263 			num = XLookupString(&ev.xkey, buf, sizeof(buf), &ksym, 0);
    264 			if (IsKeypadKey(ksym)) {
    265 				if (ksym == XK_KP_Enter)
    266 					ksym = XK_Return;
    267 				else if (ksym >= XK_KP_0 && ksym <= XK_KP_9)
    268 					ksym = (ksym - XK_KP_0) + XK_0;
    269 			}
    270 			if (IsFunctionKey(ksym) ||
    271 			    IsKeypadKey(ksym) ||
    272 			    IsMiscFunctionKey(ksym) ||
    273 			    IsPFKey(ksym) ||
    274 			    IsPrivateKeypadKey(ksym))
    275 				continue;
    276 			switch (ksym) {
    277 			case XK_Return:
    278 				passwd[len] = '\0';
    279 				errno = 0;
    280 				if (!(inputhash = crypt(passwd, hash)))
    281 					fprintf(stderr, "slock: crypt: %s\n", strerror(errno));
    282 				else
    283 					running = !!strcmp(inputhash, hash);
    284 				if (running) {
    285 					XBell(dpy, 100);
    286 					failure = 1;
    287 				}
    288 				explicit_bzero(&passwd, sizeof(passwd));
    289 				len = 0;
    290 				break;
    291 			case XK_Escape:
    292 				explicit_bzero(&passwd, sizeof(passwd));
    293 				len = 0;
    294 				break;
    295 			case XK_BackSpace:
    296 				if (len)
    297 					passwd[--len] = '\0';
    298 				break;
    299             case XK_Caps_Lock:
    300                 caps = !caps;
    301                 break;
    302 			default:
    303 				if (num && !iscntrl((int)buf[0]) &&
    304 				    (len + num < sizeof(passwd))) {
    305 					memcpy(passwd + len, buf, num);
    306 					len += num;
    307 				}
    308 				break;
    309 			}
    310             color = len ? (caps ? CAPS : INPUT) : (failure || failonclear ? FAILED : INIT);
    311 			if (running && oldc != color) {
    312 				for (screen = 0; screen < nscreens; screen++) {
    313 					XSetWindowBackground(dpy,
    314 					                     locks[screen]->win,
    315 					                     locks[screen]->colors[color]);
    316 					XClearWindow(dpy, locks[screen]->win);
    317                     writemessage(dpy, locks[screen]->win, screen);
    318 				}
    319 				oldc = color;
    320 			}
    321 		} else if (rr->active && ev.type == rr->evbase + RRScreenChangeNotify) {
    322 			rre = (XRRScreenChangeNotifyEvent*)&ev;
    323 			for (screen = 0; screen < nscreens; screen++) {
    324 				if (locks[screen]->win == rre->window) {
    325 					if (rre->rotation == RR_Rotate_90 ||
    326 					    rre->rotation == RR_Rotate_270)
    327 						XResizeWindow(dpy, locks[screen]->win,
    328 						              rre->height, rre->width);
    329 					else
    330 						XResizeWindow(dpy, locks[screen]->win,
    331 						              rre->width, rre->height);
    332 					XClearWindow(dpy, locks[screen]->win);
    333 					break;
    334 				}
    335 			}
    336 		} else {
    337 			for (screen = 0; screen < nscreens; screen++)
    338 				XRaiseWindow(dpy, locks[screen]->win);
    339 		}
    340 	}
    341 }
    342 
    343 static struct lock *
    344 lockscreen(Display *dpy, struct xrandr *rr, int screen)
    345 {
    346 	char curs[] = {0, 0, 0, 0, 0, 0, 0, 0};
    347 	int i, ptgrab, kbgrab;
    348 	struct lock *lock;
    349 	XColor color, dummy;
    350 	XSetWindowAttributes wa;
    351 	Cursor invisible;
    352 
    353 	if (dpy == NULL || screen < 0 || !(lock = malloc(sizeof(struct lock))))
    354 		return NULL;
    355 
    356 	lock->screen = screen;
    357 	lock->root = RootWindow(dpy, lock->screen);
    358 
    359 	for (i = 0; i < NUMCOLS; i++) {
    360 		XAllocNamedColor(dpy, DefaultColormap(dpy, lock->screen),
    361 		                 colorname[i], &color, &dummy);
    362 		lock->colors[i] = color.pixel;
    363 	}
    364 
    365 	/* init */
    366 	wa.override_redirect = 1;
    367 	wa.background_pixel = lock->colors[INIT];
    368 	lock->win = XCreateWindow(dpy, lock->root, 0, 0,
    369 	                          DisplayWidth(dpy, lock->screen),
    370 	                          DisplayHeight(dpy, lock->screen),
    371 	                          0, DefaultDepth(dpy, lock->screen),
    372 	                          CopyFromParent,
    373 	                          DefaultVisual(dpy, lock->screen),
    374 	                          CWOverrideRedirect | CWBackPixel, &wa);
    375 	lock->pmap = XCreateBitmapFromData(dpy, lock->win, curs, 8, 8);
    376 	invisible = XCreatePixmapCursor(dpy, lock->pmap, lock->pmap,
    377 	                                &color, &color, 0, 0);
    378 	XDefineCursor(dpy, lock->win, invisible);
    379 
    380 	/* Try to grab mouse pointer *and* keyboard for 600ms, else fail the lock */
    381 	for (i = 0, ptgrab = kbgrab = -1; i < 6; i++) {
    382 		if (ptgrab != GrabSuccess) {
    383 			ptgrab = XGrabPointer(dpy, lock->root, False,
    384 			                      ButtonPressMask | ButtonReleaseMask |
    385 			                      PointerMotionMask, GrabModeAsync,
    386 			                      GrabModeAsync, None, invisible, CurrentTime);
    387 		}
    388 		if (kbgrab != GrabSuccess) {
    389 			kbgrab = XGrabKeyboard(dpy, lock->root, True,
    390 			                       GrabModeAsync, GrabModeAsync, CurrentTime);
    391 		}
    392 
    393 		/* input is grabbed: we can lock the screen */
    394 		if (ptgrab == GrabSuccess && kbgrab == GrabSuccess) {
    395 			XMapRaised(dpy, lock->win);
    396 			if (rr->active)
    397 				XRRSelectInput(dpy, lock->win, RRScreenChangeNotifyMask);
    398 
    399 			XSelectInput(dpy, lock->root, SubstructureNotifyMask);
    400 			return lock;
    401 		}
    402 
    403 		/* retry on AlreadyGrabbed but fail on other errors */
    404 		if ((ptgrab != AlreadyGrabbed && ptgrab != GrabSuccess) ||
    405 		    (kbgrab != AlreadyGrabbed && kbgrab != GrabSuccess))
    406 			break;
    407 
    408 		usleep(100000);
    409 	}
    410 
    411 	/* we couldn't grab all input: fail out */
    412 	if (ptgrab != GrabSuccess)
    413 		fprintf(stderr, "slock: unable to grab mouse pointer for screen %d\n",
    414 		        screen);
    415 	if (kbgrab != GrabSuccess)
    416 		fprintf(stderr, "slock: unable to grab keyboard for screen %d\n",
    417 		        screen);
    418 	return NULL;
    419 }
    420 
    421 int
    422 resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst)
    423 {
    424     char **sdst = dst;
    425     int *idst = dst;
    426     float *fdst = dst;
    427 
    428     char fullname[256];
    429     char fullclass[256];
    430     char *type;
    431     XrmValue ret;
    432 
    433     snprintf(fullname, sizeof(fullname), "%s.%s", "slock", name);
    434     snprintf(fullclass, sizeof(fullclass), "%s.%s", "Slock", name);
    435     fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0';
    436 
    437     XrmGetResource(db, fullname, fullclass, &type, &ret);
    438     if (ret.addr == NULL || strncmp("String", type, 64))
    439         return 1;
    440 
    441     switch (rtype) {
    442         case STRING:
    443             *sdst = ret.addr;
    444             break;
    445         case INTEGER:
    446             *idst = strtoul(ret.addr, NULL, 10);
    447             break;
    448         case FLOAT:
    449             *fdst = strtof(ret.addr, NULL);
    450             break;
    451     }
    452     return 0;
    453  }
    454 
    455 void
    456 config_init(Display *dpy)
    457 {
    458     char *resm;
    459     XrmDatabase db;
    460     ResourcePref *p;
    461 
    462     XrmInitialize();
    463     resm = XResourceManagerString(dpy);
    464     if (!resm)
    465         return;
    466 
    467     db = XrmGetStringDatabase(resm);
    468     for (p = resources; p < resources + LEN(resources); p++)
    469         resource_load(db, p->name, p->type, p->dst);
    470 }
    471 
    472 static void
    473 usage(void)
    474 {
    475     die("usage: slock [-v] [-m message] [cmd [arg ...]]\n");
    476 }
    477 
    478 int
    479 main(int argc, char **argv) {
    480 	struct xrandr rr;
    481 	struct lock **locks;
    482 	struct passwd *pwd;
    483 	struct group *grp;
    484 	uid_t duid;
    485 	gid_t dgid;
    486 	const char *hash;
    487 	Display *dpy;
    488 	int s, nlocks, nscreens;
    489 
    490 	ARGBEGIN {
    491 	case 'v':
    492 		fprintf(stderr, "slock-"VERSION"\n");
    493 		return 0;
    494     case 'm':
    495         message = EARGF(usage());
    496         break;
    497 	default:
    498 		usage();
    499 	} ARGEND
    500 
    501 	/* validate drop-user and -group */
    502 	errno = 0;
    503 	if (!(pwd = getpwnam(user)))
    504 		die("slock: getpwnam %s: %s\n", user,
    505 		    errno ? strerror(errno) : "user entry not found");
    506 	duid = pwd->pw_uid;
    507 	errno = 0;
    508 	if (!(grp = getgrnam(group)))
    509 		die("slock: getgrnam %s: %s\n", group,
    510 		    errno ? strerror(errno) : "group entry not found");
    511 	dgid = grp->gr_gid;
    512 
    513 #ifdef __linux__
    514 	dontkillme();
    515 #endif
    516 
    517 	hash = gethash();
    518 	errno = 0;
    519 	if (!crypt("", hash))
    520 		die("slock: crypt: %s\n", strerror(errno));
    521 
    522 	if (!(dpy = XOpenDisplay(NULL)))
    523 		die("slock: cannot open display\n");
    524 
    525 	/* drop privileges */
    526 	if (setgroups(0, NULL) < 0)
    527 		die("slock: setgroups: %s\n", strerror(errno));
    528 	if (setgid(dgid) < 0)
    529 		die("slock: setgid: %s\n", strerror(errno));
    530 	if (setuid(duid) < 0)
    531 		die("slock: setuid: %s\n", strerror(errno));
    532 
    533     config_init(dpy);
    534 
    535 	/* check for Xrandr support */
    536 	rr.active = XRRQueryExtension(dpy, &rr.evbase, &rr.errbase);
    537 
    538 	/* get number of screens in display "dpy" and blank them */
    539 	nscreens = ScreenCount(dpy);
    540 	if (!(locks = calloc(nscreens, sizeof(struct lock *))))
    541 		die("slock: out of memory\n");
    542 	for (nlocks = 0, s = 0; s < nscreens; s++) {
    543         if ((locks[s] = lockscreen(dpy, &rr, s)) != NULL) {
    544             writemessage(dpy, locks[s]->win, s);
    545 			nlocks++;
    546         } else {
    547 			break;
    548         }
    549 	}
    550 	XSync(dpy, 0);
    551 
    552 	/* did we manage to lock everything? */
    553 	if (nlocks != nscreens)
    554 		return 1;
    555 
    556 	/* run post-lock command */
    557 	if (argc > 0) {
    558 		switch (fork()) {
    559 		case -1:
    560 			die("slock: fork failed: %s\n", strerror(errno));
    561 		case 0:
    562 			if (close(ConnectionNumber(dpy)) < 0)
    563 				die("slock: close: %s\n", strerror(errno));
    564 			execvp(argv[0], argv);
    565 			fprintf(stderr, "slock: execvp %s: %s\n", argv[0], strerror(errno));
    566 			_exit(1);
    567 		}
    568 	}
    569 
    570 	/* everything is now blank. Wait for the correct password */
    571 	readpw(dpy, &rr, locks, nscreens, hash);
    572 
    573 	return 0;
    574 }