This commit was manufactured by cvs2svn to create tag
[heimdal.git] / appl / xnlock / xnlock.c
blobade0b8afe9e4924783fc2850b4f3192ba3579188
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$");
12 #endif
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
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
50 #include <roken.h>
51 #include <err.h>
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
65 #define font_height(font) (font->ascent + font->descent)
67 static char *SPACE_STRING = " ";
68 static char STRING[] = "****************";
70 #define STRING_LENGTH (sizeof(STRING))
71 #define MAX_PASSWD_LENGTH 256
72 /* (sizeof(STRING)) */
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
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;
103 #define MAXLINES 40
105 #define IS_MOVING 1
106 #define GET_PASSWD 2
107 static int state; /* indicates states: walking or getting passwd */
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;
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;
125 static XtResource resources[] = {
126 { XtNbackground, XtCBackground, XtRPixel, sizeof(Pixel),
127 XtOffsetOf(struct appres_t, bg), XtRString, "black" },
129 { XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
130 XtOffsetOf(struct appres_t, fg), XtRString, "white" },
132 { XtNfont, XtCFont, XtRFontStruct, sizeof (XFontStruct *),
133 XtOffsetOf(struct appres_t, font),
134 XtRString, "-*-new century schoolbook-*-*-*-18-*" },
136 { "ignorePasswd", "IgnorePasswd", XtRBoolean, sizeof(Boolean),
137 XtOffsetOf(struct appres_t,ignore_passwd),XtRImmediate,(XtPointer)False },
139 { "acceptRootPasswd", "AcceptRootPasswd", XtRBoolean, sizeof(Boolean),
140 XtOffsetOf(struct appres_t, accept_root), XtRImmediate, (XtPointer)True },
142 { "text", "Text", XtRString, sizeof(String),
143 XtOffsetOf(struct appres_t, text), XtRString, "I'm out running around." },
145 { "program", "Program", XtRString, sizeof(String),
146 XtOffsetOf(struct appres_t, text_prog), XtRImmediate, NULL },
148 { "file", "File", XtRString, sizeof(String),
149 XtOffsetOf(struct appres_t,file), XtRImmediate, NULL },
151 { "logoutPasswd", "logoutPasswd", XtRString, sizeof(String),
152 XtOffsetOf(struct appres_t, logoutPasswd), XtRString, LOGOUT_PASSWD },
154 { "noScreenSaver", "NoScreenSaver", XtRBoolean, sizeof(Boolean),
155 XtOffsetOf(struct appres_t,no_screensaver), XtRImmediate, (XtPointer)True },
157 { "destroyTickets", "DestroyTickets", XtRBoolean, sizeof(Boolean),
158 XtOffsetOf(struct appres_t,destroytickets), XtRImmediate, (XtPointer)True },
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" },
174 static char*
175 get_words(void)
177 FILE *pp = NULL;
178 static char buf[512];
179 long n;
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;
187 n = fread(buf, 1, sizeof(buf) - 1, pp);
188 buf[n] = 0;
189 pclose(pp);
190 return buf;
192 if (appres.file) {
193 pp = fopen(appres.file, "r");
194 if (!pp) {
195 warn("fopen %s", appres.file);
196 return appres.text;
198 n = fread(buf, 1, sizeof(buf) - 1, pp);
199 buf[n] = 0;
200 fclose(pp);
201 return buf;
204 return appres.text;
207 static void
208 usage(void)
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);
225 static void
226 init_words (int argc, char **argv)
228 int i = 0;
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();
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");
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);
272 static void
273 ScreenSaver(int save)
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);
287 /* Forward decls necessary */
288 static void talk(int force_erase);
289 static unsigned long look(void);
291 static int
292 zrefresh(void)
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;
307 return 0;
310 static void
311 leave(void)
313 XUngrabPointer(dpy, CurrentTime);
314 XUngrabKeyboard(dpy, CurrentTime);
315 ScreenSaver(0);
316 XCloseDisplay(dpy);
317 zrefresh();
318 exit(0);
321 static void
322 walk(int dir)
324 int incr = 0;
325 static int lastdir;
326 static int up = 1;
327 static Pixmap frame;
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;
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.
344 XCopyPlane(dpy, frame, XtWindow(widget), gc, 0, 0, 64,64, x, y, 1L);
345 XFlush(dpy);
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;
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);
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);
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);
383 lastdir = dir;
386 static long
387 my_random (void)
389 #ifdef HAVE_RANDOM
390 return random();
391 #else
392 return rand();
393 #endif
396 static int
397 think(void)
399 if (my_random() & 1)
400 walk(FRONT);
401 if (my_random() & 1) {
402 words = get_words();
403 return 1;
405 return 0;
408 static void
409 move(XtPointer _p, XtIntervalId *_id)
411 static int length, dir;
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;
420 if (!(my_random() % 3) && (interval = look())) {
421 timeout_id = XtAppAddTimeOut(app, interval, move, NULL);
422 return;
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);
459 } while (!dir);
461 walk(dir);
462 --length;
463 timeout_id = XtAppAddTimeOut(app, interval, move, NULL);
466 static void
467 post_prompt_box(Window window)
469 int width = (Width / 3);
470 int height = font_height(font) * 6;
471 int box_x, box_y;
473 /* make sure the entire nose icon fits in the box */
474 if (height < 100)
475 height = 100;
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;
482 time_y = prompt_y = Height / 2;
483 box_y = prompt_y - 3 * font_height(font);
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);
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);
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);
512 static void
513 RaiseWindow(Widget w, XEvent *ev, String *s, Cardinal *n)
515 Widget x;
516 if(!XtIsRealized(w))
517 return;
518 x = XtParent(w);
519 XRaiseWindow(dpy, XtWindow(x));
523 static void
524 ClearWindow(Widget w, XEvent *_event, String *_s, Cardinal *_n)
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);
543 static void
544 countdown(XtPointer _t, XtIntervalId *_d)
546 int *timeout = (int *)_t;
547 char buf[128];
548 time_t seconds;
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;
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);
570 XDrawImageString(dpy, XtWindow(widget), gc,
571 time_x, time_y, buf, strlen(buf));
572 XtAppAddTimeOut(app, 1000L, countdown, timeout);
573 return;
576 #ifdef KRB5
577 static int
578 verify_krb5(const char *password)
580 krb5_error_code ret;
581 krb5_ccache id;
582 krb5_boolean get_v4_tgt;
584 krb5_cc_default(context, &id);
585 ret = krb5_verify_user(context,
586 client,
588 password,
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;
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);
613 krb5_free_principal(context, mcred.server);
615 #endif
616 if (k_hasafs())
617 krb5_afslog(context, id, NULL, NULL);
618 return 0;
620 if (ret != KRB5KRB_AP_ERR_MODIFIED)
621 krb5_warn(context, ret, "verify_krb5");
623 return -1;
625 #endif
627 static int
628 verify(char *password)
631 * First try with root password, if allowed.
633 if ( appres.accept_root
634 && strcmp(crypt(password, root_cpass), root_cpass) == 0)
635 return 0;
638 * Password that log out user
640 if (getuid() != 0 &&
641 geteuid() != 0 &&
642 (time(0) - locked_at) > ALLOW_LOGOUT &&
643 strcmp(crypt(password, appres.logoutPasswd), appres.logoutPasswd) == 0)
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);
654 * Try copy of users password.
656 if (strcmp(crypt(password, user_cpass), user_cpass) == 0)
657 return 0;
660 * Try to verify as user in case password change.
662 if (unix_verify_user(login, password) == 0)
663 return 0;
665 #ifdef KRB5
667 * Try to verify as user with kerberos 5.
669 if(verify_krb5(password) == 0)
670 return 0;
671 #endif
673 #ifdef KRB4
675 int ret;
677 * Try to verify as user with kerberos 4.
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;
686 if (ret != INTK_BADPW)
687 warnx ("warning: %s",
688 (ret < 0) ? strerror(ret) : krb_get_err_text(ret));
690 #endif
692 return -1;
696 static void
697 GetPasswd(Widget w, XEvent *_event, String *_s, Cardinal *_n)
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;
708 if (event->type == ButtonPress) {
709 x = event->x, y = event->y;
710 return;
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);
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;
729 if (event->type != KeyPress)
730 return;
732 time_left = 30;
734 keysym = XLookupKeysym(event, 0);
735 if (keysym == XK_Control_L || keysym == XK_Control_R) {
736 is_ctrl = XNLOCK_CTRL;
737 return;
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);
747 if(verify(passwd) == 0)
748 leave();
750 cnt = 0;
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;
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);
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);
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"
795 static void
796 init_images(void)
798 static Pixmap *images[] = {
799 &left0, &left1, &right0, &right1,
800 &left_front, &right_front, &front, &down
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
807 int i;
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");
816 static void
817 talk(int force_erase)
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];
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);
837 talking = 0;
838 if (!force_erase)
839 timeout_id = XtAppAddTimeOut(app, 40L,
840 (XtTimerCallbackProc)move,
841 NULL);
842 return;
844 XSetForeground(dpy, gc, White);
845 talking = 1;
846 walk(FRONT);
847 strlcpy (buf, words, sizeof(buf));
848 p = buf;
850 /* possibly avoid a lot of work here
851 * if no CR or only one, then just print the line
853 if (!(p2 = strchr(p, '\n')) || !p2[1]) {
854 int w;
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;
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;
887 p = p2+1;
888 if (!(p2 = strchr(p, '\n')))
889 break;
891 height++;
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.
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;
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);
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);
923 X = 15;
924 Y = 15 + font_height(font);
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);
932 timeout_id = XtAppAddTimeOut(app, (total/15) * 1000,
933 (XtTimerCallbackProc)talk, NULL);
936 static unsigned long
937 look(void)
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;
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;
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;
961 main (int argc, char **argv)
963 int i;
964 Widget override;
965 XGCValues gcvalues;
967 setprogname (argv[0]);
970 * Must be setuid root to read /etc/shadow, copy encrypted
971 * passwords here and then switch to sane uid.
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));
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);
988 /* Now we're no longer running setuid root. */
989 strlcpy(login, pw->pw_name, sizeof(login));
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() % ('~' - ' ')) + ' ';
1002 locked_at = time(0);
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
1012 krb5_error_code ret;
1013 char *str;
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);
1023 #endif
1025 override = XtVaAppInitialize(&app, "XNlock", options, XtNumber(options),
1026 (Cardinal*)&argc, argv, NULL,
1027 XtNoverrideRedirect, True,
1028 NULL);
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;
1037 if (appres.destroytickets) {
1038 #ifdef KRB4
1039 int fd;
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
1049 dpy = XtDisplay(override);
1051 if (dpy == 0)
1052 errx (1, "Error: Can't open display");
1054 Width = DisplayWidth(dpy, DefaultScreen(dpy)) + 2;
1055 Height = DisplayHeight(dpy, DefaultScreen(dpy)) + 2;
1057 for(i = 0; i < ScreenCount(dpy); i++){
1058 Widget shell, core;
1060 struct xxx{
1061 Pixel bg;
1062 }res;
1064 XtResource Res[] = {
1065 { XtNbackground, XtCBackground, XtRPixel, sizeof(Pixel),
1066 XtOffsetOf(struct xxx, bg), XtRString, "black" }
1069 if(i == DefaultScreen(dpy))
1070 continue;
1072 shell = XtVaAppCreateShell(NULL,NULL, applicationShellWidgetClass, dpy,
1073 XtNscreen, ScreenOfDisplay(dpy, i),
1074 XtNoverrideRedirect, True,
1075 XtNx, -1,
1076 XtNy, -1,
1077 NULL);
1079 XtVaGetApplicationResources(shell, (XtPointer)&res,
1080 Res, XtNumber(Res),
1081 NULL);
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);
1091 widget = XtVaCreateManagedWidget("_foo", widgetClass, override,
1092 XtNwidth, Width,
1093 XtNheight, Height,
1094 XtNbackground, Black,
1095 NULL);
1097 init_words(--argc, ++argv);
1098 init_images();
1100 gcvalues.foreground = Black;
1101 gcvalues.background = White;
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);
1111 x = Width / 2;
1112 y = Height / 2;
1113 srand (time(0));
1114 state = IS_MOVING;
1117 static XtActionsRec actions[] = {
1118 { "ClearWindow", ClearWindow },
1119 { "GetPasswd", GetPasswd },
1120 { "RaiseWindow", RaiseWindow },
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()"));
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);
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);