/* xlock.c - lock a VaxStation
 *
 *	$Author: holtzman $
 *	$Date: 86/09/08 21:19:26 $
 *	$Locker: holtzman $
 *	$Log:	xlock.c,v $
 * Revision 1.5  86/09/08  21:19:26  holtzman
 * Wimp SIGCONT stuff merged in.
 * 
 * Revision 1.4  85/11/05  17:58:52  holtzman
 * Made -l work
 * 
 * Revision 1.3  85/09/30  09:23:07  holtzman
 * Added -l (logout) option
 * 
 * Revision 1.1  85/08/11  15:53:52  holtzman
 * Initial revision
 * 
*/
#include <stdio.h>
#include <ctype.h>
#include <X/Xlib.h>
#include <pwd.h>
#include <signal.h>
#include <sys/time.h>

#define	FAILURE	0			    /*Failure return code */
#define AllEvents 0xffff		    /*Select all events  */
#include "mask.h"			    /* contains locked cursor mask */
#include "curs.h"			    /* contains locked cursor form */
#include "openm.h"			    /* contains unlocked cursor mask */
#include "openc.h"			    /* contains unlocked cursor form */

#define BIGFONT "sbdr66"		    /* font to use for large type */
#define SMALLFONT "kilter28"		    /* font to use for rest of type */

/* command line options */
int pflag = 0;				    /* Input password */
int lflag = 0;				    /* logout out if lock expires */
int luser = 0;				    /* call asocial people names */
int timeline;				    /* line which expire time is on */
int refresh;				    /* need to refresh flag */
int mousex, mousey;			    /* initial mouse coords */
long timer;				    /* unlock time */
int ref_delay;				    /* refresh delay count */
struct passwd *me;			    /* /etc/passwd entry */
Window w0;				    /* main window */
Window w1;				    /* dummy window id */
Font bfont;				    /* font id for BIGFONT */
Font sfont;				    /* font id for SMALLFONT */

long time();
int done(), update(), dummy(), noop(), abuse();

