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