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