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