main(argc, argv)
int argc;
char *argv[];
{
	char *getlogin(), *index();
	long expire = 15*60;
	char c, *pass, salt[3];
	int code, num, i;
	struct itimerval time1, time2;

	Bitmap curs,mask,openc,openm;
	Cursor cursor, open;
	XEvent event;
	FontInfo *bfinfo, *sfinfo;
	Window *children;
	WindowInfo *info;
	XKeyPressedEvent *key;
	extern KeyMapEntry StdMap[];
	int myid;

        for (i = 1; i < argc; ++i) {
		 switch (argv[i][0]) {
			case '-':
			  switch (argv[i][1]) {
				 case 'l':		    /* logout */
				   lflag++;
				   break;
				 default:
				   printf("Usage: %s [-l]\n", argv[0]);
				   exit(1);
			  }
			  break;
			default:
			  printf("Usage: %s [-l]\n", argv[0]);
			  exit(1);
		 }
	}

	/* get the /etc/password entry for this user */

	if ((me = getpwuid(getuid())) == NULL)
	    Error("can't find password entry");

	/* set the crypt seed (salt) */
	strncpy(salt, me->pw_passwd, 2);

	/* open the user's display */
	if (XOpenDisplay() == NULL) 
		Error("unable to open display");

	/* check if another xlock is running */
	/* we should check XQueryTree return code, but */
	/* there is currently in a bug.  It is invalid */
	XQueryTree(RootWindow, &w1, &num, &children);
	for (i = 0; i < num; i++) {
		 char *name;
		 XFetchName(children[i], &name);
		 if (!strcmp(name, "xlock")) {
			  printf("Sorry, xlock already running on that display\n");
			  exit(0);
		 }
		 free(name);
	}
	free(children);

	/* open the user's big font */
	if ((bfinfo = XOpenFont(BIGFONT)) == FAILURE)
	  Error("Cant open big font");
	
	/* set the font id */
	bfont = bfinfo->id;

	/* open the user's small font */
	if ((sfinfo = XOpenFont(SMALLFONT)) == FAILURE)
	  Error("Cant open small font");

	/* set the font id */
	sfont = sfinfo->id;

	/* create the main window */
	w0 = XCreateWindow(RootWindow, 30, 30, 900, 800,
			   2, BlackPixmap, WhitePixmap);

	if (w0 == FAILURE) Error("bad window");
	
	/* give the window a name */
	XStoreName(w0,"xlock");

	/* Set up a resize hint, 0x0 is minimum size, */
	/* 1 is the x increment, 1 is the y increment*/
	XSetResizeHint(w0, 0, 0, 1, 1);

	/* create the open lock cursor */
	/* by first making cursor and mask bitmaps */
	/* and then turning it into a cursor*/
	openc = XStoreBitmap(openc_width,openc_height,openc_bits);
	openm = XStoreBitmap(openm_width,openm_height,openm_bits);
	open = XStoreCursor(openc,openm,8,8,WhitePixel,BlackPixel,GXcopy);

	/* make it the cursor for our window */
	XDefineCursor(w0, open);

	/* Window won't be displayed 'till we map  it */
	XMapWindow(w0);

	/* Clear the window */
	XClear(w0);

	/* Store away the mouse pos, so we can restore it when done */
	XQueryMouse(RootWindow, &mousex, &mousey, &w1);

	/* Make sure the mouse is in our window */
	XWarpMouse(w0, 400, 400);

	/* Select the type of events we are interested in */
	XSelectInput(w0, ButtonPressed|KeyPressed|ExposeWindow|ExposeRegion);

	/* and print our banner screen  */
	banner(w0, sfont);

	while (1) {				/* Reader loop */
	  XNextEvent(&event);
	  
	  if (event.type == KeyPressed) {	/* check for keypress */
	    key = (XKeyPressedEvent *)&event;
	    switch (key->detail) {
	   case 125:				/* 125 = "bar" */
	      goto lock;			/* so lock */
	      break;
	    case 124:				/* pressed "help" */
	      helpkey(w0,sfont);		/* so scream */
	      break;
	    case 138:				/* pressed e1 */
	      done();				/* which means cancel */
	    case 177:
	    case 174:
	    case 175:
	    case 176:				/* modifier keys */
	    case 2224:				/* (like shift, ctrl, etc) */
	      break;				/* so do nothing */
	    default:				/* On any other keey */
	      XFeep(0);				/* beep */
	    }
	  }
	  else
	    if ((event.type == ExposeWindow) || (event.type == ExposeRegion))
	      {
		banner(w0,sfont);		/* if exosure, refresh */
	      }
	  else
	    XFeep(0);				/* any other even, beep */
	}
lock:						/* lock the terminal */
	timer = time(0) + expire;		/* set the expiration timer */

	showlocked(me, w0, sfont, bfont, timer);/* put up the locked mesg */
	
	XFlush();				/* flush output */

	/* set up the locked cursor by */
	/* creating a cursor bitmap and a mask bitmap and */
	/* melding them into a cursor */
	curs = XStoreBitmap(curs_width,curs_height,curs_bits);
	mask = XStoreBitmap(mask_width,mask_height,mask_bits);
	cursor = XStoreCursor(curs,mask,8,8,WhitePixel,BlackPixel,GXcopy);

	XSelectInput(w0,0x0000);		/* select no events */

	XFocusKeyboard(w0);			/* sieze keyboard */

	/* sieze mouse, setting cursor to the locked cursor */
	/* and simultaneously setting event mask */
	XGrabMouse(w0,cursor, ButtonPressed);

	/* once again select the input we are interested in */
	XSelectInput(w0, ButtonPressed | KeyPressed |
		     ExposeWindow | ExposeRegion | ExposeCopy |
		     FocusChange | UnmapWindow);

	signal(SIGHUP, done);			/* if HUP received, we */
						/* should unlock*/

	/* be on the defense for lusers who try to cheat the timer */
/*	myid = getpid();
	if (fork() == 0) {
frk:		 if (fork() <= 0) {
			  signal(SIGALRM, noop);
			  time1.it_value.tv_sec = 0;
			  time1.it_value.tv_usec = 10000;

			  time1.it_interval.tv_sec = 0;
			  time1.it_interval.tv_usec = 0;

			  setitimer(ITIMER_REAL, &time1, &time2);
			  sigpause(0);
			  if (kill(myid, SIGCONT) == 0) goto frk;
		 }
		 exit(0);
	}*/
	signal(SIGCONT, abuse);

	/* set up a timer and an interupt service */
	/* the interupt service will handle screen refresh */
	signal(SIGALRM, dummy);

	time1.it_value.tv_sec = 0;		/* first timeout in .1 sec */
	time1.it_value.tv_usec = 100000;

	time1.it_interval.tv_sec = 0;		/* and more every .1 sec */
	time1.it_interval.tv_usec = 100000;

	setitimer(ITIMER_REAL, &time1, &time2);	/* activate timer */

	pass = NULL;				/* initialized pswd attempt */

	while (1) {				/* main locked loop */

		 while (!XPending())		/* wait for an event */
		    sigpause(0);		/* in .1s bursts */

		 XNextEvent(&event);		/* get the event */

		 switch (event.type) {		/* and handle it */
			case ButtonPressed:	/* buttons just feep */
			  XFeep(0);
			  break;
			case ExposeRegion:
			case ExposeWindow:
			case ExposeCopy:	/* exposures flag refresh */
			  refresh++;		/* which is done in update */
			  break;		/* which as called by dummy */
			case FocusChange:	/* keep a grip on keyboard  */
			  XFocusKeyboard(w0);
			  break;
			case UnmapWindow:	/* keep our window displayed */
			  XMapWindow(w0);
			  XFocusKeyboard(w0);
			  XFlush();
			  break;
			case KeyPressed:	/* password char */
			  /* decode keypress */
			  key = (XKeyPressedEvent *)&event;
			  code = key->detail;
			  c = StdMap[code & ValueMask][KeyState(code)];
			  /* if cr or lf we should validate password */
			  if (c == '\n' || c == '\r')
			     /* validate within fork because */
			     /* crypt takes much time */
			     if (fork() == 0) {	
				      /* make sure we don't try to refresh */
				      /* screen also */
				      signal(SIGALRM, SIG_IGN);
				      /* if good HUP parent */
				      /* otherwise beep and exit */
				      if (strcmp(me->pw_passwd,
						 crypt(pass, salt)) == 0)
					 kill(getppid(),SIGHUP);
				      else {
					       XFeep(0);
					       XFlush();
				      }
				      exit(0);
			     }
			     /* Null out password attempt after handing */
			     /* it to child */
			     else {
				      free(pass);
				      pass = NULL;
			     }
			  /* not a cr or lf so tack char onto string */
			  /* creating a larger string, as we need it */
			  else {
				   char *temp;
				   if (pass != NULL) {
					    temp = (char *)calloc(strlen(pass) + 2, 1);
					    strcpy(temp,pass);
					    temp[strlen(pass)] = c;
					    free(pass);
					    pass = temp;
				   }
				   else {
					    pass = (char *)calloc(2,1);
					    pass[0] = c;
					    pass[1] = '\0';
				   }
			  }
			  break;
			default:
			  break;
		 }
	}
}
/* Error - print out and error message, and exit */
Error(arg)
char	*arg;
{
	printf("%s\n",arg);
	exit(1);
}
/* helpkey - chide user for typing the "help" key */
helpkey(window, font)
Window window;
Font font;
{
  XFeep(0);

  XText(window, 1, 1,
	"Press the bar NEXT TO the 'Help' key to lock.",45,
	font, BlackPixel, WhitePixel);
}
/* banner - display the xlock banner page */
/*          present them with the choice of whether or not to lock */
banner(window, font)
Window window;					/* window to write in */
Font font;					/* font to use */
{
  XClear(window);				/* start with an empty wind. */

  XText(window, 1, 1,				/* display the text */
	"Press the bar next to the 'Help' key to lock.", 45,
	font, BlackPixel, WhitePixel);

  XText(window, 1, 50,				/* two lines worth */
	"Press the E1 key to quit.",25,
	font, BlackPixel, WhitePixel);
}
/* showlocked - forms the screen of a locked terminal */
showlocked(me, window, font, bfont, timer)
struct passwd *me;				/* user's /etc/passwd entry */
Window *window;					/* window to write in */
Font font, bfont;				/* fonts to use */
long timer;					/* expire time */
{
     char *comma, *username;
     int line = 0;

     XClear(window);				/* start with window empty */
     line = 5;					/* start on the fifth line */
  
     /* put up text */
     /* all text is centered in window */
     writecenter(window, "This terminal has been", font, line);
     line += 30;

     line += 30;
     writecenter(window, "Locked", bfont, line);
     line += 96;

     writecenter(window, "by", font, line);
     line += 60;

     /* did he ever try to suspend the lock? */
     if (luser) {
	      writecenter(window, "an anti-social lusing wimp named",
			  font, line - 30);
     }

     /* format users real name */
     username = me->pw_gecos;

     if ((comma = index(username, ',')) != NULL)
       *comma = '\0';
     
     if (strlen(username) == 0)
       username = me->pw_name;

     writecenter(window, username, font, line);
     line += 30;

     /* also display machine:userid */
     {
       char *temp, machine[17], *ab;
       int len;

       gethostname(machine, 16);

       machine[16] = '\0';

       /* lowercasify hostname for personal taste */
       for (ab = machine ; *ab != '\0'; ++ab)
	  if (isupper(*ab))
	     *ab = tolower(*ab);

       ab = machine;

       /* we know we are at mit, so remove it from head of the hostname */
       if (ab[0] == 'm' && ab[1] == 'i' && ab[2] == 't' && ab[3] == '-')
	  ab += 4;

       len = strlen(me->pw_name)+strlen("(:)") + strlen(ab) + 1;

       temp = (char *)calloc(len,1);

       sprintf(temp, "(%s:%s)",ab , me->pw_name);

       writecenter(window, temp, font, line);
       line += 30;

       free(temp);

     }


     /* let the world know where the expire time will wind up */
     timeline = line +=200;

     /* display the timer line */
     writetime(timer - time(0));
     line += 60;

     if (!luser)
	writecenter(window,
		    "Another Student Information Processing Board Product",
		    font, line);
     line += 30;

     line += 60;
     writecenter(window, "Type your login password and <Return> to unlock.",
		 font, line);
     line += 30;
}

