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