slock.c 12 KB

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