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