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