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