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