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