1 /*
2  * xtrlock.c
3  *
4  * X Transparent Lock
5  *
6  * Copyright (C)1993,1994 Ian Jackson
7  *
8  * This is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2, or (at your option)
11  * any later version.
12  *
13  * This is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  */
18 
19 #include <X11/X.h>
20 #include <X11/Xlib.h>
21 #include <X11/Xutil.h>
22 #include <X11/keysym.h>
23 #include <X11/Xos.h>
24 
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <errno.h>
30 #include <pwd.h>
31 #include <grp.h>
32 #include <limits.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <unistd.h>
36 #include <math.h>
37 #include <ctype.h>
38 #include <limits.h>
39 
40 #ifdef SHADOW_PWD
41 #include <shadow.h>
42 #endif
43 
44 #ifdef MULTITOUCH
45 #include <X11/extensions/XInput.h>
46 #include <X11/extensions/XInput2.h>
47 #endif
48 
49 #include "lock.bitmap"
50 #include "mask.bitmap"
51 #include "patchlevel.h"
52 
53 Display *display;
54 Window window, root;
55 
56 #define TIMEOUTPERATTEMPT 30000
57 #define MAXGOODWILL  (TIMEOUTPERATTEMPT*5)
58 #define INITIALGOODWILL MAXGOODWILL
59 #define GOODWILLPORTION 0.3
60 
61 struct passwd *pw;
passwordok(const char * s)62 int passwordok(const char *s) {
63 #if 0
64   char key[3];
65   char *encr;
66 
67   key[0] = *(pw->pw_passwd);
68   key[1] =  (pw->pw_passwd)[1];
69   key[2] =  0;
70   encr = crypt(s, key);
71   return !strcmp(encr, pw->pw_passwd);
72 #else
73   /* simpler, and should work with crypt() algorithms using longer
74      salt strings (like the md5-based one on freebsd).  --marekm */
75   return !strcmp(crypt(s, pw->pw_passwd), pw->pw_passwd);
76 #endif
77 }
78 
79 #if MULTITOUCH
80 XIEventMask evmask;
81 
82 /* (Optimistically) attempt to grab multitouch devices which are not
83  * intercepted via XGrabPointer. */
handle_multitouch(Cursor cursor)84 void handle_multitouch(Cursor cursor) {
85   XIDeviceInfo *info;
86   int xi_ndevices;
87 
88   info = XIQueryDevice(display, XIAllDevices, &xi_ndevices);
89 
90   for (int i=0; i < xi_ndevices; i++) {
91     XIDeviceInfo *dev = &info[i];
92 
93     for (int j=0; j < dev->num_classes; j++) {
94       if (dev->classes[j]->type == XITouchClass &&
95           dev->use == XISlavePointer) {
96         XIGrabDevice(display, dev->deviceid, window, CurrentTime, cursor,
97                      GrabModeAsync, GrabModeAsync, False, &evmask);
98       }
99     }
100   }
101   XIFreeDeviceInfo(info);
102 }
103 #endif
104 
main(int argc,char ** argv)105 int main(int argc, char **argv){
106   XEvent ev;
107   KeySym ks;
108   char cbuf[10], rbuf[128]; /* shadow appears to suggest 127 a good value here */
109   int clen, rlen=0;
110   long goodwill= INITIALGOODWILL, timeout= 0;
111   XSetWindowAttributes attrib;
112   Cursor cursor;
113   Pixmap csr_source,csr_mask;
114   XColor csr_fg, csr_bg, dummy, black;
115   int ret, screen, blank = 0, fork_after = 0;
116 #ifdef SHADOW_PWD
117   struct spwd *sp;
118 #endif
119   struct timeval tv;
120   int tvt, gs;
121 
122   if (getenv("WAYLAND_DISPLAY"))
123       fprintf(stderr,"WARNING: Wayland X server detected: xtrlock"
124          " cannot intercept all user input. See xtrlock(1).\n");
125 
126   while (argc > 1) {
127     if ((strcmp(argv[1], "-b") == 0)) {
128       blank = 1;
129       argc--;
130       argv++;
131     } else if ((strcmp(argv[1], "-f") == 0)) {
132       fork_after = 1;
133       argc--;
134       argv++;
135     } else {
136       fprintf(stderr,"xtrlock (version %s); usage: xtrlock [-b] [-f]\n",
137               program_version);
138       exit(1);
139     }
140   }
141 
142   errno=0;  pw= getpwuid(getuid());
143   if (!pw) { perror("password entry for uid not found"); exit(1); }
144 #ifdef SHADOW_PWD
145   sp = getspnam(pw->pw_name);
146   if (sp)
147     pw->pw_passwd = sp->sp_pwdp;
148   endspent();
149 #endif
150 
151   /* logically, if we need to do the following then the same
152      applies to being installed setgid shadow.
153      we do this first, because of a bug in linux. --jdamery */
154   if (setgid(getgid())) { perror("setgid"); exit(1); }
155   /* we can be installed setuid root to support shadow passwords,
156      and we don't need root privileges any longer.  --marekm */
157   if (setuid(getuid())) { perror("setuid"); exit(1); }
158 
159   if (strlen(pw->pw_passwd) < 13) {
160     fputs("password entry has no pwd\n",stderr); exit(1);
161   }
162 
163   display= XOpenDisplay(0);
164 
165   if (display==NULL) {
166     fprintf(stderr,"xtrlock (version %s): cannot open display\n",
167 	    program_version);
168     exit(1);
169   }
170 
171 #ifdef MULTITOUCH
172   unsigned char mask[XIMaskLen(XI_LASTEVENT)];
173   int xi_major = 2, xi_minor = 2, xi_opcode, xi_error, xi_event;
174 
175   if (!XQueryExtension(display, INAME, &xi_opcode, &xi_event, &xi_error)) {
176     fprintf(stderr, "xtrlock (version %s): No X Input extension\n",
177             program_version);
178     exit(1);
179   }
180 
181   if (XIQueryVersion(display, &xi_major, &xi_minor) != Success ||
182       xi_major * 10 + xi_minor < 22) {
183     fprintf(stderr,"xtrlock (version %s): Need XI 2.2\n",
184             program_version);
185     exit(1);
186   }
187 
188   evmask.mask = mask;
189   evmask.mask_len = sizeof(mask);
190   memset(mask, 0, sizeof(mask));
191   evmask.deviceid = XIAllDevices;
192   XISetMask(mask, XI_HierarchyChanged);
193   XISelectEvents(display, DefaultRootWindow(display), &evmask, 1);
194 #endif
195 
196   attrib.override_redirect= True;
197 
198   if (blank) {
199     screen = DefaultScreen(display);
200     attrib.background_pixel = BlackPixel(display, screen);
201     window= XCreateWindow(display,DefaultRootWindow(display),
202                           0,0,DisplayWidth(display, screen),DisplayHeight(display, screen),
203                           0,DefaultDepth(display, screen), CopyFromParent, DefaultVisual(display, screen),
204                           CWOverrideRedirect|CWBackPixel,&attrib);
205     XAllocNamedColor(display, DefaultColormap(display, screen), "black", &black, &dummy);
206   } else {
207     window= XCreateWindow(display,DefaultRootWindow(display),
208                           0,0,1,1,0,CopyFromParent,InputOnly,CopyFromParent,
209                           CWOverrideRedirect,&attrib);
210   }
211 
212   XSelectInput(display,window,KeyPressMask|KeyReleaseMask);
213 
214   csr_source= XCreateBitmapFromData(display,window,lock_bits,lock_width,lock_height);
215   csr_mask= XCreateBitmapFromData(display,window,mask_bits,mask_width,mask_height);
216 
217   ret = XAllocNamedColor(display,
218                         DefaultColormap(display, DefaultScreen(display)),
219                         "steelblue3",
220                         &dummy, &csr_bg);
221   if (ret==0)
222     XAllocNamedColor(display,
223                     DefaultColormap(display, DefaultScreen(display)),
224                     "black",
225                     &dummy, &csr_bg);
226 
227   ret = XAllocNamedColor(display,
228                         DefaultColormap(display,DefaultScreen(display)),
229                         "grey25",
230                         &dummy, &csr_fg);
231   if (ret==0)
232     XAllocNamedColor(display,
233                     DefaultColormap(display, DefaultScreen(display)),
234                     "white",
235                     &dummy, &csr_bg);
236 
237 
238 
239   cursor= XCreatePixmapCursor(display,csr_source,csr_mask,&csr_fg,&csr_bg,
240                               lock_x_hot,lock_y_hot);
241 
242   XMapWindow(display,window);
243 
244   /*Sometimes the WM doesn't ungrab the keyboard quickly enough if
245    *launching xtrlock from a keystroke shortcut, meaning xtrlock fails
246    *to start We deal with this by waiting (up to 100 times) for 10,000
247    *microsecs and trying to grab each time. If we still fail
248    *(i.e. after 1s in total), then give up, and emit an error
249    */
250 
251   gs=0; /*gs==grab successful*/
252   for (tvt=0 ; tvt<100; tvt++) {
253     ret = XGrabKeyboard(display,window,False,GrabModeAsync,GrabModeAsync,
254 			CurrentTime);
255     if (ret == GrabSuccess) {
256       gs=1;
257       break;
258     }
259     /*grab failed; wait .01s*/
260     tv.tv_sec=0;
261     tv.tv_usec=10000;
262     select(1,NULL,NULL,NULL,&tv);
263   }
264   if (gs==0){
265     fprintf(stderr,"xtrlock (version %s): cannot grab keyboard\n",
266 	    program_version);
267     exit(1);
268   }
269 
270   if (XGrabPointer(display,window,False,(KeyPressMask|KeyReleaseMask)&0,
271                GrabModeAsync,GrabModeAsync,None,
272                cursor,CurrentTime)!=GrabSuccess) {
273     XUngrabKeyboard(display,CurrentTime);
274     fprintf(stderr,"xtrlock (version %s): cannot grab pointer\n",
275 	    program_version);
276     exit(1);
277   }
278 
279 #ifdef MULTITOUCH
280   handle_multitouch(cursor);
281 #endif
282 
283   if (fork_after) {
284     pid_t pid = fork();
285     if (pid < 0) {
286       fprintf(stderr,"xtrlock (version %s): cannot fork: %s\n",
287               program_version, strerror(errno));
288       exit(1);
289     } else if (pid > 0) {
290       exit(0);
291     }
292   }
293 
294   for (;;) {
295     XNextEvent(display,&ev);
296     switch (ev.type) {
297     case KeyPress:
298       if (ev.xkey.time < timeout) { XBell(display,0); break; }
299       clen= XLookupString(&ev.xkey,cbuf,9,&ks,0);
300       switch (ks) {
301       case XK_Escape: case XK_Clear:
302         rlen=0; break;
303       case XK_Delete: case XK_BackSpace:
304         if (rlen>0) rlen--;
305         break;
306       case XK_Linefeed: case XK_Return: case XK_KP_Enter:
307         if (rlen==0) break;
308         rbuf[rlen]=0;
309         if (passwordok(rbuf)) goto loop_x;
310         XBell(display,0);
311         rlen= 0;
312         if (timeout) {
313           goodwill+= ev.xkey.time - timeout;
314           if (goodwill > MAXGOODWILL) {
315             goodwill= MAXGOODWILL;
316           }
317         }
318         timeout= -goodwill*GOODWILLPORTION;
319         goodwill+= timeout;
320         timeout+= ev.xkey.time + TIMEOUTPERATTEMPT;
321         break;
322       default:
323         if (clen != 1) break;
324         /* allow space for the trailing \0 */
325 	if (rlen < (sizeof(rbuf) - 1)){
326 	  rbuf[rlen]=cbuf[0];
327 	  rlen++;
328 	}
329         break;
330       }
331       break;
332 #if MULTITOUCH
333     case GenericEvent:
334       if (ev.xcookie.extension == xi_opcode &&
335           XGetEventData(display,&ev.xcookie) &&
336           ev.xcookie.evtype == XI_HierarchyChanged) {
337         handle_multitouch(cursor);
338       }
339       break;
340 #endif
341     default:
342       break;
343     }
344   }
345  loop_x:
346   exit(0);
347 }
348