slock.c
1/* See LICENSE file for license details. */
2#define _XOPEN_SOURCE 500
3#define LENGTH(X) (sizeof X / sizeof X[0])
4#if HAVE_SHADOW_H
5#include <shadow.h>
6#endif
7
8#include <ctype.h>
9#include <errno.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 <X11/extensions/Xrandr.h>
19#ifdef XINERAMA
20#include <X11/extensions/Xinerama.h>
21#endif
22#include <X11/keysym.h>
23#include <X11/Xlib.h>
24#include <X11/Xutil.h>
25#include <X11/Xft/Xft.h>
26#include <Imlib2.h>
27
28#include "arg.h"
29#include "util.h"
30
31char *argv0;
32
33enum {
34 BACKGROUND,
35 INIT,
36 INPUT,
37 FAILED,
38 NUMCOLS
39};
40
41#include "config.h"
42
43struct lock {
44 int screen;
45 Window root, win;
46 Pixmap pmap;
47 Pixmap bgmap;
48 unsigned long colors[NUMCOLS];
49 unsigned int x, y;
50 unsigned int xoff, yoff, mw, mh;
51 Drawable drawable;
52 GC gc;
53 XRectangle rectangles[LENGTH(rectangles)];
54};
55
56struct xrandr {
57 int active;
58 int evbase;
59 int errbase;
60};
61
62static void
63die(const char *errstr, ...)
64{
65 va_list ap;
66
67 va_start(ap, errstr);
68 vfprintf(stderr, errstr, ap);
69 va_end(ap);
70 exit(1);
71}
72
73#ifdef __linux__
74#include <fcntl.h>
75#include <linux/oom.h>
76
77static void
78dontkillme(void)
79{
80 FILE *f;
81 const char oomfile[] = "/proc/self/oom_score_adj";
82
83 if (!(f = fopen(oomfile, "w"))) {
84 if (errno == ENOENT)
85 return;
86 die("slock: fopen %s: %s\n", oomfile, strerror(errno));
87 }
88 fprintf(f, "%d", OOM_SCORE_ADJ_MIN);
89 if (fclose(f)) {
90 if (errno == EACCES)
91 die("slock: unable to disable OOM killer. "
92 "Make sure to suid or sgid slock.\n");
93 else
94 die("slock: fclose %s: %s\n", oomfile, strerror(errno));
95 }
96}
97#endif
98
99static const char *
100gethash(void)
101{
102 const char *hash;
103 struct passwd *pw;
104
105 /* Check if the current user has a password entry */
106 errno = 0;
107 if (!(pw = getpwuid(getuid()))) {
108 if (errno)
109 die("slock: getpwuid: %s\n", strerror(errno));
110 else
111 die("slock: cannot retrieve password entry\n");
112 }
113 hash = pw->pw_passwd;
114
115#if HAVE_SHADOW_H
116 if (!strcmp(hash, "x")) {
117 struct spwd *sp;
118 if (!(sp = getspnam(pw->pw_name)))
119 die("slock: getspnam: cannot retrieve shadow entry. "
120 "Make sure to suid or sgid slock.\n");
121 hash = sp->sp_pwdp;
122 }
123#else
124 if (!strcmp(hash, "*")) {
125#ifdef __OpenBSD__
126 if (!(pw = getpwuid_shadow(getuid())))
127 die("slock: getpwnam_shadow: cannot retrieve shadow entry. "
128 "Make sure to suid or sgid slock.\n");
129 hash = pw->pw_passwd;
130#else
131 die("slock: getpwuid: cannot retrieve shadow entry. "
132 "Make sure to suid or sgid slock.\n");
133#endif /* __OpenBSD__ */
134 }
135#endif /* HAVE_SHADOW_H */
136
137 return hash;
138}
139
140static void
141resizerectangles(struct lock *lock)
142{
143 int i;
144
145 for (i = 0; i < LENGTH(rectangles); i++){
146 lock->rectangles[i].x = (rectangles[i].x * logosize) + lock->xoff + ((lock->mw) / 2) - (logow / 2 * logosize);
147 lock->rectangles[i].y = (rectangles[i].y * logosize) + lock->yoff + ((lock->mh) / 2) - (logoh / 2 * logosize);
148 lock->rectangles[i].width = rectangles[i].width * logosize;
149 lock->rectangles[i].height = rectangles[i].height * logosize;
150 }
151}
152
153static void
154drawlogo(Display *dpy, struct lock *lock, int color)
155{
156 if (lock->bgmap) {
157 lock->drawable = lock->bgmap;
158 } else {
159 XSetForeground(dpy, lock->gc, lock->colors[BACKGROUND]);
160 XFillRectangle(dpy, lock->drawable, lock->gc, 0, 0, lock->x, lock->y);
161 }
162 XSetForeground(dpy, lock->gc, lock->colors[color]);
163 XFillRectangles(dpy, lock->drawable, lock->gc, lock->rectangles, LENGTH(rectangles));
164 XCopyArea(dpy, lock->drawable, lock->win, lock->gc, 0, 0, lock->x, lock->y, 0, 0);
165 XSync(dpy, False);
166}
167
168static void
169readpw(Display *dpy, struct xrandr *rr, struct lock **locks, int nscreens,
170 const char *hash)
171{
172 XRRScreenChangeNotifyEvent *rre;
173 char buf[32], passwd[256], *inputhash;
174 int num, screen, running, failure, oldc;
175 unsigned int len, color;
176 KeySym ksym;
177 XEvent ev;
178
179 len = 0;
180 running = 1;
181 failure = 0;
182 oldc = INIT;
183
184 while (running && !XNextEvent(dpy, &ev)) {
185 if (ev.type == KeyPress) {
186 explicit_bzero(&buf, sizeof(buf));
187 num = XLookupString(&ev.xkey, buf, sizeof(buf), &ksym, 0);
188 if (IsKeypadKey(ksym)) {
189 if (ksym == XK_KP_Enter)
190 ksym = XK_Return;
191 else if (ksym >= XK_KP_0 && ksym <= XK_KP_9)
192 ksym = (ksym - XK_KP_0) + XK_0;
193 }
194 if (IsFunctionKey(ksym) ||
195 IsKeypadKey(ksym) ||
196 IsMiscFunctionKey(ksym) ||
197 IsPFKey(ksym) ||
198 IsPrivateKeypadKey(ksym))
199 continue;
200 switch (ksym) {
201 case XK_Return:
202 passwd[len] = '\0';
203 errno = 0;
204 if (!(inputhash = crypt(passwd, hash)))
205 fprintf(stderr, "slock: crypt: %s\n", strerror(errno));
206 else
207 running = !!strcmp(inputhash, hash);
208 if (running) {
209 XBell(dpy, 100);
210 failure = 1;
211 }
212 explicit_bzero(&passwd, sizeof(passwd));
213 len = 0;
214 break;
215 case XK_Escape:
216 explicit_bzero(&passwd, sizeof(passwd));
217 len = 0;
218 break;
219 case XK_BackSpace:
220 if (len)
221 passwd[--len] = '\0';
222 break;
223 default:
224 if (num && !iscntrl((int)buf[0]) &&
225 (len + num < sizeof(passwd))) {
226 memcpy(passwd + len, buf, num);
227 len += num;
228 }
229 break;
230 }
231 color = len ? INPUT : ((failure || failonclear) ? FAILED : INIT);
232 if (running && oldc != color) {
233 for (screen = 0; screen < nscreens; screen++)
234 drawlogo(dpy, locks[screen], color);
235 oldc = color;
236 }
237 } else if (rr->active && ev.type == rr->evbase + RRScreenChangeNotify) {
238 rre = (XRRScreenChangeNotifyEvent*)&ev;
239 for (screen = 0; screen < nscreens; screen++) {
240 if (locks[screen]->win == rre->window) {
241 if (rre->rotation == RR_Rotate_90 ||
242 rre->rotation == RR_Rotate_270)
243 XResizeWindow(dpy, locks[screen]->win,
244 rre->height, rre->width);
245 else
246 XResizeWindow(dpy, locks[screen]->win,
247 rre->width, rre->height);
248 XClearWindow(dpy, locks[screen]->win);
249 break;
250 }
251 }
252 } else {
253 for (screen = 0; screen < nscreens; screen++)
254 XRaiseWindow(dpy, locks[screen]->win);
255 }
256 }
257}
258
259static struct lock *
260lockscreen(Display *dpy, struct xrandr *rr, int screen, Imlib_Image *image)
261{
262 char curs[] = {0, 0, 0, 0, 0, 0, 0, 0};
263 int i, ptgrab, kbgrab;
264 struct lock *lock;
265 XColor color, dummy;
266 XSetWindowAttributes wa;
267 XWindowAttributes attrs;
268 Cursor invisible;
269#ifdef XINERAMA
270 XineramaScreenInfo *info;
271 int n;
272#endif
273
274 if (dpy == NULL || screen < 0 || !(lock = malloc(sizeof(struct lock))))
275 return NULL;
276
277 lock->screen = screen;
278 lock->root = RootWindow(dpy, lock->screen);
279 XGetWindowAttributes(dpy, lock->root, &attrs);
280
281 if (image) {
282 lock->bgmap = XCreatePixmap(dpy, lock->root, attrs.width, attrs.height, attrs.depth);
283 imlib_context_set_image(image);
284 imlib_context_set_display(dpy);
285 imlib_context_set_visual(attrs.visual);
286 imlib_context_set_colormap(attrs.colormap);
287 imlib_context_set_drawable(lock->bgmap);
288 imlib_render_image_on_drawable(0, 0);
289 imlib_free_image_and_decache();
290 } else {
291 lock->bgmap = 0;
292 }
293
294 for (i = 0; i < NUMCOLS; i++) {
295 XAllocNamedColor(dpy, attrs.colormap, colorname[i], &color, &dummy);
296 lock->colors[i] = color.pixel;
297 }
298
299 lock->x = attrs.width;
300 lock->y = attrs.height;
301#ifdef XINERAMA
302 if ((info = XineramaQueryScreens(dpy, &n))) {
303 lock->xoff = info[0].x_org;
304 lock->yoff = info[0].y_org;
305 lock->mw = info[0].width;
306 lock->mh = info[0].height;
307 } else
308#endif
309 {
310 lock->xoff = lock->yoff = 0;
311 lock->mw = lock->x;
312 lock->mh = lock->y;
313 }
314 lock->drawable = XCreatePixmap(dpy, lock->root,
315 lock->x, lock->y, DefaultDepth(dpy, screen));
316 lock->gc = XCreateGC(dpy, lock->root, 0, NULL);
317 XSetLineAttributes(dpy, lock->gc, 1, LineSolid, CapButt, JoinMiter);
318
319 /* init */
320 wa.override_redirect = 1;
321 wa.background_pixel = lock->colors[BACKGROUND];
322 lock->win = XCreateWindow(dpy, lock->root, 0, 0,
323 lock->x, lock->y,
324 0, DefaultDepth(dpy, lock->screen),
325 CopyFromParent,
326 DefaultVisual(dpy, lock->screen),
327 CWOverrideRedirect | CWBackPixel, &wa);
328 if (lock->bgmap)
329 XSetWindowBackgroundPixmap(dpy, lock->win, lock->bgmap);
330 lock->pmap = XCreateBitmapFromData(dpy, lock->win, curs, 8, 8);
331 invisible = XCreatePixmapCursor(dpy, lock->pmap, lock->pmap,
332 &color, &color, 0, 0);
333 XDefineCursor(dpy, lock->win, invisible);
334
335 resizerectangles(lock);
336
337 /* Try to grab mouse pointer *and* keyboard for 600ms, else fail the lock */
338 for (i = 0, ptgrab = kbgrab = -1; i < 6; i++) {
339 if (ptgrab != GrabSuccess) {
340 ptgrab = XGrabPointer(dpy, lock->root, False,
341 ButtonPressMask | ButtonReleaseMask |
342 PointerMotionMask, GrabModeAsync,
343 GrabModeAsync, None, invisible, CurrentTime);
344 }
345 if (kbgrab != GrabSuccess) {
346 kbgrab = XGrabKeyboard(dpy, lock->root, True,
347 GrabModeAsync, GrabModeAsync, CurrentTime);
348 }
349
350 /* input is grabbed: we can lock the screen */
351 if (ptgrab == GrabSuccess && kbgrab == GrabSuccess) {
352 XMapRaised(dpy, lock->win);
353 if (rr->active)
354 XRRSelectInput(dpy, lock->win, RRScreenChangeNotifyMask);
355
356 XSelectInput(dpy, lock->root, SubstructureNotifyMask);
357 drawlogo(dpy, lock, INIT);
358 return lock;
359 }
360
361 /* retry on AlreadyGrabbed but fail on other errors */
362 if ((ptgrab != AlreadyGrabbed && ptgrab != GrabSuccess) ||
363 (kbgrab != AlreadyGrabbed && kbgrab != GrabSuccess))
364 break;
365
366 usleep(100000);
367 }
368
369 /* we couldn't grab all input: fail out */
370 if (ptgrab != GrabSuccess)
371 fprintf(stderr, "slock: unable to grab mouse pointer for screen %d\n",
372 screen);
373 if (kbgrab != GrabSuccess)
374 fprintf(stderr, "slock: unable to grab keyboard for screen %d\n",
375 screen);
376 return NULL;
377}
378
379static void
380usage(void)
381{
382 die("usage: slock [-v] [-i image] [cmd [arg ...]]\n");
383}
384
385int
386main(int argc, char **argv) {
387 struct xrandr rr;
388 struct lock **locks;
389 struct passwd *pwd;
390 struct group *grp;
391 uid_t duid;
392 gid_t dgid;
393 const char *hash;
394 Display *dpy;
395 int s, nlocks, nscreens;
396 Imlib_Image image = NULL;
397
398 ARGBEGIN {
399 case 'v':
400 fprintf(stderr, "slock-"VERSION"\n");
401 return 0;
402 case 'i':
403 imlib_flush_loaders();
404 image = imlib_load_image(EARGF(usage()));
405 if (!image)
406 die("slock: unable to load image: %s.\n", EARGF(usage()));
407 break;
408 default:
409 usage();
410 } ARGEND
411
412 /* validate drop-user and -group */
413 errno = 0;
414 if (!(pwd = getpwnam(user)))
415 die("slock: getpwnam %s: %s\n", user,
416 errno ? strerror(errno) : "user entry not found");
417 duid = pwd->pw_uid;
418 errno = 0;
419 if (!(grp = getgrnam(group)))
420 die("slock: getgrnam %s: %s\n", group,
421 errno ? strerror(errno) : "group entry not found");
422 dgid = grp->gr_gid;
423
424#ifdef __linux__
425 dontkillme();
426#endif
427
428 hash = gethash();
429 errno = 0;
430 if (!crypt("", hash))
431 die("slock: crypt: %s\n", strerror(errno));
432
433 if (!(dpy = XOpenDisplay(NULL)))
434 die("slock: cannot open display\n");
435
436 /* drop privileges */
437 if (setgroups(0, NULL) < 0)
438 die("slock: setgroups: %s\n", strerror(errno));
439 if (setgid(dgid) < 0)
440 die("slock: setgid: %s\n", strerror(errno));
441 if (setuid(duid) < 0)
442 die("slock: setuid: %s\n", strerror(errno));
443
444 /* check for Xrandr support */
445 rr.active = XRRQueryExtension(dpy, &rr.evbase, &rr.errbase);
446
447 /* get number of screens in display "dpy" and blank them */
448 nscreens = ScreenCount(dpy);
449 if (!(locks = calloc(nscreens, sizeof(struct lock *))))
450 die("slock: out of memory\n");
451 for (nlocks = 0, s = 0; s < nscreens; s++) {
452 if ((locks[s] = lockscreen(dpy, &rr, s, image)) != NULL)
453 nlocks++;
454 else
455 break;
456 }
457 XSync(dpy, 0);
458
459 /* did we manage to lock everything? */
460 if (nlocks != nscreens)
461 return 1;
462
463 /* run post-lock command */
464 if (argc > 0) {
465 switch (fork()) {
466 case -1:
467 die("slock: fork failed: %s\n", strerror(errno));
468 case 0:
469 if (close(ConnectionNumber(dpy)) < 0)
470 die("slock: close: %s\n", strerror(errno));
471 execvp(argv[0], argv);
472 fprintf(stderr, "slock: execvp %s: %s\n", argv[0], strerror(errno));
473 _exit(1);
474 }
475 }
476
477 /* everything is now blank. Wait for the correct password */
478 readpw(dpy, &rr, locks, nscreens, hash);
479
480 if (image) {
481 imlib_context_set_image(image);
482 }
483
484 for (nlocks = 0, s = 0; s < nscreens; s++) {
485 XFreePixmap(dpy, locks[s]->drawable);
486 XFreeGC(dpy, locks[s]->gc);
487 }
488
489 XSync(dpy, 0);
490 XCloseDisplay(dpy);
491 return 0;
492}