ftp://ftp.uni-heidelberg.de/pub/X11/contrib/applications/xscreensaver-1.25.tar.Z
[xscreensaver] / driver / lock.c
1 /*    xscreensaver, Copyright (c) 1993-1995 Jamie Zawinski <jwz@mcom.com>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 #if __STDC__
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <string.h>
16 #endif
17
18 #ifdef  HAVE_SHADOW
19 #include <shadow.h>
20 #endif
21
22 #include <pwd.h>
23 #include <stdio.h>
24
25 #include <X11/Intrinsic.h>
26
27 #if !__STDC__
28 # define _NO_PROTO
29 #endif
30
31 #include <Xm/Xm.h>
32 #include <Xm/List.h>
33 #include <Xm/TextF.h>
34
35 #include "xscreensaver.h"
36
37 #ifndef NO_LOCKING
38
39 Time passwd_timeout;
40
41 extern char *screensaver_version;
42 extern char *progname;
43 extern XtAppContext app;
44 extern Bool verbose_p;
45
46 extern Widget passwd_dialog;
47 extern Widget passwd_form;
48 extern Widget roger_label;
49 extern Widget passwd_label1;
50 extern Widget passwd_label3;
51 extern Widget passwd_text;
52 extern Widget passwd_done;
53 extern Widget passwd_cancel;
54
55 extern create_passwd_dialog P((Widget));
56 extern void ungrab_keyboard_and_mouse P((void));
57
58 static enum { pw_read, pw_ok, pw_fail, pw_cancel, pw_time } passwd_state;
59 static char typed_passwd [1024];
60
61 static char root_passwd [255];
62 static char user_passwd [255];
63
64 #ifdef HAVE_SHADOW
65 # define PWTYPE struct spwd *
66 # define PWSLOT sp_pwdp
67 # define GETPW  getspnam
68 #else
69 # define PWTYPE struct passwd *
70 # define PWSLOT pw_passwd
71 # define GETPW  getpwnam
72 #endif
73
74 Bool
75 lock_init ()
76 {
77   Bool ok = True;
78   char *u;
79   PWTYPE p = GETPW ("root");
80   if (p && p->PWSLOT && p->PWSLOT[0] != '*')
81     strcpy (root_passwd, p->PWSLOT);
82   else
83     {
84       fprintf (stderr, "%s: couldn't get root's password\n", progname);
85       strcpy (root_passwd, "*");
86     }
87
88   /* It has been reported that getlogin() returns the wrong user id on some
89      very old SGI systems... */
90   u = getlogin ();
91   if (u)
92     p = GETPW (u);
93   else
94     {
95       /* getlogin() fails if not attached to a terminal;
96          in that case, use getpwuid(). */
97       struct passwd *p2 = getpwuid (getuid ());
98       u = p2->pw_name;
99 #ifdef HAVE_SHADOW
100       p = GETPW (u);
101 #else
102       p = p2;
103 #endif
104     }
105
106   if (p && p->PWSLOT &&
107       /* p->PWSLOT[0] != '*' */         /* sensible */
108       (strlen (p->PWSLOT) > 4)          /* solaris */
109       )
110     strcpy (user_passwd, p->PWSLOT);
111   else
112     {
113       fprintf (stderr, "%s: couldn't get password of \"%s\"\n", progname, u);
114       strcpy (user_passwd, "*");
115       ok = False;
116     }
117   return ok;
118 }
119
120
121 \f
122 #if (XmVersion >= 1002)  /* The `destroy' bug apears to be fixed as   */
123 # define DESTROY_WORKS   /* of Motif 1.2.1, but the `verify-callback' */
124 #endif                   /* bug is still present. */
125
126 static void
127 passwd_cancel_cb (button, client_data, call_data)
128      Widget button;
129      XtPointer client_data, call_data;
130 {
131   passwd_state = pw_cancel;
132 }
133
134 static void
135 passwd_done_cb (button, client_data, call_data)
136      Widget button;
137      XtPointer client_data, call_data;
138 {
139   if (passwd_state != pw_read) return; /* already done */
140   if (!strcmp ((char *) crypt (typed_passwd, user_passwd), user_passwd))
141     passwd_state = pw_ok;
142   /* do not allow root to have empty passwd */
143   else if (typed_passwd [0] &&
144            !strcmp ((char *) crypt (typed_passwd, root_passwd), root_passwd))
145     passwd_state = pw_ok;
146   else
147     passwd_state = pw_fail;
148 }
149
150 #ifdef VERIFY_CALLBACK_WORKS
151
152   /* ####  It looks to me like adding any modifyVerify callback causes
153      ####  Motif 1.1.4 to free the the TextF_Value() twice.  I can't see
154      ####  the bug in the Motif source, but Purify complains, even if
155      ####  check_passwd_cb() is a no-op.
156
157      ####  Update: Motif 1.2.1 also loses, but in a different way: it
158      ####  writes beyond the end of a malloc'ed block in ModifyVerify().
159      ####  Probably this block is the text field's text.
160    */
161
162 static void 
163 check_passwd_cb (button, client_data, call_data)
164      Widget button;
165      XtPointer client_data, call_data;
166 {
167   XmTextVerifyCallbackStruct *vcb = (XmTextVerifyCallbackStruct *) call_data;
168
169   if (passwd_state != pw_read)
170     return;
171   else if (vcb->reason == XmCR_ACTIVATE)
172     {
173       passwd_done_cb (0, 0, 0);
174     }
175   else if (vcb->text->length > 1)       /* don't allow "paste" operations */
176     {
177       vcb->doit = False;
178     }
179   else if (vcb->text->ptr != 0)
180     {
181       int i;
182       strncat (typed_passwd, vcb->text->ptr, vcb->text->length);
183       typed_passwd [vcb->endPos + vcb->text->length] = 0;
184       for (i = 0; i < vcb->text->length; i++)
185         vcb->text->ptr [i] = '*';
186     }
187 }
188
189 #else /* !VERIFY_CALLBACK_WORKS */
190
191 static void keypress();
192 static void backspace();
193 static void kill_line();
194 static void done();
195
196 static XtActionsRec actions[] = {{"keypress",  keypress},
197                                  {"backspace", backspace},
198                                  {"kill_line", kill_line},
199                                  {"done",      done}
200                                 };
201
202 #if 0 /* oh fuck, why doesn't this work? */
203 static char translations[] = "\
204 <Key>BackSpace:         backspace()\n\
205 <Key>Delete:            backspace()\n\
206 Ctrl<Key>H:             backspace()\n\
207 Ctrl<Key>U:             kill_line()\n\
208 Ctrl<Key>X:             kill_line()\n\
209 Ctrl<Key>J:             done()\n\
210 Ctrl<Key>M:             done()\n\
211 <Key>:                  keypress()\n\
212 ";
213 #else
214 static char translations[] = "<Key>:keypress()";
215 #endif
216
217 static void
218 keypress (w, event, argv, argc)
219      Widget w;
220      XEvent *event;
221      String *argv;
222      Cardinal *argc;
223 {
224   int i, j;
225   char s [sizeof (typed_passwd)];
226   int size = XLookupString ((XKeyEvent *) event, s, sizeof (s), 0, 0);
227   if (size != 1) return;
228
229   /* hack because I can't get translations to dance to my tune... */
230   if (*s == '\010') { backspace (w, event, argv, argc); return; }
231   if (*s == '\177') { backspace (w, event, argv, argc); return; }
232   if (*s == '\025') { kill_line (w, event, argv, argc); return; }
233   if (*s == '\030') { kill_line (w, event, argv, argc); return; }
234   if (*s == '\012') { done (w, event, argv, argc); return; }
235   if (*s == '\015') { done (w, event, argv, argc); return; }
236
237   i = j = strlen (typed_passwd);
238   typed_passwd [i] = *s;
239   s [++i] = 0;
240   while (i--)
241     s [i] = '*';
242   XmTextFieldSetString (passwd_text, s);
243   XmTextFieldSetInsertionPosition (passwd_text, j + 1);
244 }
245
246 static void
247 backspace (w, event, argv, argc)
248      Widget w;
249      XEvent *event;
250      String *argv;
251      Cardinal *argc;
252 {
253   char s [sizeof (typed_passwd)];
254   int i = strlen (typed_passwd);
255   int j = i;
256   if (i == 0)
257     return;
258   typed_passwd [--i] = 0;
259   s [i] = 0;
260   while (i--)
261     s [i] = '*';
262   XmTextFieldSetString (passwd_text, s);
263   XmTextFieldSetInsertionPosition (passwd_text, j + 1);
264 }
265
266 static void
267 kill_line (w, event, argv, argc)
268      Widget w;
269      XEvent *event;
270      String *argv;
271      Cardinal *argc;
272 {
273   memset (typed_passwd, 0, sizeof (typed_passwd));
274   XmTextFieldSetString (passwd_text, "");
275 }
276
277 static void
278 done (w, event, argv, argc)
279      Widget w;
280      XEvent *event;
281      String *argv;
282      Cardinal *argc;
283 {
284   passwd_done_cb (w, 0, 0);
285 }
286
287 #endif /* !VERIFY_CALLBACK_WORKS */
288
289 static void
290 format_into_label (widget, string)
291      Widget widget;
292      char *string;
293 {
294   char *label;
295   char buf [255];
296   XmString xm_label = 0;
297   XmString new_xm_label;
298   Arg av[10];
299   int ac = 0;
300   XtSetArg (av [ac], XmNlabelString, &xm_label); ac++;
301   XtGetValues (widget, av, ac);
302   XmStringGetLtoR (xm_label, XmSTRING_DEFAULT_CHARSET, &label);
303   if (!strcmp (label, XtName (widget)))
304     strcpy (buf, "ERROR: RESOURCES ARE NOT INSTALLED CORRECTLY");
305   else
306     sprintf (buf, label, string);
307   new_xm_label = XmStringCreate (buf, XmSTRING_DEFAULT_CHARSET);
308   ac = 0;
309   XtSetArg (av [ac], XmNlabelString, new_xm_label); ac++;
310   XtSetValues (widget, av, ac);
311   XmStringFree (new_xm_label);
312   XtFree (label);
313 }
314
315 #if __STDC__
316 extern void skull (Display *, Window, GC, GC, int, int, int, int);
317 #endif
318
319 static void
320 roger (button, client_data, call_data)
321      Widget button;
322      XtPointer client_data, call_data;
323 {
324   Display *dpy = XtDisplay (button);
325   Screen *screen = XtScreen (button);
326   Window window = XtWindow (button);
327   Arg av [10];
328   int ac = 0;
329   XGCValues gcv;
330   Colormap cmap;
331   GC draw_gc, erase_gc;
332   unsigned int fg, bg;
333   int x, y, size;
334   XWindowAttributes xgwa;
335   XGetWindowAttributes (dpy, window, &xgwa);
336   cmap = xgwa.colormap;
337   if (xgwa.width > xgwa.height) size = xgwa.height;
338   else size = xgwa.width;
339   if (size > 40) size -= 30;
340   x = (xgwa.width - size) / 2;
341   y = (xgwa.height - size) / 2;
342   XtSetArg (av [ac], XmNforeground, &fg); ac++;
343   XtSetArg (av [ac], XmNbackground, &bg); ac++;
344   XtGetValues (button, av, ac);
345   /* if it's black on white, swap it cause it looks better (hack hack) */
346   if (fg == BlackPixelOfScreen (screen) && bg == WhitePixelOfScreen (screen))
347     fg = WhitePixelOfScreen (screen), bg = BlackPixelOfScreen (screen);
348   gcv.foreground = bg;
349   erase_gc = XCreateGC (dpy, window, GCForeground, &gcv);
350   gcv.foreground = fg;
351   draw_gc = XCreateGC (dpy, window, GCForeground, &gcv);
352   XFillRectangle (dpy, window, erase_gc, 0, 0, xgwa.width, xgwa.height);
353   skull (dpy, window, draw_gc, erase_gc, x, y, size, size);
354   XFreeGC (dpy, draw_gc);
355   XFreeGC (dpy, erase_gc);
356 }
357
358 static void
359 make_passwd_dialog (parent)
360      Widget parent;
361 {
362   struct passwd *pw;
363   create_passwd_dialog (parent);
364
365   XtAddCallback (passwd_done, XmNactivateCallback, passwd_done_cb, 0);
366   XtAddCallback (passwd_cancel, XmNactivateCallback, passwd_cancel_cb, 0);
367   XtAddCallback (roger_label, XmNexposeCallback, roger, 0);
368
369 #ifdef VERIFY_CALLBACK_WORKS
370   XtAddCallback (passwd_text, XmNmodifyVerifyCallback, check_passwd_cb, 0);
371   XtAddCallback (passwd_text, XmNactivateCallback, check_passwd_cb, 0);
372 #else
373   XtAddCallback (passwd_text, XmNactivateCallback, passwd_done_cb, 0);
374   XtOverrideTranslations (passwd_text, XtParseTranslationTable (translations));
375 #endif
376
377 #if (XmVersion >= 1002)
378   /* The focus stuff changed around; this didn't exist in 1.1.5. */
379   XtVaSetValues (passwd_form, XmNinitialFocus, passwd_text, 0);
380 #endif
381
382   /* Another random thing necessary in 1.2.1 but not 1.1.5... */
383   XtVaSetValues (roger_label, XmNborderWidth, 2, 0);
384
385   pw = getpwuid (getuid ());
386   format_into_label (passwd_label3, (pw->pw_name ? pw->pw_name : "???"));
387   format_into_label (passwd_label1, screensaver_version);
388 }
389
390
391 extern void idle_timer ();
392
393 static int passwd_idle_timer_tick;
394 static XtIntervalId id;
395
396 static void
397 passwd_idle_timer (junk1, junk2)
398      void *junk1;
399      XtPointer junk2;
400 {
401   Display *dpy = XtDisplay (passwd_form);
402   Window window = XtWindow (passwd_form);
403   static Dimension x, y, d, s, ss;
404   static GC gc = 0;
405   int max = passwd_timeout / 1000;
406
407   idle_timer (junk1, junk2);
408
409   if (passwd_idle_timer_tick == max)  /* first time */
410     {
411       Arg av [10];
412       int ac = 0;
413       XGCValues gcv;
414       unsigned long fg, bg;
415       XtSetArg (av [ac], XmNheight, &d); ac++;
416       XtGetValues (passwd_done, av, ac);
417       ac = 0;
418       XtSetArg (av [ac], XmNwidth, &x); ac++;
419       XtSetArg (av [ac], XmNheight, &y); ac++;
420       XtSetArg (av [ac], XmNforeground, &fg); ac++;
421       XtSetArg (av [ac], XmNbackground, &bg); ac++;
422       XtGetValues (passwd_form, av, ac);
423       x -= d;
424       y -= d;
425       d -= 4;
426       gcv.foreground = fg;
427       if (gc) XFreeGC (dpy, gc);
428       gc = XCreateGC (dpy, window, GCForeground, &gcv);
429       s = 360*64 / (passwd_idle_timer_tick - 1);
430       ss = 90*64;
431       XFillArc (dpy, window, gc, x, y, d, d, 0, 360*64);
432       XSetForeground (dpy, gc, bg);
433       x += 1;
434       y += 1;
435       d -= 2;
436     }
437
438   if (--passwd_idle_timer_tick)
439     {
440       id = XtAppAddTimeOut (app, 1000, passwd_idle_timer, 0);
441       XFillArc (dpy, window, gc, x, y, d, d, ss, s);
442       ss += s;
443     }
444 }
445
446 extern void pop_up_dialog_box ();
447 extern int BadWindow_ehandler ();
448
449 static Bool
450 pop_passwd_dialog (parent)
451      Widget parent;
452 {
453   Display *dpy = XtDisplay (passwd_dialog);
454   Window focus;
455   int revert_to;
456   typed_passwd [0] = 0;
457   passwd_state = pw_read;
458   XmTextFieldSetString (passwd_text, "");
459
460   XGetInputFocus (dpy, &focus, &revert_to);
461 #ifndef DESTROY_WORKS
462   /* This fucker blows up if we destroy the widget.  I can't figure
463      out why.  The second destroy phase dereferences freed memory...
464      So we just keep it around; but unrealizing or unmanaging it
465      doesn't work right either, so we hack the window directly. FMH.
466    */
467   if (XtWindow (passwd_form))
468     XMapRaised (dpy, XtWindow (passwd_dialog));
469 #endif
470
471   pop_up_dialog_box (passwd_dialog, passwd_form, 2);
472   XtManageChild (passwd_form);
473
474 #if (XmVersion < 1002)
475   /* The focus stuff changed around; this causes problems in 1.2.1
476      but is necessary in 1.1.5. */
477   XmProcessTraversal (passwd_text, XmTRAVERSE_CURRENT);
478 #endif
479
480   passwd_idle_timer_tick = passwd_timeout / 1000;
481   id = XtAppAddTimeOut (app, 1000, passwd_idle_timer, 0);
482
483
484   XGrabServer (dpy);                            /* ############ DANGER! */
485
486   /* this call to ungrab used to be in main_loop() - see comment in
487       xscreensaver.c around line 696. */
488   ungrab_keyboard_and_mouse ();
489
490   while (passwd_state == pw_read)
491     {
492       XEvent event;
493       XtAppNextEvent (app, &event);
494       /* wait for timer event */
495       if (event.xany.type == 0 && passwd_idle_timer_tick == 0)
496         passwd_state = pw_time;
497       XtDispatchEvent (&event);
498     }
499   XUngrabServer (dpy);
500   XSync (dpy, False);                           /* ###### (danger over) */
501
502   if (passwd_state != pw_time)
503     XtRemoveTimeOut (id);
504
505   if (passwd_state != pw_ok)
506     {
507       char *lose;
508       switch (passwd_state)
509         {
510         case pw_time: lose = "Timed out!"; break;
511         case pw_fail: lose = "Sorry!"; break;
512         case pw_cancel: lose = 0; break;
513         default: abort ();
514         }
515       XmProcessTraversal (passwd_cancel, 0); /* turn off I-beam */
516       if (lose)
517         {
518           XmTextFieldSetString (passwd_text, lose);
519           XmTextFieldSetInsertionPosition (passwd_text, strlen (lose) + 1);
520           passwd_idle_timer_tick = 1;
521           id = XtAppAddTimeOut (app, 3000, passwd_idle_timer, 0);
522           while (1)
523             {
524               XEvent event;
525               XtAppNextEvent (app, &event);
526               if (event.xany.type == 0 &&       /* wait for timer event */
527                   passwd_idle_timer_tick == 0)
528                 break;
529               XtDispatchEvent (&event);
530             }
531         }
532     }
533   memset (typed_passwd, 0, sizeof (typed_passwd));
534   XmTextFieldSetString (passwd_text, "");
535   XtSetKeyboardFocus (parent, None);
536
537 #ifdef DESTROY_WORKS
538   XtDestroyWidget (passwd_dialog);
539   passwd_dialog = 0;
540 #else
541   XUnmapWindow (XtDisplay (passwd_dialog), XtWindow (passwd_dialog));
542 #endif
543   {
544     int (*old_handler) ();
545     old_handler = XSetErrorHandler (BadWindow_ehandler);
546     /* I don't understand why this doesn't refocus on the old selected
547        window when MWM is running in click-to-type mode.  The value of
548        `focus' seems to be correct. */
549     XSetInputFocus (dpy, focus, revert_to, CurrentTime);
550     XSync (dpy, False);
551     XSetErrorHandler (old_handler);
552   }
553
554   return (passwd_state == pw_ok ? True : False);
555 }
556
557 Bool
558 unlock_p (parent)
559      Widget parent;
560 {
561   static Bool initted = False;
562   if (! initted)
563     {
564 #ifndef VERIFY_CALLBACK_WORKS
565       XtAppAddActions (app, actions, XtNumber (actions));
566 #endif
567       passwd_dialog = 0;
568       initted = True;
569     }
570   if (! passwd_dialog)
571     make_passwd_dialog (parent);
572   return pop_passwd_dialog (parent);
573 }
574
575 #endif /* !NO_LOCKING */