slock

[fork] screen locker
git clone https://hhvn.uk/slock
git clone git://hhvn.uk/slock
Log | Files | Refs | README | LICENSE

slock.c (13029B)


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