Merge from vendor branch NCURSES:
[dragonfly.git] / crypto / heimdal-0.6.3 / appl / xnlock / xnlock.c
1 /*
2  * xnlock -- Dan Heller, 1990
3  * "nlock" is a "new lockscreen" type program... something that prevents
4  * screen burnout by making most of it "black" while providing something
5  * of interest to be displayed in case anyone is watching.
6  * "xnlock" is the X11 version of the program.
7  * Original sunview version written by Dan Heller 1985 (not included here).
8  */
9 #ifdef HAVE_CONFIG_H
10 #include <config.h>
11 RCSID("$Id: xnlock.c,v 1.93.2.4 2004/09/08 09:16:00 joda Exp $");
12 #endif
13
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <signal.h>
18 #include <X11/StringDefs.h>
19 #include <X11/Intrinsic.h>
20 #include <X11/keysym.h>
21 #include <X11/Shell.h>
22 #include <X11/Xos.h>
23 #ifdef strerror
24 #undef strerror
25 #endif
26 #include <ctype.h>
27 #ifdef HAVE_SYS_TYPES_H
28 #include <sys/types.h>
29 #endif
30 #ifdef HAVE_PWD_H
31 #include <pwd.h>
32 #endif
33 #ifdef HAVE_CRYPT_H
34 #undef des_encrypt
35 #define des_encrypt wingless_pigs_mostly_fail_to_fly
36 #include <crypt.h>
37 #undef des_encrypt
38 #endif
39
40 #ifdef KRB5
41 #include <krb5.h>
42 #endif
43 #ifdef KRB4
44 #include <krb.h>
45 #endif
46 #if defined(KRB4) || defined(KRB5)
47 #include <kafs.h>
48 #endif
49
50 #include <roken.h>
51 #include <err.h>
52
53 static char login[16];
54 static char userprompt[128];
55 #ifdef KRB4
56 static char name[ANAME_SZ];
57 static char inst[INST_SZ];
58 static char realm[REALM_SZ];
59 #endif
60 #ifdef KRB5
61 static krb5_context context;
62 static krb5_principal client;
63 #endif
64
65 #define font_height(font)               (font->ascent + font->descent)
66
67 static char *SPACE_STRING = "                                                      ";
68 static char STRING[] = "****************";
69
70 #define STRING_LENGTH (sizeof(STRING))
71 #define MAX_PASSWD_LENGTH 256
72 /* (sizeof(STRING)) */
73
74 #define PROMPT      "Password: "
75 #define FAIL_MSG    "Sorry, try again"
76 #define LEFT    001
77 #define RIGHT   002
78 #define DOWN    004
79 #define UP      010
80 #define FRONT   020
81 #define X_INCR 3
82 #define Y_INCR 2
83 #define XNLOCK_CTRL 1
84 #define XNLOCK_NOCTRL 0
85
86 static XtAppContext     app;
87 static Display        *dpy;
88 static unsigned short   Width, Height;
89 static Widget           widget;
90 static GC               gc;
91 static XtIntervalId     timeout_id;
92 static char            *words;
93 static int              x, y;
94 static Pixel            Black, White;
95 static XFontStruct    *font;
96 static char             root_cpass[128];
97 static char             user_cpass[128];
98 static int              time_left, prompt_x, prompt_y, time_x, time_y;
99 static unsigned long    interval;
100 static Pixmap           left0, left1, right0, right1, left_front,
101                         right_front, front, down;
102
103 #define MAXLINES 40
104
105 #define IS_MOVING  1
106 #define GET_PASSWD 2
107 static int state; /* indicates states: walking or getting passwd */
108
109 static int ALLOW_LOGOUT = (60*10);      /* Allow logout after nn seconds */
110 #define LOGOUT_PASSWD "enuHDmTo5Lq4g" /* when given password "LOGOUT" */
111 static time_t locked_at;
112
113 struct appres_t {
114     Pixel bg;
115     Pixel fg;
116     XFontStruct *font;
117     Boolean ignore_passwd;
118     Boolean do_reverse;
119     Boolean accept_root;
120     char *text, *text_prog, *file, *logoutPasswd;
121     Boolean no_screensaver;
122     Boolean destroytickets;
123 } appres;
124
125 static XtResource resources[] = {
126     { XtNbackground, XtCBackground, XtRPixel, sizeof(Pixel), 
127       XtOffsetOf(struct appres_t, bg), XtRString, "black" },
128
129     { XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel), 
130       XtOffsetOf(struct appres_t, fg), XtRString, "white" },
131
132     { XtNfont, XtCFont, XtRFontStruct, sizeof (XFontStruct *),
133       XtOffsetOf(struct appres_t, font), 
134       XtRString, "-*-new century schoolbook-*-*-*-18-*" },
135
136     { "ignorePasswd", "IgnorePasswd", XtRBoolean, sizeof(Boolean),
137       XtOffsetOf(struct appres_t,ignore_passwd),XtRImmediate,(XtPointer)False },
138
139     { "acceptRootPasswd", "AcceptRootPasswd", XtRBoolean, sizeof(Boolean),
140       XtOffsetOf(struct appres_t, accept_root), XtRImmediate, (XtPointer)True },
141
142     { "text", "Text", XtRString, sizeof(String),
143       XtOffsetOf(struct appres_t, text), XtRString, "I'm out running around." },
144     
145     { "program", "Program", XtRString, sizeof(String),
146       XtOffsetOf(struct appres_t, text_prog), XtRImmediate, NULL },
147
148     { "file", "File", XtRString, sizeof(String),
149       XtOffsetOf(struct appres_t,file), XtRImmediate, NULL },
150
151     { "logoutPasswd", "logoutPasswd", XtRString, sizeof(String),
152       XtOffsetOf(struct appres_t, logoutPasswd), XtRString, LOGOUT_PASSWD },
153     
154     { "noScreenSaver", "NoScreenSaver", XtRBoolean, sizeof(Boolean),
155       XtOffsetOf(struct appres_t,no_screensaver), XtRImmediate, (XtPointer)True },
156
157     { "destroyTickets", "DestroyTickets", XtRBoolean, sizeof(Boolean),
158       XtOffsetOf(struct appres_t,destroytickets), XtRImmediate, (XtPointer)True },
159 };
160
161 static XrmOptionDescRec options[] = {
162     { "-fg", ".foreground", XrmoptionSepArg, NULL }, 
163     { "-foreground", ".foreground", XrmoptionSepArg, NULL }, 
164     { "-fn", ".font", XrmoptionSepArg, NULL }, 
165     { "-font", ".font", XrmoptionSepArg, NULL }, 
166     { "-ip", ".ignorePasswd", XrmoptionNoArg, "True" },
167     { "-noip", ".ignorePasswd", XrmoptionNoArg, "False" },
168     { "-ar",  ".acceptRootPasswd", XrmoptionNoArg, "True" },
169     { "-noar", ".acceptRootPasswd", XrmoptionNoArg, "False" },
170     { "-nonoscreensaver", ".noScreenSaver", XrmoptionNoArg, "False" },
171     { "-nodestroytickets", ".destroyTickets", XrmoptionNoArg, "False" },
172 };
173
174 static char*
175 get_words(void)
176 {
177     FILE *pp = NULL;
178     static char buf[512];
179     long n;
180
181     if (appres.text_prog) {
182         pp = popen(appres.text_prog, "r");
183         if (!pp) {
184             warn("popen %s", appres.text_prog);
185             return appres.text;
186         }
187         n = fread(buf, 1, sizeof(buf) - 1, pp);
188         buf[n] = 0;
189         pclose(pp);
190         return buf;
191     }
192     if (appres.file) {
193         pp = fopen(appres.file, "r");
194         if (!pp) {
195             warn("fopen %s", appres.file);
196             return appres.text;
197         }
198         n = fread(buf, 1, sizeof(buf) - 1, pp);
199         buf[n] = 0;
200         fclose(pp);
201         return buf;
202     }
203
204     return appres.text;
205 }
206
207 static void
208 usage(void)
209 {
210     fprintf(stderr, "usage: %s [options] [message]\n", getprogname());
211     fprintf(stderr, "-fg color     foreground color\n");
212     fprintf(stderr, "-bg color     background color\n");
213     fprintf(stderr, "-rv           reverse foreground/background colors\n");
214     fprintf(stderr, "-nrv          no reverse video\n");
215     fprintf(stderr, "-ip           ignore passwd\n");
216     fprintf(stderr, "-nip          don't ignore passwd\n");
217     fprintf(stderr, "-ar           accept root's passwd to unlock\n");
218     fprintf(stderr, "-nar          don't accept root's passwd\n");
219     fprintf(stderr, "-f [file]     message is read from file or ~/.msgfile\n");
220     fprintf(stderr, "-prog program  text is gotten from executing `program'\n");
221     fprintf(stderr, "-nodestroytickets keep kerberos tickets\n");
222     exit(1);
223 }
224
225 static void
226 init_words (int argc, char **argv)
227 {
228     int i = 0;
229
230     while(argv[i]) {
231         if(strcmp(argv[i], "-p") == 0
232            || strcmp(argv[i], "-prog") == 0) {
233             i++;
234             if(argv[i]) {
235                 appres.text_prog = argv[i];
236                 i++;
237             } else {
238                 warnx ("-p requires an argument");
239                 usage();
240             }
241         } else if(strcmp(argv[i], "-f") == 0) {
242             i++;
243             if(argv[i]) {
244                 appres.file = argv[i];
245                 i++;
246             } else {
247                 asprintf (&appres.file,
248                           "%s/.msgfile", getenv("HOME"));
249                 if (appres.file == NULL)
250                     errx (1, "cannot allocate memory for message");
251             }
252         } else if(strcmp(argv[i], "--version") == 0) {
253             print_version(NULL);
254             exit(0);
255         } else {
256             int j;
257             int len = 1;
258             for(j = i; argv[j]; j++)
259                 len += strlen(argv[j]) + 1;
260             appres.text = malloc(len);
261             if (appres.text == NULL)
262                 errx (1, "cannot allocate memory for message");
263             appres.text[0] = 0;
264             for(; i < j; i++){
265                 strlcat(appres.text, argv[i], len);
266                 strlcat(appres.text, " ", len);
267             }
268         }
269     }
270 }
271
272 static void
273 ScreenSaver(int save)
274 {
275     static int timeout, interval, prefer_blank, allow_exp;
276     if(!appres.no_screensaver){
277         if (save) {
278             XGetScreenSaver(dpy, &timeout, &interval, 
279                             &prefer_blank, &allow_exp);
280             XSetScreenSaver(dpy, 0, interval, prefer_blank, allow_exp);
281         } else
282             /* restore state */
283             XSetScreenSaver(dpy, timeout, interval, prefer_blank, allow_exp);
284     }
285 }
286
287 /* Forward decls necessary */
288 static void talk(int force_erase);
289 static unsigned long look(void);
290
291 static int
292 zrefresh(void)
293 {
294   switch (fork()) {
295   case -1:
296       warn ("zrefresh: fork");
297       return -1;
298   case 0:
299       /* Child */
300       execlp("zrefresh", "zrefresh", 0);
301       execl(BINDIR "/zrefresh", "zrefresh", 0);
302       return -1;
303   default:
304       /* Parent */
305       break;
306   }
307   return 0;
308 }
309
310 static void
311 leave(void)
312 {
313     XUngrabPointer(dpy, CurrentTime);
314     XUngrabKeyboard(dpy, CurrentTime);
315     ScreenSaver(0);
316     XCloseDisplay(dpy);
317     zrefresh();
318     exit(0);
319 }
320
321 static void
322 walk(int dir)
323 {
324     int incr = 0;
325     static int lastdir;
326     static int up = 1;
327     static Pixmap frame;
328
329     XSetForeground(dpy, gc, White);
330     XSetBackground(dpy, gc, Black);
331     if (dir & (LEFT|RIGHT)) { /* left/right movement (mabye up/down too) */
332         up = -up; /* bouncing effect (even if hit a wall) */
333         if (dir & LEFT) {
334             incr = X_INCR;
335             frame = (up < 0) ? left0 : left1;
336         } else {
337             incr = -X_INCR;
338             frame = (up < 0) ? right0 : right1;
339         }
340         if ((lastdir == FRONT || lastdir == DOWN) && dir & UP) {
341             /* workaround silly bug that leaves screen dust when
342              * guy is facing forward or down and moves up-left/right.
343              */
344             XCopyPlane(dpy, frame, XtWindow(widget), gc, 0, 0, 64,64, x, y, 1L);
345             XFlush(dpy);
346         }
347         /* note that maybe neither UP nor DOWN is set! */
348         if (dir & UP && y > Y_INCR)
349             y -= Y_INCR;
350         else if (dir & DOWN && y < (int)Height - 64)
351             y += Y_INCR;
352     }
353     /* Explicit up/down movement only (no left/right) */
354     else if (dir == UP)
355         XCopyPlane(dpy, front, XtWindow(widget), gc,
356             0,0, 64,64, x, y -= Y_INCR, 1L);
357     else if (dir == DOWN)
358         XCopyPlane(dpy, down, XtWindow(widget), gc,
359             0,0, 64,64, x, y += Y_INCR, 1L);
360     else if (dir == FRONT && frame != front) {
361         if (up > 0)
362             up = -up;
363         if (lastdir & LEFT)
364             frame = left_front;
365         else if (lastdir & RIGHT)
366             frame = right_front;
367         else
368             frame = front;
369         XCopyPlane(dpy, frame, XtWindow(widget), gc, 0, 0, 64,64, x, y, 1L);
370     }
371     if (dir & LEFT)
372         while(--incr >= 0) {
373             XCopyPlane(dpy, frame, XtWindow(widget), gc,
374                 0,0, 64,64, --x, y+up, 1L);
375             XFlush(dpy);
376         }
377     else if (dir & RIGHT)
378         while(++incr <= 0) {
379             XCopyPlane(dpy, frame, XtWindow(widget), gc,
380                 0,0, 64,64, ++x, y+up, 1L);
381             XFlush(dpy);
382         }
383     lastdir = dir;
384 }
385
386 static long
387 my_random (void)
388 {
389 #ifdef HAVE_RANDOM
390     return random();
391 #else
392     return rand();
393 #endif
394 }
395
396 static int
397 think(void)
398 {
399     if (my_random() & 1)
400         walk(FRONT);
401     if (my_random() & 1) {
402         words = get_words();
403         return 1;
404     }
405     return 0;
406 }
407
408 static void
409 move(XtPointer _p, XtIntervalId *_id)
410 {
411     static int length, dir;
412
413     if (!length) {
414         int tries = 0;
415         dir = 0;
416         if ((my_random() & 1) && think()) {
417             talk(0); /* sets timeout to itself */
418             return;
419         }
420         if (!(my_random() % 3) && (interval = look())) {
421             timeout_id = XtAppAddTimeOut(app, interval, move, NULL);
422             return;
423         }
424         interval = 20 + my_random() % 100;
425         do  {
426             if (!tries)
427                 length = Width/100 + my_random() % 90, tries = 8;
428             else
429                 tries--;
430             switch (my_random() % 8) {
431                 case 0:
432                     if (x - X_INCR*length >= 5)
433                         dir = LEFT;
434                 case 1:
435                     if (x + X_INCR*length <= (int)Width - 70)
436                         dir = RIGHT;
437                 case 2:
438                     if (y - (Y_INCR*length) >= 5)
439                         dir = UP, interval = 40;
440                 case 3:
441                     if (y + Y_INCR*length <= (int)Height - 70)
442                         dir = DOWN, interval = 20;
443                 case 4:
444                     if (x - X_INCR*length >= 5 && y - (Y_INCR*length) >= 5)
445                         dir = (LEFT|UP);
446                 case 5:
447                     if (x + X_INCR * length <= (int)Width - 70 &&
448                         y-Y_INCR * length >= 5)
449                         dir = (RIGHT|UP);
450                 case 6:
451                     if (x - X_INCR * length >= 5 &&
452                         y + Y_INCR * length <= (int)Height - 70)
453                         dir = (LEFT|DOWN);
454                 case 7:
455                     if (x + X_INCR*length <= (int)Width - 70 &&
456                         y + Y_INCR*length <= (int)Height - 70)
457                         dir = (RIGHT|DOWN);
458             }
459         } while (!dir);
460     }
461     walk(dir);
462     --length;
463     timeout_id = XtAppAddTimeOut(app, interval, move, NULL);
464 }
465
466 static void
467 post_prompt_box(Window window)
468 {
469     int width = (Width / 3);
470     int height = font_height(font) * 6;
471     int box_x, box_y;
472
473     /* make sure the entire nose icon fits in the box */
474     if (height < 100)
475         height = 100;
476
477     if(width < 105 + font->max_bounds.width*STRING_LENGTH)
478         width = 105 + font->max_bounds.width*STRING_LENGTH;
479     box_x = (Width - width) / 2;
480     time_x = prompt_x = box_x + 105;
481
482     time_y = prompt_y = Height / 2;
483     box_y = prompt_y - 3 * font_height(font);
484
485     /* erase current guy -- text message may still exist */
486     XSetForeground(dpy, gc, Black);
487     XFillRectangle(dpy, window, gc, x, y, 64, 64);
488     talk(1); /* forcefully erase message if one is being displayed */
489     /* Clear area in middle of screen for prompt box */
490     XSetForeground(dpy, gc, White);
491     XFillRectangle(dpy, window, gc, box_x, box_y, width, height);
492
493     /* make a box that's 5 pixels thick. Then add a thin box inside it */
494     XSetForeground(dpy, gc, Black);
495     XSetLineAttributes(dpy, gc, 5, 0, 0, 0);
496     XDrawRectangle(dpy, window, gc, box_x+5, box_y+5, width-10, height-10);
497     XSetLineAttributes(dpy, gc, 0, 0, 0, 0);
498     XDrawRectangle(dpy, window, gc, box_x+12, box_y+12, width-23, height-23);
499
500     XDrawString(dpy, window, gc,
501                 prompt_x, prompt_y-font_height(font), 
502                 userprompt, strlen(userprompt));
503     XDrawString(dpy, window, gc, prompt_x, prompt_y, PROMPT, strlen(PROMPT));
504     /* set background for copyplane and DrawImageString; need reverse video */
505     XSetBackground(dpy, gc, White);
506     XCopyPlane(dpy, right0, window, gc, 0,0, 64,64,
507                box_x + 20, box_y + (height - 64)/2, 1L);
508     prompt_x += XTextWidth(font, PROMPT, strlen(PROMPT));
509     time_y += 2*font_height(font);
510 }
511
512 static void
513 RaiseWindow(Widget w, XEvent *ev, String *s, Cardinal *n)
514 {
515   Widget x;
516   if(!XtIsRealized(w))
517     return;
518   x = XtParent(w);
519   XRaiseWindow(dpy, XtWindow(x));
520 }
521
522
523 static void
524 ClearWindow(Widget w, XEvent *_event, String *_s, Cardinal *_n)
525 {
526     XExposeEvent *event = (XExposeEvent *)_event;
527     if (!XtIsRealized(w))
528         return;
529     XClearArea(dpy, XtWindow(w), event->x, event->y, 
530                event->width, event->height, False);
531     if (state == GET_PASSWD)
532         post_prompt_box(XtWindow(w));
533     if (timeout_id == 0 && event->count == 0) {
534         timeout_id = XtAppAddTimeOut(app, 1000L, move, NULL);
535         /* first grab the input focus */
536         XSetInputFocus(dpy, XtWindow(w), RevertToPointerRoot, CurrentTime);
537         /* now grab the pointer and keyboard and contrain to this window */
538         XGrabPointer(dpy, XtWindow(w), TRUE, 0, GrabModeAsync,
539              GrabModeAsync, XtWindow(w), None, CurrentTime);
540     }
541 }
542
543 static void
544 countdown(XtPointer _t, XtIntervalId *_d)
545 {
546     int *timeout = (int *)_t;
547     char buf[128];
548     time_t seconds;
549
550     if (--(*timeout) < 0) {
551         XExposeEvent event;
552         XtRemoveTimeOut(timeout_id);
553         state = IS_MOVING;
554         event.x = event.y = 0;
555         event.width = Width, event.height = Height;
556         ClearWindow(widget, (XEvent *)&event, 0, 0);
557         timeout_id = XtAppAddTimeOut(app, 200L, move, NULL);
558         return;
559     }
560     seconds = time(0) - locked_at;
561     if (seconds >= 3600)
562       snprintf(buf, sizeof(buf),
563                "Locked for %d:%02d:%02d    ",
564                (int)seconds/3600, (int)seconds/60%60, (int)seconds%60);
565     else
566       snprintf(buf, sizeof(buf),
567                "Locked for %2d:%02d    ",
568                (int)seconds/60, (int)seconds%60);
569       
570     XDrawImageString(dpy, XtWindow(widget), gc,
571         time_x, time_y, buf, strlen(buf));
572     XtAppAddTimeOut(app, 1000L, countdown, timeout);
573     return;
574 }
575
576 #ifdef KRB5
577 static int
578 verify_krb5(const char *password)
579 {
580     krb5_error_code ret;
581     krb5_ccache id;
582     krb5_boolean get_v4_tgt;
583     
584     krb5_cc_default(context, &id);
585     ret = krb5_verify_user(context,
586                            client, 
587                            id,
588                            password, 
589                            0,
590                            NULL);
591     if (ret == 0){
592 #ifdef KRB4
593         krb5_appdefault_boolean(context, "xnlock", 
594                                 krb5_principal_get_realm(context, client),
595                                 "krb4_get_tickets", FALSE, &get_v4_tgt);
596         if(get_v4_tgt) {
597             CREDENTIALS c;
598             krb5_creds mcred, cred;
599
600             krb5_make_principal(context, &mcred.server,
601                                 client->realm,
602                                 "krbtgt",
603                                 client->realm,
604                                 NULL);
605             ret = krb5_cc_retrieve_cred(context, id, 0, &mcred, &cred);
606             if(ret == 0) {
607                 ret = krb524_convert_creds_kdc_ccache(context, id, &cred, &c);
608                 if(ret == 0) 
609                     tf_setup(&c, c.pname, c.pinst);
610                 memset(&c, 0, sizeof(c));
611                 krb5_free_creds_contents(context, &cred);
612             }
613             krb5_free_principal(context, mcred.server);
614         }
615 #endif
616         if (k_hasafs())
617             krb5_afslog(context, id, NULL, NULL);
618         return 0;
619     }
620     if (ret != KRB5KRB_AP_ERR_MODIFIED)
621         krb5_warn(context, ret, "verify_krb5");
622     
623     return -1;
624 }
625 #endif
626
627 static int
628 verify(char *password)
629 {
630     /*
631      * First try with root password, if allowed.
632      */
633     if (   appres.accept_root
634         && strcmp(crypt(password, root_cpass), root_cpass) == 0)
635       return 0;
636
637     /*
638      * Password that log out user
639      */
640     if (getuid() != 0 &&
641         geteuid() != 0 &&
642         (time(0) - locked_at) > ALLOW_LOGOUT &&
643         strcmp(crypt(password, appres.logoutPasswd), appres.logoutPasswd) == 0)
644             {
645                 signal(SIGHUP, SIG_IGN);
646                 kill(-1, SIGHUP);
647                 sleep(5);
648                 /* If the X-server shut down then so will we, else
649                  * continue */
650                 signal(SIGHUP, SIG_DFL);
651             }
652
653     /*
654      * Try copy of users password.
655      */
656     if (strcmp(crypt(password, user_cpass), user_cpass) == 0)
657       return 0;
658
659     /*
660      * Try to verify as user in case password change.
661      */
662     if (unix_verify_user(login, password) == 0)
663         return 0;
664
665 #ifdef KRB5
666     /*
667      * Try to verify as user with kerberos 5.
668      */
669     if(verify_krb5(password) == 0)
670         return 0;
671 #endif
672
673 #ifdef KRB4
674     {
675         int ret;
676         /*
677          * Try to verify as user with kerberos 4.
678          */
679         ret = krb_verify_user(name, inst, realm, password,
680                               KRB_VERIFY_NOT_SECURE, NULL);
681         if (ret == KSUCCESS){
682             if (k_hasafs())
683                 krb_afslog(NULL, NULL);
684             return 0;
685         }
686         if (ret != INTK_BADPW)
687             warnx ("warning: %s",
688                    (ret < 0) ? strerror(ret) : krb_get_err_text(ret));
689     }
690 #endif
691     
692     return -1;
693 }
694
695
696 static void
697 GetPasswd(Widget w, XEvent *_event, String *_s, Cardinal *_n)
698 {
699     XKeyEvent *event = (XKeyEvent *)_event;
700     static char passwd[MAX_PASSWD_LENGTH];
701     static int cnt;
702     static int is_ctrl = XNLOCK_NOCTRL;
703     char c;
704     KeySym keysym;
705     int echolen;
706     int old_state = state;
707
708     if (event->type == ButtonPress) {
709         x = event->x, y = event->y;
710         return;
711     }
712     if (state == IS_MOVING) {
713         /* guy is running around--change to post prompt box. */
714         XtRemoveTimeOut(timeout_id);
715         state = GET_PASSWD;
716         if (appres.ignore_passwd || !strlen(user_cpass))
717             leave();
718         post_prompt_box(XtWindow(w));
719         cnt = 0;
720         time_left = 30;
721         countdown((XtPointer)&time_left, 0);
722     }
723     if (event->type == KeyRelease) {
724       keysym = XLookupKeysym(event, 0);
725       if (keysym == XK_Control_L || keysym == XK_Control_R) {
726         is_ctrl = XNLOCK_NOCTRL;
727       }
728     }
729     if (event->type != KeyPress)
730         return;
731
732     time_left = 30;
733     
734     keysym = XLookupKeysym(event, 0);
735     if (keysym == XK_Control_L || keysym == XK_Control_R) {
736       is_ctrl = XNLOCK_CTRL;
737       return;
738     }
739     if (!XLookupString(event, &c, 1, &keysym, 0))
740         return;
741     if (keysym == XK_Return || keysym == XK_Linefeed) {
742         passwd[cnt] = 0;
743         if(old_state == IS_MOVING)
744             return;
745         XtRemoveTimeOut(timeout_id);
746
747         if(verify(passwd) == 0)
748             leave();
749
750         cnt = 0;
751
752         XDrawImageString(dpy, XtWindow(widget), gc,
753             time_x, time_y, FAIL_MSG, strlen(FAIL_MSG));
754         time_left = 0;
755         timeout_id = XtAppAddTimeOut(app, 2000L, countdown, &time_left);
756         return;
757     }
758     if (keysym == XK_BackSpace || keysym == XK_Delete || keysym == XK_Left) {
759         if (cnt)
760             passwd[cnt--] = ' ';
761     } else if (keysym == XK_u && is_ctrl == XNLOCK_CTRL) {
762       while (cnt) {
763         passwd[cnt--] = ' ';
764         echolen = min(cnt, STRING_LENGTH);
765         XDrawImageString(dpy, XtWindow(w), gc,
766                     prompt_x, prompt_y, STRING, echolen);
767         XDrawImageString(dpy, XtWindow(w), gc,
768                          prompt_x + XTextWidth(font, STRING, echolen),
769                          prompt_y, SPACE_STRING, STRING_LENGTH - echolen + 1);
770       }
771     } else if (isprint((unsigned char)c)) {
772         if ((cnt + 1) >= MAX_PASSWD_LENGTH)
773             XBell(dpy, 50);
774         else
775             passwd[cnt++] = c;
776     } else
777         return;
778     echolen = min(cnt, STRING_LENGTH);
779     XDrawImageString(dpy, XtWindow(w), gc,
780         prompt_x, prompt_y, STRING, echolen);
781     XDrawImageString(dpy, XtWindow(w), gc,
782         prompt_x + XTextWidth(font, STRING, echolen),
783         prompt_y, SPACE_STRING, STRING_LENGTH - echolen +1);
784 }
785
786 #include "nose.0.left"
787 #include "nose.1.left"
788 #include "nose.0.right"
789 #include "nose.1.right"
790 #include "nose.left.front"
791 #include "nose.right.front"
792 #include "nose.front"
793 #include "nose.down"
794
795 static void
796 init_images(void)
797 {
798     static Pixmap *images[] = {
799         &left0, &left1, &right0, &right1,
800         &left_front, &right_front, &front, &down 
801     };
802     static unsigned char *bits[] = {
803         nose_0_left_bits, nose_1_left_bits, nose_0_right_bits,
804         nose_1_right_bits, nose_left_front_bits, nose_right_front_bits,
805         nose_front_bits, nose_down_bits
806     };
807     int i;
808
809     for (i = 0; i < XtNumber(images); i++)
810         if (!(*images[i] =
811                 XCreatePixmapFromBitmapData(dpy, DefaultRootWindow(dpy),
812                     (char*)(bits[i]), 64, 64, 1, 0, 1)))
813             XtError("Can't load nose images");
814 }
815
816 static void
817 talk(int force_erase)
818 {
819     int width = 0, height, Z, total = 0;
820     static int X, Y, talking;
821     static struct { int x, y, width, height; } s_rect;
822     char *p, *p2;
823     char buf[BUFSIZ], args[MAXLINES][256];
824
825     /* clear what we've written */
826     if (talking || force_erase) {
827         if (!talking)
828             return;
829         if (talking == 2) {
830             XSetForeground(dpy, gc, Black);
831             XDrawString(dpy, XtWindow(widget), gc, X, Y, words, strlen(words));
832         } else if (talking == 1) {
833             XSetForeground(dpy, gc, Black);
834             XFillRectangle(dpy, XtWindow(widget), gc, s_rect.x-5, s_rect.y-5,
835                            s_rect.width+10, s_rect.height+10);
836         }
837         talking = 0;
838         if (!force_erase)
839             timeout_id = XtAppAddTimeOut(app, 40L,
840                                          (XtTimerCallbackProc)move,
841                                          NULL);
842         return;
843     }
844     XSetForeground(dpy, gc, White);
845     talking = 1;
846     walk(FRONT);
847     strlcpy (buf, words, sizeof(buf));
848     p = buf;
849
850     /* possibly avoid a lot of work here
851      * if no CR or only one, then just print the line
852      */
853     if (!(p2 = strchr(p, '\n')) || !p2[1]) {
854         int w;
855
856         if (p2)
857             *p2 = 0;
858         w = XTextWidth(font, words, strlen(words));
859         X = x + 32 - w/2;
860         Y = y - 5 - font_height(font);
861         /* give us a nice 5 pixel margin */
862         if (X < 5)
863             X = 5;
864         else if (X + w + 15 > (int)Width + 5)
865             X = Width - w - 5;
866         if (Y < 5)
867             Y = y + 64 + 5 + font_height(font);
868         XDrawString(dpy, XtWindow(widget), gc, X, Y, words, strlen(words));
869         timeout_id = XtAppAddTimeOut(app, 5000L, (XtTimerCallbackProc)talk, 
870                                      NULL);
871         talking++;
872         return;
873     }
874
875     /* p2 now points to the first '\n' */
876     for (height = 0; p; height++) {
877         int w;
878         *p2 = 0;
879         if ((w = XTextWidth(font, p, p2 - p)) > width)
880             width = w;
881         total += p2 - p; /* total chars; count to determine reading time */
882         strlcpy(args[height], p, sizeof(args[height]));
883         if (height == MAXLINES - 1) {
884             puts("Message too long!");
885             break;
886         }
887         p = p2+1;
888         if (!(p2 = strchr(p, '\n')))
889             break;
890     }
891     height++;
892
893     /* Figure out the height and width in pixels (height, width) extend
894      * the new box by 15 pixels on the sides (30 total) top and bottom.
895      */
896     s_rect.width = width + 30;
897     s_rect.height = height * font_height(font) + 30;
898     if (x - s_rect.width - 10 < 5)
899         s_rect.x = 5;
900     else
901         if ((s_rect.x = x+32-(s_rect.width+15)/2)
902                                          + s_rect.width+15 > (int)Width-5)
903             s_rect.x = Width - 15 - s_rect.width;
904     if (y - s_rect.height - 10 < 5)
905         s_rect.y = y + 64 + 5;
906     else
907         s_rect.y = y - 5 - s_rect.height;
908
909     XSetForeground(dpy, gc, White);
910     XFillRectangle(dpy, XtWindow(widget), gc,
911        s_rect.x-5, s_rect.y-5, s_rect.width+10, s_rect.height+10);
912
913     /* make a box that's 5 pixels thick. Then add a thin box inside it */
914     XSetForeground(dpy, gc, Black);
915     XSetLineAttributes(dpy, gc, 5, 0, 0, 0);
916     XDrawRectangle(dpy, XtWindow(widget), gc,
917                    s_rect.x, s_rect.y, s_rect.width-1, s_rect.height-1);
918     XSetLineAttributes(dpy, gc, 0, 0, 0, 0);
919     XDrawRectangle(dpy, XtWindow(widget), gc,
920                    s_rect.x + 7, s_rect.y + 7, s_rect.width - 15, 
921                    s_rect.height - 15);
922
923     X = 15;
924     Y = 15 + font_height(font);
925
926     /* now print each string in reverse order (start at bottom of box) */
927     for (Z = 0; Z < height; Z++) {
928         XDrawString(dpy, XtWindow(widget), gc, s_rect.x+X, s_rect.y+Y,
929             args[Z], strlen(args[Z]));
930         Y += font_height(font);
931     }
932     timeout_id = XtAppAddTimeOut(app, (total/15) * 1000, 
933                                  (XtTimerCallbackProc)talk, NULL);
934 }
935
936 static unsigned long
937 look(void)
938 {
939     XSetForeground(dpy, gc, White);
940     XSetBackground(dpy, gc, Black);
941     if (my_random() % 3) {
942         XCopyPlane(dpy, (my_random() & 1)? down : front, XtWindow(widget), gc,
943             0, 0, 64,64, x, y, 1L);
944         return 1000L;
945     }
946     if (!(my_random() % 5))
947         return 0;
948     if (my_random() % 3) {
949         XCopyPlane(dpy, (my_random() & 1)? left_front : right_front,
950             XtWindow(widget), gc, 0, 0, 64,64, x, y, 1L);
951         return 1000L;
952     }
953     if (!(my_random() % 5))
954         return 0;
955     XCopyPlane(dpy, (my_random() & 1)? left0 : right0, XtWindow(widget), gc,
956         0, 0, 64,64, x, y, 1L);
957     return 1000L;
958 }
959
960 int
961 main (int argc, char **argv)
962 {
963     int i;
964     Widget override;
965     XGCValues gcvalues;
966
967     setprogname (argv[0]);
968
969     /*
970      * Must be setuid root to read /etc/shadow, copy encrypted
971      * passwords here and then switch to sane uid.
972      */
973     {
974       struct passwd *pw;
975       uid_t uid = getuid();
976       if (!(pw = k_getpwuid(0)))
977         errx (1, "can't get root's passwd!");
978       strlcpy(root_cpass, pw->pw_passwd, sizeof(root_cpass));
979
980       if (!(pw = k_getpwuid(uid)))
981         errx (1, "Can't get your password entry!");
982       strlcpy(user_cpass, pw->pw_passwd, sizeof(user_cpass));
983       setuid(uid);
984       if (uid != 0 && setuid(0) != -1) {
985         fprintf(stderr, "Failed to drop privileges!\n");
986         exit(1);
987       }
988       /* Now we're no longer running setuid root. */
989       strlcpy(login, pw->pw_name, sizeof(login));
990     }
991
992 #if defined(HAVE_SRANDOMDEV)
993     srandomdev();
994 #elif defined(HAVE_RANDOM)
995     srandom(time(NULL));
996 #else
997     srand (time(NULL));
998 #endif
999     for (i = 0; i < STRING_LENGTH; i++)
1000         STRING[i] = ((unsigned long)my_random() % ('~' - ' ')) + ' ';
1001
1002     locked_at = time(0);
1003
1004     snprintf(userprompt, sizeof(userprompt), "User: %s", login);
1005 #ifdef KRB4
1006     krb_get_default_principal(name, inst, realm);
1007     snprintf(userprompt, sizeof(userprompt), "User: %s", 
1008              krb_unparse_name_long(name, inst, realm));
1009 #endif
1010 #ifdef KRB5
1011     {
1012         krb5_error_code ret;
1013         char *str;
1014
1015         ret = krb5_init_context(&context);
1016         if (ret)
1017             errx (1, "krb5_init_context failed: %d", ret);
1018         krb5_get_default_principal(context, &client);
1019         krb5_unparse_name(context, client, &str);
1020         snprintf(userprompt, sizeof(userprompt), "User: %s", str);
1021         free(str);
1022     }
1023 #endif
1024
1025     override = XtVaAppInitialize(&app, "XNlock", options, XtNumber(options),
1026                                  (Cardinal*)&argc, argv, NULL, 
1027                                  XtNoverrideRedirect, True, 
1028                                  NULL);
1029     
1030     XtVaGetApplicationResources(override,(XtPointer)&appres,
1031                                 resources,XtNumber(resources),
1032                                 NULL);
1033     /* the background is black and the little guy is white */
1034     Black = appres.bg;
1035     White = appres.fg;
1036
1037     if (appres.destroytickets) {
1038 #ifdef KRB4
1039         int fd;
1040
1041         dest_tkt();             /* Nuke old ticket file */
1042                                 /* but keep a place holder */
1043         fd = open (TKT_FILE, O_WRONLY | O_CREAT | O_EXCL, 0600);
1044         if (fd >= 0)
1045             close (fd);
1046 #endif
1047     }
1048
1049     dpy = XtDisplay(override);
1050     
1051     if (dpy == 0)
1052       errx (1, "Error: Can't open display");
1053
1054     Width = DisplayWidth(dpy, DefaultScreen(dpy)) + 2;
1055     Height = DisplayHeight(dpy, DefaultScreen(dpy)) + 2;
1056     
1057     for(i = 0; i < ScreenCount(dpy); i++){
1058         Widget shell, core;
1059
1060         struct xxx{
1061             Pixel bg;
1062         }res;
1063         
1064         XtResource Res[] = {
1065             { XtNbackground, XtCBackground, XtRPixel, sizeof(Pixel),
1066               XtOffsetOf(struct xxx, bg), XtRString, "black" }
1067         };
1068
1069         if(i == DefaultScreen(dpy))
1070             continue;
1071       
1072         shell = XtVaAppCreateShell(NULL,NULL, applicationShellWidgetClass, dpy, 
1073                                    XtNscreen, ScreenOfDisplay(dpy, i), 
1074                                    XtNoverrideRedirect, True, 
1075                                    XtNx, -1, 
1076                                    XtNy, -1,
1077                                    NULL);
1078       
1079         XtVaGetApplicationResources(shell, (XtPointer)&res, 
1080                                     Res, XtNumber(Res),
1081                                     NULL);
1082
1083         core = XtVaCreateManagedWidget("_foo", widgetClass, shell,
1084                                        XtNwidth, DisplayWidth(dpy, i),
1085                                        XtNheight, DisplayHeight(dpy, i),
1086                                        XtNbackground, res.bg, 
1087                                        NULL);
1088         XtRealizeWidget(shell);
1089     }
1090
1091     widget = XtVaCreateManagedWidget("_foo", widgetClass, override,
1092                                      XtNwidth,  Width,
1093                                      XtNheight, Height,
1094                                      XtNbackground, Black, 
1095                                      NULL);
1096
1097     init_words(--argc, ++argv);
1098     init_images();
1099
1100     gcvalues.foreground = Black;
1101     gcvalues.background = White;
1102
1103
1104     font = appres.font;
1105     gcvalues.font = font->fid;
1106     gcvalues.graphics_exposures = False;
1107     gc = XCreateGC(dpy, DefaultRootWindow(dpy),
1108         GCForeground | GCBackground | GCGraphicsExposures | GCFont,
1109         &gcvalues);
1110
1111     x = Width / 2;
1112     y = Height / 2;
1113     srand (time(0));
1114     state = IS_MOVING;
1115
1116     {
1117         static XtActionsRec actions[] = {
1118             { "ClearWindow",    ClearWindow  },
1119             { "GetPasswd",      GetPasswd    },
1120             { "RaiseWindow",    RaiseWindow  },
1121         };
1122         XtAppAddActions(app, actions, XtNumber(actions));
1123         XtOverrideTranslations(widget,
1124                XtParseTranslationTable(
1125                                        "<Expose>:       ClearWindow()   \n"
1126                                        "<BtnDown>:      GetPasswd()     \n"
1127                                        "<Visible>: RaiseWindow() \n"
1128                                        "<KeyRelease>:  GetPasswd()     \n"
1129                                        "<KeyPress>:     GetPasswd()"));
1130     }
1131
1132     XtRealizeWidget(override);
1133     if((i = XGrabPointer(dpy, XtWindow(widget), True, 0, GrabModeAsync,
1134                          GrabModeAsync, XtWindow(widget), 
1135                          None, CurrentTime)) != 0) 
1136         errx(1, "Failed to grab pointer (%d)", i);
1137         
1138     if((i = XGrabKeyboard(dpy, XtWindow(widget), True, GrabModeAsync,
1139                           GrabModeAsync, CurrentTime)) != 0)
1140         errx(1, "Failed to grab keyboard (%d)", i);
1141     ScreenSaver(1);
1142     XtAppMainLoop(app);
1143     exit(0);
1144 }
1145