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