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