0 /* See LICENSE file for license details. */
1 #define _XOPEN_SOURCE 500
2 #if HAVE_SHADOW_H
3 #include <shadow.h>
4 #endif
5
6 #include <ctype.h>
7 #include <errno.h>
8 #include <grp.h>
9 #include <pwd.h>
10 #include <stdarg.h>
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <sys/types.h>
16 #include <sys/wait.h>
17 #include <X11/extensions/Xrandr.h>
18 #include <X11/extensions/scrnsaver.h>
19 #include <X11/extensions/dpms.h>
20 #include <X11/keysym.h>
21 #include <X11/Xlib.h>
22 #include <X11/Xutil.h>
23 #include <security/pam_appl.h>
24 #include <pthread.h>
25 #include <pwd.h>
26 #include <signal.h>
27 #include "slock.h"
28 #include "global.h"
29
30 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
31 Display *dpy;
32
33 #ifndef EXTERN_LOCK
34 static int pam_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr);
35 struct pam_conv pamc = {pam_conv, NULL};
36 char passwd[256];
37 char username[256];
38 GC lockgc;
39
40 enum {
41 INIT,
42 INPUT,
43 FAILED,
44 PAM,
45 TEXT,
46 NUMCOLS
47 };
48
49 static const int colors[NUMCOLS] = {
50 [INIT] = 0x111111, /* after initialization */
51 [INPUT] = 0x333333, /* during input */
52 [FAILED] = 0x771111, /* wrong password */
53 [PAM] = 0x222222, /* waiting for PAM */
54 [TEXT] = 0xDDDDDD,
55 };
56
57 /* treat a cleared input like a wrong password (color) */
58 static const int failonclear = 0;
59
60 /* PAM service that's used for authentication */
61 static const char* pam_service = "slock";
62
63 struct lock {
64 int screen;
65 Window root, win;
66 Pixmap pmap;
67 };
68
69 struct xrandr {
70 int active;
71 int evbase;
72 int errbase;
73 };
74
75 static void
76 die(const char *errstr, ...)
77 {
78 va_list ap;
79
80 va_start(ap, errstr);
81 vfprintf(stderr, errstr, ap);
82 va_end(ap);
83 exit(1);
84 }
85
86 static int
87 pam_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
88 {
89 int retval = PAM_CONV_ERR;
90 for(int i=0; i<num_msg; i++) {
91 if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF &&
92 strncmp(msg[i]->msg, "Password: ", 10) == 0) {
93 struct pam_response *resp_msg = malloc(sizeof(struct pam_response));
94 if (!resp_msg)
95 die("malloc failed\n");
96 char *password = malloc(strlen(passwd) + 1);
97 if (!password)
98 die("malloc failed\n");
99 memset(password, 0, strlen(passwd) + 1);
100 strcpy(password, passwd);
101 resp_msg->resp_retcode = 0;
102 resp_msg->resp = password;
103 resp[i] = resp_msg;
104 retval = PAM_SUCCESS;
105 }
106 }
107 return retval;
108 }
109
110 #ifdef __linux__
111 #include <fcntl.h>
112 #include <linux/oom.h>
113
114 static void
115 dontkillme(void)
116 {
117 FILE *f;
118 const char oomfile[] = "/proc/self/oom_score_adj";
119
120 if (!(f = fopen(oomfile, "w"))) {
121 if (errno == ENOENT)
122 return;
123 die("slock: fopen %s: %s\n", oomfile, strerror(errno));
124 }
125 fprintf(f, "%d", OOM_SCORE_ADJ_MIN);
126 if (fclose(f)) {
127 if (errno == EACCES)
128 printf("slock: unable to disable OOM killer. "
129 "Make sure to suid or sgid slock.\n");
130 else
131 die("slock: fclose %s: %s\n", oomfile, strerror(errno));
132 }
133 }
134 #endif
135
136 static const char *
137 gethash(void)
138 {
139 const char *hash;
140 struct passwd *pw;
141
142 /* Check if the current user has a password entry */
143 errno = 0;
144 if (!(pw = getpwuid(getuid()))) {
145 if (errno)
146 die("slock: getpwuid: %s\n", strerror(errno));
147 else
148 die("slock: cannot retrieve password entry\n");
149 }
150 hash = pw->pw_passwd;
151
152 #if HAVE_SHADOW_H
153 if (!strcmp(hash, "x")) {
154 struct spwd *sp;
155 if (!(sp = getspnam(pw->pw_name)))
156 die("slock: getspnam: cannot retrieve shadow entry. "
157 "Make sure to suid or sgid slock.\n");
158 hash = sp->sp_pwdp;
159 }
160 #else
161 if (!strcmp(hash, "*")) {
162 #ifdef __OpenBSD__
163 if (!(pw = getpwuid_shadow(getuid())))
164 die("slock: getpwnam_shadow: cannot retrieve shadow entry. "
165 "Make sure to suid or sgid slock.\n");
166 hash = pw->pw_passwd;
167 #else
168 die("slock: getpwuid: cannot retrieve shadow entry. "
169 "Make sure to suid or sgid slock.\n");
170 #endif /* __OpenBSD__ */
171 }
172 #endif /* HAVE_SHADOW_H */
173
174 /* pam, store user name */
175 hash = pw->pw_name;
176 return hash;
177 }
178
179 static void
180 readpw(struct xrandr *rr, struct lock **locks, int nscreens,
181 const char *hash)
182 {
183 XRRScreenChangeNotifyEvent *rre;
184 char buf[32];
185 int num, screen, running, failure, retval; //oldc
186 unsigned int len, color;
187 KeySym ksym;
188 XEvent ev;
189 pam_handle_t *pamh;
190
191 len = 0;
192 running = 1;
193 failure = 0;
194 //oldc = INIT;
195
196 while (running && !XNextEvent(dpy, &ev)) {
197 if (ev.type == KeyPress) {
198 memset(&buf, 0, sizeof(buf));
199 num = XLookupString(&ev.xkey, buf, sizeof(buf), &ksym, 0);
200 if (IsKeypadKey(ksym)) {
201 if (ksym == XK_KP_Enter)
202 ksym = XK_Return;
203 else if (ksym >= XK_KP_0 && ksym <= XK_KP_9)
204 ksym = (ksym - XK_KP_0) + XK_0;
205 }
206 if (IsFunctionKey(ksym) ||
207 IsKeypadKey(ksym) ||
208 IsMiscFunctionKey(ksym) ||
209 IsPFKey(ksym) ||
210 IsPrivateKeypadKey(ksym))
211 continue;
212 switch (ksym) {
213 case XK_Return:
214 passwd[len] = '\0';
215 errno = 0;
216 retval = pam_start(pam_service, hash, &pamc, &pamh);
217 color = PAM;
218 for (screen = 0; screen < nscreens; screen++) {
219 XSetWindowBackground(dpy, locks[screen]->win, colors[color]);
220 XClearWindow(dpy, locks[screen]->win);
221 XRaiseWindow(dpy, locks[screen]->win);
222 }
223 XSync(dpy, False);
224
225 if (retval == PAM_SUCCESS)
226 retval = pam_authenticate(pamh, 0);
227 if (retval == PAM_SUCCESS)
228 retval = pam_acct_mgmt(pamh, 0);
229
230 running = 1;
231 if (retval == PAM_SUCCESS) {
232 running = 0;
233 XCloseDisplay(dpy);
234 if (!(dpy = XOpenDisplay(0))) {
235 printf("Cannot open display.\n");
236 }
237 } else
238 fprintf(stderr, "slock: %s\n", pam_strerror(pamh, retval));
239 pam_end(pamh, retval);
240 if (running) {
241 XBell(dpy, 100);
242 failure = 1;
243 }
244 memset(&passwd, 0, sizeof(passwd));
245 len = 0;
246 break;
247 case XK_Escape:
248 memset(&passwd, 0, sizeof(passwd));
249 len = 0;
250 break;
251 case XK_BackSpace:
252 if (len)
253 passwd[--len] = '\0';
254 failure = 0;
255 break;
256 default:
257 if (num && !iscntrl((int)buf[0]) &&
258 (len + num < sizeof(passwd))) {
259 memcpy(passwd + len, buf, num);
260 len += num;
261 }
262 break;
263 }
264 color = len ? INPUT : ((failure || failonclear) ? FAILED : INIT);
265 if (running) {
266 for (screen = 0; screen < nscreens; screen++) {
267 XSetWindowBackground(dpy,
268 locks[screen]->win,
269 colors[color]);
270 XClearWindow(dpy, locks[screen]->win);
271 char hidden[256];
272 memset(hidden, '*', len);
273 Screen* scr = XScreenOfDisplay(dpy, screen);
274 XSetForeground(dpy, lockgc, colors[TEXT]);
275
276 XSetFont(dpy, lockgc, font->fid);
277 XDrawString(dpy, locks[screen]->win, lockgc, XWidthOfScreen(scr)/2-XTextWidth(font, hidden, len)/2, XHeightOfScreen(scr)/2, hidden, len);
278 XDrawString(dpy, locks[screen]->win, lockgc, XWidthOfScreen(scr)/2-XTextWidth(font, username, strlen(username))/2, XHeightOfScreen(scr)/2-32, username, strlen(username));
279 }
280 }
281 } else if (rr->active && ev.type == rr->evbase + RRScreenChangeNotify) {
282 rre = (XRRScreenChangeNotifyEvent*)&ev;
283 for (screen = 0; screen < nscreens; screen++) {
284 if (locks[screen]->win == rre->window) {
285 if (rre->rotation == RR_Rotate_90 ||
286 rre->rotation == RR_Rotate_270)
287 XResizeWindow(dpy, locks[screen]->win,
288 rre->height, rre->width);
289 else
290 XResizeWindow(dpy, locks[screen]->win,
291 rre->width, rre->height);
292 XClearWindow(dpy, locks[screen]->win);
293 break;
294 }
295 }
296 } else {
297 for (screen = 0; screen < nscreens; screen++)
298 XRaiseWindow(dpy, locks[screen]->win);
299 }
300 }
301 }
302
303 static struct lock* lockscreen(struct xrandr *rr, int screen)
304 {
305 char curs[] = {0, 0, 0, 0, 0, 0, 0, 0};
306 int i, ptgrab, kbgrab;
307 struct lock *lock;
308 XColor color, dummy;
309 XSetWindowAttributes wa;
310 Cursor invisible;
311
312 if (dpy == NULL || screen < 0 || !(lock = malloc(sizeof(struct lock))))
313 return NULL;
314
315 lock->screen = screen;
316 lock->root = RootWindow(dpy, lock->screen);
317
318 /* init */
319 wa.override_redirect = 1;
320 wa.background_pixel = colors[INIT];
321 lock->win = XCreateWindow(dpy, lock->root, 0, 0,
322 DisplayWidth(dpy, lock->screen),
323 DisplayHeight(dpy, lock->screen),
324 0, DefaultDepth(dpy, lock->screen),
325 CopyFromParent,
326 DefaultVisual(dpy, lock->screen),
327 CWOverrideRedirect | CWBackPixel, &wa);
328 lock->pmap = XCreateBitmapFromData(dpy, lock->win, curs, 8, 8);
329 invisible = XCreatePixmapCursor(dpy, lock->pmap, lock->pmap,
330 &color, &color, 0, 0);
331 XDefineCursor(dpy, lock->win, invisible);
332 lockgc = XCreateGC(dpy, lock->win, 0, NULL);
333
334 /* Try to grab mouse pointer *and* keyboard for 600ms, else fail the lock */
335 for (i = 0, ptgrab = kbgrab = -1; i < 6; i++) {
336 if (ptgrab != GrabSuccess) {
337 ptgrab = XGrabPointer(dpy, lock->root, False,
338 ButtonPressMask | ButtonReleaseMask |
339 PointerMotionMask, GrabModeAsync,
340 GrabModeAsync, None, invisible, CurrentTime);
341 }
342 if (kbgrab != GrabSuccess) {
343 kbgrab = XGrabKeyboard(dpy, lock->root, True,
344 GrabModeAsync, GrabModeAsync, CurrentTime);
345 }
346
347 /* input is grabbed: we can lock the screen */
348 if (ptgrab == GrabSuccess && kbgrab == GrabSuccess) {
349 XMapRaised(dpy, lock->win);
350 if (rr->active)
351 XRRSelectInput(dpy, lock->win, RRScreenChangeNotifyMask);
352
353 XSelectInput(dpy, lock->root, SubstructureNotifyMask);
354 return lock;
355 }
356
357 /* retry on AlreadyGrabbed but fail on other errors */
358 if ((ptgrab != AlreadyGrabbed && ptgrab != GrabSuccess) ||
359 (kbgrab != AlreadyGrabbed && kbgrab != GrabSuccess))
360 break;
361
362 usleep(100000);
363 }
364
365 /* we couldn't grab all input: fail out */
366 if (ptgrab != GrabSuccess)
367 fprintf(stderr, "slock: unable to grab mouse pointer for screen %d\n",
368 screen);
369 if (kbgrab != GrabSuccess)
370 fprintf(stderr, "slock: unable to grab keyboard for screen %d\n",
371 screen);
372 return NULL;
373 }
374 #endif
375
376 int slock() {
377 #ifdef EXTERN_LOCK
378 int pid = spawn(LOCK);
379 int status;
380 waitpid(pid, &status, 0);
381 return 0;
382 #else
383 struct xrandr rr;
384 struct lock **locks;
385 struct passwd *pwd;
386 const char *hash;
387 int s, nlocks, nscreens;
388
389 #ifdef __linux__
390 dontkillme();
391 #endif
392
393 hash = gethash();
394 errno = 0;
395
396 rr.active = XRRQueryExtension(dpy, &rr.evbase, &rr.errbase);
397
398 /* get number of screens in display "dpy" and blank them */
399 nscreens = ScreenCount(dpy);
400 if (!(locks = calloc(nscreens, sizeof(struct lock *))))
401 printf("slock: out of memory\n");
402 for (nlocks = 0, s = 0; s < nscreens; s++) {
403 if ((locks[s] = lockscreen(&rr, s)) != NULL)
404 nlocks++;
405 else
406 break;
407 }
408 XSync(dpy, 0);
409
410 /* did we manage to lock everything? */
411 if (nlocks != nscreens)
412 return 1;
413
414 /* everything is now blank. Wait for the correct password */
415 readpw(&rr, locks, nscreens, hash);
416
417 return 0;
418 #endif
419 }
420
421 int screensaver() {
422 #ifndef EXTERN_LOCK
423 username[0] = '\0';
424 struct passwd *pw;
425 uid_t uid;
426 int c;
427 uid = geteuid();
428 pw = getpwuid(uid);
429 if (pw)
430 strncpy(username, pw->pw_name, 256);
431 #endif
432 if (!(dpy = XOpenDisplay(0))) {
433 printf("Cannot open display.\n");
434 return -1;
435 }
436
437 DPMSEnable(dpy);
438
439 XScreenSaverInfo *info = XScreenSaverAllocInfo();
440 while (1) {
441 pthread_mutex_lock(&mutex);
442 XScreenSaverQueryInfo(dpy, DefaultRootWindow(dpy), info);
443 if (info->idle>=(LOCK_TIME-15)*1000)
444 slock();
445 pthread_mutex_unlock(&mutex);
446 sleep(1);
447 }
448 XFree(info);
449 return 0;
450 }
451