/* writecenter - put text centered on a particular line in a window */
writecenter(window, text, font, line)
Window window;					/* window to write in */
Font font;					/* font to write with */
char *text;					/* text (null term.) string */
int line;					/* line to place text on */
{
  WindowInfo *winfo;
  FontInfo *finfo;
  int center;
  int start;
  int len;

  /* set aside some storage for font and window info */
  winfo = (WindowInfo *)calloc(sizeof(WindowInfo), 1);
  finfo = (FontInfo *)calloc(sizeof(FontInfo), 1);

  XQueryWindow(window, winfo);			/* find out about the window*/
  center = winfo->width/2;			/* find the center */

  XQueryFont(font, finfo);			/* find the font width */
  if ((start = center - finfo->width*(len = strlen(text))/2) < 0)
     start = 0;

  XText(window, start, line,			/* write the text */
	text, len,
	font, BlackPixel, WhitePixel);

}
/* writetime - update the Expire time line in the lock window */
writetime(left)
long left;
{
       char *temp;
            	  
       temp = (char *)calloc(sizeof("Lock expires in: xx:xx minutes")+1,1);

       sprintf(temp, "Lock expires in: %2d:%02d minutes", left/60,
	       left%60);

       writecenter(w0, temp, sfont, timeline);
       
       free(temp);
}    
/* update - called once a second to refresh timer line, and check for */
/* timer expiration */
update()
{

	 int left = timer - time(0);
	 /* if no time left then call exit code with a return value of 1 */
	 if (left <= 0) {
		  printf("Lock Expired\n");
		  done1();
	 }
	 /* otherwise refresh the time */
	 /* and make sure we are top window */
	 writetime(left);
	 XRaiseWindow(w0);

	 /* also, refresh entire window if neccessary */
	 if (refresh) {
		  showlocked(me, w0, sfont, bfont, timer);
		  refresh = 0;
	 }
	 XFlush();				/* flush output */
}
	
/* dummy - the interrupt routine for SIGALRM.  This routine will */
/* run every .1 seconds.  9/10 of the time it just returns, the event */
/* handler will wake up because it was doing a sigpause.  That is the */
/* historic reason for the name. */
/* On the tenth time (every 1s) it will call update, which will check the */
/* timer, refresh the on-screen countdown, and refresh the rest of the */
/* screen if need be */
int dummy()
{
	 if (!(ref_delay = (++ref_delay)%10)) /* every ten times run update */
	    update();
}

/* successfull completion */
/* restore mouse and exit */
int done()
{
	 XWarpMouse(RootWindow, mousex, mousey);
	 XFlush();
	 exit(0);
}
/* unsuccessfull completion */
/* restore mouse and exit with error code */
int done1()
{
	 XWarpMouse(RootWindow, mousex, mousey);
	 XFlush();
	 if (lflag) {
		  kill(getppid(), SIGHUP);
	 }
	 exit(1);
}
int noop()
{}
int abuse()
{
	 luser++;
	 refresh++;
}
