http://ftp.aanet.ru/pub/Linux/X11/apps/xscreensaver-2.31.tar.gz
[xscreensaver] / driver / lock.c
1 /* lock.c --- handling the password dialog for locking-mode.
2  * xscreensaver, Copyright (c) 1993-1998 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 /* Athena locking code contributed by Jon A. Christopher <jac8782@tamu.edu> */
14 /* Copyright 1997, with the same permissions as above. */
15
16 #ifdef HAVE_CONFIG_H
17 # include "config.h"
18 #endif
19
20 #ifndef NO_LOCKING   /* whole file */
21
22 #include <X11/StringDefs.h>
23 #include <X11/Intrinsic.h>
24 #include <X11/IntrinsicP.h>     /* for XtResizeWidget */
25 #include "xscreensaver.h"
26 #include "resources.h"
27
28 #ifndef VMS
29 # include <pwd.h>
30 #else /* VMS */
31 extern char *getenv(const char *name);
32 extern int validate_user(char *name, char *password);
33 #endif /* VMS */
34
35
36 #ifdef HAVE_ATHENA
37
38 # include <X11/Shell.h>
39 # include <X11/StringDefs.h>
40 # include <X11/Xaw/Text.h>
41 # include <X11/Xaw/Label.h>
42 # include <X11/Xaw/Dialog.h>
43
44 #else  /* HAVE_MOTIF */
45
46 # include <Xm/Xm.h>
47 # include <Xm/List.h>
48 # include <Xm/TextF.h>
49
50 #endif /* HAVE_MOTIF */
51
52 #ifdef _VROOT_H_
53 ERROR!  You must not include vroot.h in this file.
54 #endif
55
56 extern Widget passwd_dialog;
57 extern Widget passwd_form;
58 extern Widget roger_label;
59 extern Widget passwd_label1;
60 extern Widget passwd_label3;
61 extern Widget passwd_cancel;
62
63 #ifdef HAVE_MOTIF
64 extern Widget passwd_text;
65 extern Widget passwd_done;
66 #else  /* HAVE_ATHENA */
67 static Widget passwd_text = 0;  /* gag... */
68 static Widget passwd_done = 0;
69 #endif /* HAVE_ATHENA */
70
71
72
73 static enum { pw_read, pw_ok, pw_fail, pw_cancel, pw_time } passwd_state;
74 static char typed_passwd [80];
75
76 \f
77 #if defined(HAVE_ATHENA) || (XmVersion >= 1002)
78    /* The `destroy' bug apears to be fixed as of Motif 1.2.1, but
79       the `verify-callback' bug is still present. */
80 # define DESTROY_WORKS
81 #endif
82
83 static void
84 passwd_cancel_cb (Widget button, XtPointer client_data, XtPointer call_data)
85 {
86   passwd_state = pw_cancel;
87 }
88
89
90 #ifdef VMS
91 static Bool
92 vms_passwd_valid_p(char *pw)
93 {
94   char *u = getenv("USER");
95   return (validate_user (i, typed_passwd) == 1);
96 }
97 # undef passwd_valid_p
98 # define passwd_valid_p vms_passwd_valid_p
99
100 #endif /* VMS */
101
102
103 static void
104 passwd_done_cb (Widget button, XtPointer client_data, XtPointer call_data)
105 {
106   if (passwd_state != pw_read) return; /* already done */
107
108   if (passwd_valid_p (typed_passwd))
109     passwd_state = pw_ok;
110   else
111     passwd_state = pw_fail;
112 }
113
114
115 #if defined(HAVE_MOTIF) && defined(VERIFY_CALLBACK_WORKS)
116
117   /* It looks to me like adding any modifyVerify callback causes
118      Motif 1.1.4 to free the the TextF_Value() twice.  I can't see
119      the bug in the Motif source, but Purify complains, even if
120      check_passwd_cb() is a no-op.
121
122      Update: Motif 1.2.1 also loses, but in a different way: it
123      writes beyond the end of a malloc'ed block in ModifyVerify().
124      Probably this block is the text field's text.
125    */
126
127 static void 
128 check_passwd_cb (Widget button, XtPointer client_data, XtPointer call_data)
129 {
130   XmTextVerifyCallbackStruct *vcb = (XmTextVerifyCallbackStruct *) call_data;
131
132   if (passwd_state != pw_read)
133     return;
134   else if (vcb->reason == XmCR_ACTIVATE)
135     {
136       passwd_done_cb (0, 0, 0);
137     }
138   else if (vcb->text->length > 1)       /* don't allow "paste" operations */
139     {
140       vcb->doit = False;
141     }
142   else if (vcb->text->ptr != 0)
143     {
144       int i;
145       int L = vcb->text->length;
146       if (L >= sizeof(typed_passwd))
147         L = sizeof(typed_passwd)-1;
148       strncat (typed_passwd, vcb->text->ptr, L);
149       typed_passwd [vcb->endPos + L] = 0;
150       for (i = 0; i < vcb->text->length; i++)
151         vcb->text->ptr [i] = '*';
152     }
153 }
154
155 # else /* HAVE_ATHENA || !VERIFY_CALLBACK_WORKS */
156
157 static void keypress (Widget w, XEvent *event, String *av, Cardinal *ac);
158 static void backspace (Widget w, XEvent *event, String *av, Cardinal *ac);
159 static void kill_line (Widget w, XEvent *event, String *av, Cardinal *ac);
160 static void done (Widget w, XEvent *event, String *av, Cardinal *ac);
161
162 static XtActionsRec actions[] = {{"keypress",  keypress},
163                                  {"backspace", backspace},
164                                  {"kill_line", kill_line},
165                                  {"done",      done}
166                                 };
167
168 # if 0  /* This works for Athena, but not Motif: keypress() gets called
169            for all keys anyway.  So, the implementation of keypress()
170            has BackSpace, etc, hardcoded into it instead.  FMH!
171          */
172 static char translations[] =  "<Key>BackSpace:  backspace()\n"
173                               "<Key>Delete:     backspace()\n"
174                               "Ctrl<Key>H:      backspace()\n"
175                               "Ctrl<Key>U:      kill_line()\n"
176                               "Ctrl<Key>X:      kill_line()\n"
177                               "Ctrl<Key>J:      done()\n"
178                               "Ctrl<Key>M:      done()\n"
179                               "<Key>:           keypress()\n";
180 # else  /* !0 */
181 static char translations[] =  "<Key>:           keypress()\n";
182 # endif /* !0 */
183
184
185 static void
186 text_field_set_string (Widget widget, char *text, int position)
187 {
188 #ifdef HAVE_MOTIF
189   XmTextFieldSetString (widget, text);
190   XmTextFieldSetInsertionPosition (widget, position);
191
192 #else /* HAVE_ATHENA */
193   char *buf;
194   int end_pos;
195
196   XawTextBlock block;
197   block.firstPos = 0;
198   block.length = strlen (text);
199   block.ptr = text;
200   block.format = 0;
201   if (block.length == 0)
202     {
203       buf = XawDialogGetValueString(passwd_form);
204       if (buf)
205         end_pos = strlen(buf);
206       else
207         end_pos = -1;
208     }
209   XawTextReplace (widget, 0, end_pos, &block);
210   XawTextSetInsertionPoint (widget, position);
211 #endif /* HAVE_ATHENA */
212 }
213
214
215 static void
216 keypress (Widget w, XEvent *event, String *argv, Cardinal *argc)
217 {
218   int i, j;
219   char s [sizeof(typed_passwd)];
220   int size = XLookupString ((XKeyEvent *) event, s, sizeof(s)-1, 0, 0);
221   if (size != 1) return;
222
223   /* hack because I can't get translations to dance to my tune... */
224   if (*s == '\010') { backspace (w, event, argv, argc); return; }
225   if (*s == '\177') { backspace (w, event, argv, argc); return; }
226   if (*s == '\025') { kill_line (w, event, argv, argc); return; }
227   if (*s == '\030') { kill_line (w, event, argv, argc); return; }
228   if (*s == '\012') { done (w, event, argv, argc); return; }
229   if (*s == '\015') { done (w, event, argv, argc); return; }
230
231   i = j = strlen (typed_passwd);
232
233   if (i >= (sizeof(typed_passwd)-1))
234     {
235       XBell(XtDisplay(w), 0);
236       return;
237     }
238
239   typed_passwd [i] = *s;
240   s [++i] = 0;
241   while (i--)
242     s [i] = '*';
243
244   text_field_set_string (passwd_text, s, j + 1);
245 }
246
247 static void
248 backspace (Widget w, XEvent *event, String *argv, Cardinal *argc)
249 {
250   char s [sizeof(typed_passwd)];
251   int i = strlen (typed_passwd);
252   int j = i;
253   if (i == 0)
254     return;
255   typed_passwd [--i] = 0;
256   s [i] = 0;
257   while (i--)
258     s [i] = '*';
259
260   text_field_set_string (passwd_text, s, j + 1);
261 }
262
263 static void
264 kill_line (Widget w, XEvent *event, String *argv, Cardinal *argc)
265 {
266   memset (typed_passwd, 0, sizeof(typed_passwd));
267   text_field_set_string (passwd_text, "", 0);
268 }
269
270 static void
271 done (Widget w, XEvent *event, String *argv, Cardinal *argc)
272 {
273   passwd_done_cb (w, 0, 0);
274 }
275
276 #endif /* HAVE_ATHENA || !VERIFY_CALLBACK_WORKS */
277
278
279 static void
280 make_passwd_dialog (saver_info *si)
281 {
282   char *username = 0;
283   saver_screen_info *ssi = si->default_screen;
284   Widget parent = ssi->toplevel_shell;
285
286   if (ssi->demo_cmap &&
287       ssi->demo_cmap != ssi->cmap &&
288       ssi->demo_cmap != DefaultColormapOfScreen (ssi->screen))
289     {
290       XFreeColormap (si->dpy, ssi->demo_cmap);
291       ssi->demo_cmap = 0;
292     }
293
294   if (ssi->default_visual == DefaultVisualOfScreen (ssi->screen))
295     ssi->demo_cmap = DefaultColormapOfScreen (ssi->screen);
296   else
297     ssi->demo_cmap = XCreateColormap (si->dpy,
298                                      RootWindowOfScreen (ssi->screen),
299                                      ssi->default_visual, AllocNone);
300
301   create_passwd_dialog (parent, ssi->default_visual, ssi->demo_cmap);
302
303 #ifdef HAVE_ATHENA
304   XtVaSetValues(passwd_form, XtNvalue, typed_passwd, 0);
305
306   XawDialogAddButton(passwd_form,"ok", passwd_done_cb, 0);
307   XawDialogAddButton(passwd_form,"cancel", passwd_cancel_cb, 0);
308   passwd_done = XtNameToWidget(passwd_form,"ok");
309   passwd_text = XtNameToWidget(passwd_form,"value");
310
311   XtAppAddActions(XtWidgetToApplicationContext(passwd_text),
312                   actions, XtNumber(actions));
313   XtOverrideTranslations(passwd_text, XtParseTranslationTable(translations));
314
315   /* Lose the label on the inner dialog. */
316   {
317     Widget w = XtNameToWidget(passwd_form, "label");
318     if (w) XtUnmanageChild(w);
319   }
320
321 #else  /* HAVE_MOTIF */
322
323   XtAddCallback (passwd_done, XmNactivateCallback, passwd_done_cb, 0);
324   XtAddCallback (passwd_cancel, XmNactivateCallback, passwd_cancel_cb, 0);
325   XtAddCallback (roger_label, XmNexposeCallback, roger, 0);
326
327 # ifdef VERIFY_CALLBACK_WORKS
328   XtAddCallback (passwd_text, XmNmodifyVerifyCallback, check_passwd_cb, 0);
329   XtAddCallback (passwd_text, XmNactivateCallback, check_passwd_cb, 0);
330 # else  /* !VERIFY_CALLBACK_WORKS */
331   XtAddCallback (passwd_text, XmNactivateCallback, passwd_done_cb, 0);
332   XtOverrideTranslations (passwd_text, XtParseTranslationTable (translations));
333 # endif /* !VERIFY_CALLBACK_WORKS */
334
335 # if defined(HAVE_MOTIF) && (XmVersion >= 1002)
336   /* The focus stuff changed around; this didn't exist in 1.1.5. */
337   XtVaSetValues (passwd_form, XmNinitialFocus, passwd_text, 0);
338 # endif /* HAVE_MOTIF && XmVersion >= 1002 */
339
340   /* Another random thing necessary in 1.2.1 but not 1.1.5... */
341   XtVaSetValues (roger_label, XmNborderWidth, 2, 0);
342
343 #endif /* HAVE_MOTIF */
344
345 #ifndef VMS
346   {
347     struct passwd *pw = getpwuid (getuid ());
348     username = pw->pw_name;
349   }
350 #else  /* VMS -- from "R.S.Niranjan" <U00C782%BRKVC1@navistar.com> who says
351                  that on OpenVMS 6.1, using `struct passwd' crashes... */
352   username = getenv("USER");
353 #endif /* VMS */
354
355   format_into_label (passwd_label1, si->version);
356   format_into_label (passwd_label3, (username ? username : "???"));
357 }
358
359 static int passwd_idle_timer_tick = -1;
360 static XtIntervalId passwd_idle_id;
361
362 static void
363 passwd_idle_timer (XtPointer closure, XtIntervalId *id)
364 {
365   saver_info *si = (saver_info *) closure;
366   saver_preferences *p = &si->prefs;
367
368   Display *dpy = XtDisplay (passwd_form);
369 #ifdef HAVE_ATHENA
370   Window window = XtWindow (passwd_form);
371 #else  /* MOTIF */
372   Window window = XtWindow (XtParent(passwd_done));
373 #endif /* MOTIF */
374   static Dimension x, y, d, s, ss;
375   static GC gc = 0;
376   int max = p->passwd_timeout / 1000;
377
378   idle_timer ((XtPointer) si, id);
379
380   if (passwd_idle_timer_tick == max)  /* first time */
381     {
382       XGCValues gcv;
383 #ifdef HAVE_MOTIF
384       unsigned long fg = 0, bg = 0, ts = 0, bs = 0;
385       Dimension w = 0, h = 0;
386       XtVaGetValues(XtParent(passwd_done),
387                     XmNwidth, &w,
388                     0);
389       XtVaGetValues(passwd_done,
390                     XmNheight, &h,
391                     XmNy, &y,
392                     0);
393       XtVaGetValues(passwd_form,
394                     XtNforeground, &fg,
395                     XtNbackground, &bg,
396                     XmNtopShadowColor, &ts,
397                     XmNbottomShadowColor, &bs,
398                     0);
399
400       if (ts != bg && ts != fg)
401         fg = ts;
402       if (bs != bg && bs != fg)
403         fg = bs;
404
405       d = h / 2;
406       if (d & 1) d++;
407
408       x = (w / 2);
409
410 # ifdef __sgi   /* Kludge -- SGI's Motif hacks place buttons differently. */
411       {
412         static int sgi_mode = -1;
413         if (sgi_mode == -1)
414           sgi_mode = get_boolean_resource("sgiMode", "sgiMode") ? 1 : 0;
415
416         if (sgi_mode)
417           x = d;
418       }
419 # endif /* __sgi */
420
421       x -= d/2;
422       y += d/2;
423
424 #else  /* HAVE_ATHENA */
425
426       Arg av [100];
427       int ac = 0;
428       unsigned long fg = 0, bg = 0;
429       XtSetArg (av [ac], XtNheight, &d); ac++;
430       XtGetValues (passwd_done, av, ac);
431       ac = 0;
432       XtSetArg (av [ac], XtNwidth, &x); ac++;
433       XtSetArg (av [ac], XtNheight, &y); ac++;
434       XtSetArg (av [ac], XtNforeground, &fg); ac++;
435       XtSetArg (av [ac], XtNbackground, &bg); ac++;
436       XtGetValues (passwd_form, av, ac);
437       x -= d;
438       y -= d;
439       d -= 4;
440
441 #endif /* HAVE_ATHENA */
442
443       gcv.foreground = fg;
444       if (gc) XFreeGC (dpy, gc);
445       gc = XCreateGC (dpy, window, GCForeground, &gcv);
446       s = 360*64 / (passwd_idle_timer_tick - 1);
447       ss = 90*64;
448       XFillArc (dpy, window, gc, x, y, d, d, 0, 360*64);
449       XSetForeground (dpy, gc, bg);
450       x += 1;
451       y += 1;
452       d -= 2;
453     }
454
455   if (--passwd_idle_timer_tick)
456     {
457       passwd_idle_id = XtAppAddTimeOut (si->app, 1000, passwd_idle_timer,
458                                         (XtPointer) si);
459       XFillArc (dpy, window, gc, x, y, d, d, ss, s);
460       ss += s;
461     }
462 }
463
464
465 static Bool
466 pop_passwd_dialog (saver_info *si)
467 {
468   saver_preferences *p = &si->prefs;
469   saver_screen_info *ssi = si->default_screen;
470   Widget parent = ssi->toplevel_shell;
471   Display *dpy = XtDisplay (passwd_dialog);
472   Window focus;
473   int revert_to;
474   int i;
475   Window grab_window = RootWindowOfScreen(si->screens[0].screen);
476
477   typed_passwd [0] = 0;
478   passwd_state = pw_read;
479   text_field_set_string (passwd_text, "", 0);
480
481   /* In case one of the hacks has unmapped it temporarily...
482      Get that sucker on stage now! */
483   for (i = 0; i < si->nscreens; i++)
484     XMapRaised(si->dpy, si->screens[i].screensaver_window);
485
486   XGetInputFocus (dpy, &focus, &revert_to);
487 #if defined(HAVE_MOTIF) && !defined(DESTROY_WORKS)
488   /* This fucker blows up if we destroy the widget.  I can't figure
489      out why.  The second destroy phase dereferences freed memory...
490      So we just keep it around; but unrealizing or unmanaging it
491      doesn't work right either, so we hack the window directly. FMH.
492    */
493   if (XtWindow (passwd_form))
494     XMapRaised (dpy, XtWindow (passwd_dialog));
495 #endif /* HAVE_MOTIF && !DESTROY_WORKS */
496
497   monitor_power_on (si);
498   pop_up_dialog_box (passwd_dialog, passwd_form,
499                      /* for debugging -- don't ask */
500                      (si->prefs.debug_p ? 69 : 0) +
501                      2);
502   XtManageChild (passwd_form);
503
504 #ifdef HAVE_ATHENA
505   steal_focus_and_colormap (passwd_text);
506
507   /* For some reason, the passwd_form box is not stretching all the way
508      to the right edge of the window, despite being XtChainRight.
509      So... resize it by hand.
510   */
511   {
512     Dimension x=0, w=0, h=0;
513     XtVaGetValues(passwd_form, XtNx, &x, XtNwidth, &w, XtNheight, &h, 0);
514     XtVaGetValues(XtParent(passwd_form), XtNwidth, &w, 0);
515     w -= x;
516     w -= 6;
517     if (w > 0) XtResizeWidget(passwd_form, w, h, 0);
518   }
519
520 #endif /* HAVE_ATHENA */
521
522
523 #if defined(HAVE_MOTIF) && (XmVersion < 1002)
524   /* The focus stuff changed around; this causes problems in 1.2.1
525      but is necessary in 1.1.5. */
526   XmProcessTraversal (passwd_text, XmTRAVERSE_CURRENT);
527 #endif /* HAVE_MOTIF && XmVersion < 1002 */
528
529   passwd_idle_timer_tick = p->passwd_timeout / 1000;
530   passwd_idle_id = XtAppAddTimeOut (si->app, 1000,  passwd_idle_timer,
531                                     (XtPointer) si);
532
533 #ifdef HAVE_ATHENA
534   if (roger_label)
535     roger(roger_label, 0, 0);
536 #endif /* HAVE_ATHENA */
537
538
539   /* Make sure the mouse cursor is visible.
540      Since the screensaver was already active, we had already called
541      grab_keyboard_and_mouse() with our "invisible" Cursor object.
542      Now we need to change that.  (cursor == 0 means "server default
543      cursor.")
544    */
545   if (grab_window != si->mouse_grab_window ||
546       grab_window != si->keyboard_grab_window)
547     fprintf(stderr,
548             "%s: WARNING: expected mouse and keyboard grabs on 0x%x,\n"
549             "\tbut mouse-grab is 0x%x and keyboard-grab is 0x%x.\n",
550             blurb(),
551             (unsigned long) grab_window,
552             (unsigned long) si->mouse_grab_window,
553             (unsigned long) si->keyboard_grab_window);
554
555   if (p->verbose_p)
556     fprintf(stderr, "%s: re-grabbing keyboard and mouse to expose cursor.\n",
557             blurb());
558   grab_keyboard_and_mouse (si, grab_window, 0);
559
560
561   if (!si->prefs.debug_p)
562     XGrabServer (dpy);                          /* ############ DANGER! */
563
564   while (passwd_state == pw_read)
565     {
566       XEvent event;
567       XtAppNextEvent (si->app, &event);
568       /* wait for timer event */
569       if (event.xany.type == 0 && passwd_idle_timer_tick == 0)
570         passwd_state = pw_time;
571       XtDispatchEvent (&event);
572     }
573   XUngrabServer (dpy);
574   XSync (dpy, False);                           /* ###### (danger over) */
575
576
577   /* Now turn off the mouse cursor again.
578    */
579   if (p->verbose_p)
580     fprintf(stderr, "%s: re-grabbing keyboard and mouse to hide cursor.\n",
581             blurb());
582   grab_keyboard_and_mouse (si, grab_window, si->screens[0].cursor);
583
584
585   if (passwd_state != pw_time)
586     XtRemoveTimeOut (passwd_idle_id);
587
588   if (passwd_state != pw_ok)
589     {
590       char *lose;
591       switch (passwd_state)
592         {
593         case pw_time: lose = "Timed out!"; break;
594         case pw_fail: lose = "Sorry!"; break;
595         case pw_cancel: lose = 0; break;
596         default: abort ();
597         }
598
599 #ifdef HAVE_MOTIF
600       XmProcessTraversal (passwd_cancel, 0); /* turn off I-beam */
601 #else  /* HAVE_ATHENA */
602       steal_focus_and_colormap (passwd_done);
603 #endif /* HAVE_ATHENA */
604
605       if (lose)
606         {
607           text_field_set_string (passwd_text, lose, strlen (lose) + 1);
608
609           passwd_idle_timer_tick = 1;
610           passwd_idle_id = XtAppAddTimeOut (si->app, 3000, passwd_idle_timer,
611                                 (XtPointer) si);
612           while (1)
613             {
614               XEvent event;
615               XtAppNextEvent (si->app, &event);
616               if (event.xany.type == 0 &&       /* wait for timer event */
617                   passwd_idle_timer_tick == 0)
618                 break;
619               XtDispatchEvent (&event);
620             }
621         }
622     }
623   memset (typed_passwd, 0, sizeof(typed_passwd));
624   text_field_set_string (passwd_text, "", 0);
625   XtSetKeyboardFocus (parent, None);
626
627 #ifdef DESTROY_WORKS
628   XtDestroyWidget (passwd_dialog);
629   passwd_dialog = 0;
630 #else  /* !DESTROY_WORKS */
631   XUnmapWindow (XtDisplay (passwd_dialog), XtWindow (passwd_dialog));
632 #endif /* !DESTROY_WORKS */
633   {
634     XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
635     /* I don't understand why this doesn't refocus on the old selected
636        window when MWM is running in click-to-type mode.  The value of
637        `focus' seems to be correct. */
638     XSetInputFocus (dpy, focus, revert_to, CurrentTime);
639     XSync (dpy, False);
640     XSetErrorHandler (old_handler);
641   }
642
643   /* Since we installed our colormap to display the dialog properly, put
644      the old one back, so that the screensaver_window is now displayed
645      properly. */
646   for (i = 0; i < si->nscreens; i++)
647     {
648       saver_screen_info *ssi = &si->screens[i];
649       if (ssi->cmap)
650         XInstallColormap (si->dpy, ssi->cmap);
651     }
652
653   return (passwd_state == pw_ok ? True : False);
654 }
655
656 Bool
657 unlock_p (saver_info *si)
658 {
659   static Bool initted = False;
660   if (! initted)
661     {
662
663 #ifndef VERIFY_CALLBACK_WORKS
664       XtAppAddActions (si->app, actions, XtNumber (actions));
665 #endif /* !VERIFY_CALLBACK_WORKS */
666
667       passwd_dialog = 0;
668       initted = True;
669     }
670   if (! passwd_dialog)
671     make_passwd_dialog (si);
672   return pop_passwd_dialog (si);
673 }
674
675 #endif /* !NO_LOCKING -- whole file */