/*
 * $Source: /afs/gza.com/misc/xscreensaver/src/RCS/password.c,v $
 * $Author: jik $
 *
 * This file is part of xscreensaver.  It contains the code for
 * password manipulation.
 *
 * Author: Jonathan Kamens, MIT Project Athena and
 *                          MIT Student Information Processing Board
 *
 * Copyright (c) 1989 by Jonathan Kamens.  This code may be
 * distributed freely as long as this notice is kept intact in its
 * entirety and every effort is made to send all corrections and
 * improvements to the code back to the author.  Also, don't try to
 * make any money off of it or pretend that you wrote it.
 */

#if !defined(lint) && !defined(SABER)
     static char rcsid_password_c[] = "$Header: /afs/gza.com/misc/xscreensaver/src/RCS/password.c,v 1.34 1993/07/15 15:09:19 jik Exp $";
#endif

#include "xsaver.h"
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Label.h>
#include <X11/Shell.h>
#ifdef PASSWORD_PORT
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
#if defined(mips) || defined(sparc) || defined (_IBMR2)
extern int sys_nerr;
extern char *sys_errlist[];
#endif
#if (defined(ibm) && defined(SYSV) && defined(i386)) || defined(pyr)
extern int sys_nerr, errno;
extern char *sys_errlist[];
#endif
#endif
#if defined (linux)
/* #define SHADOW_PWD 1 */
#endif
#if defined(sun) && defined(__svr4__)
#include <shadow.h>
#else
#include <pwd.h>
#endif
#include "globals.h"
#include "PromptBox.h"

char *get_passwd();
Widget PasswordWindow();

extern char *time_string(), *timeleft_string(), *timeout_string(),
     *user_string(), *get_user(), *malloc(), *my_malloc();
#ifndef crypt
extern char *crypt ();
#endif
extern long random();
extern void RemoveRoot();

#ifdef PASSWORD_PORT
static int password_port = -1;
#endif



Widget PasswordWindow(which)
int which;
{
     PromptLine prompt_lines[MAXPROMPT];
     int i = 0;
     Widget prompt;
     
     switch (which) {
	case GETTING_FIRST_INITIAL_PASSWORD:
	case GETTING_SECOND_INITIAL_PASSWORD:
	case GETTING_LOCKED_PASSWORD:
	  prompt_lines[i] = default_line;
	  prompt_lines[i].str = user_string(USER_FORMAT); i++;
	  prompt_lines[i] = default_line;
	  prompt_lines[i].name = "time";
	  prompt_lines[i].str = time_string(TIME_FORMAT, Force); i++;
	  if (defs.timeout && lock_flag) {
	       prompt_lines[i] = default_line;
	       if (which < 3) {
		    prompt_lines[i].str = timeout_string(TIMEOUT_FORMAT, Force);
		    i++;
	       }
	       else {
		    prompt_lines[i].str = timeleft_string(TIMELEFT_FORMAT, Force);
		    i++;
	       }
	  }
	  prompt_lines[i] = default_line;
	  prompt_lines[i].str = "";
	  prompt_lines[i].center = False; i++;
	  prompt_lines[i] = default_line;
	  if (which != GETTING_SECOND_INITIAL_PASSWORD)
	       prompt_lines[i].str = PASS_PROMPT1;
	  else
	       prompt_lines[i].str = PASS_PROMPT2;
	  prompt_lines[i].center = False; i++;
	  prompt_lines[i].spread = 10;
	  break;

	case SHOWING_CAPSLOCK_WARNING:
	  prompt_lines[i] = default_line;
	  prompt_lines[i].str = "";
	  prompt_lines[i].center = False; i++;
	  prompt_lines[i] = default_line;
	  prompt_lines[i].str = "";
	  prompt_lines[i].center = False; i++;
	  prompt_lines[i] = default_line;
	  prompt_lines[i].str = CAPS_LOCK_PROMPT;
	  prompt_lines[i].center = True; i++;
	  prompt_lines[i] = default_line;
	  prompt_lines[i].str = "";
	  prompt_lines[i].center = False; i++;
	  prompt_lines[i] = default_line;
	  prompt_lines[i].str = "";
	  prompt_lines[i].center = False; i++;
	  break;
     }
	  
     prompt = CenteredPromptBox(root_widget, "prompt", prompt_lines, i,
			(Widget *) NULL);

     XtMapWidget(prompt);

     return(prompt);
}




     
     
char *get_passwd()
{
#if defined(sun) && defined(__svr4__)
     struct spwd *spent;
#else
     struct passwd *pwent;
#endif
     char *user;
     char *passwd;
     
     user = get_user();

     if (! *user)
	  return((char *) NULL);
#if defined(sun) && defined(__svr4__)
     spent = getspnam(user);
     if (spent) if (strlen(spent->sp_pwdp) == PASSWDLENGTH)
          return(spent->sp_pwdp);
#else
     pwent = getpwnam(user);
     if (pwent) if (strlen(pwent->pw_passwd) == PASSWDLENGTH)
	  return(pwent->pw_passwd);
#endif

     return(0);
}


Boolean correct_password(passwd)
char *passwd;
{
     char seed[3];

     if (defs.ekey) {
	  strncpy(seed, defs.ekey, 2);
	  return(! strcmp(crypt(passwd, seed), defs.ekey));
     }
     else if (defs.key) {
	  if (! strcmp(passwd, defs.key))
	       return(True);
	  else
	       return(False);
     }
     else
	  return(False);
}


/*
 * Takes the password from defs.key, encrypts it, places its value
 * into defs.ekey, and then zeros out defs.key.  This is basically for
 * security reasons -- if someone gcore's the file or manages to read
 * memory and the key is stored in plaintext, they can read it and do
 * nasty things.
 *
 * There's no sanity checking here -- if there's nothing in defs.key,
 * things will lose.
 * 
 * This is relatively useless when the unencrypted key is given on the
 * command line or put in the user's resources, since the toolkit
 * keeps several copies of the key floating around.  Even if we bzero
 * the copy we have, there are still several others in memory.  It
 * becomes useful when the key is typed in the first time the screen
 * is locked, and since we're assuming that that's what will be
 * happening most of the time, this is a useful procedure.
 */
install_password()
{
     char seed[3];
     
     /* Calculate the seed for the password */
     *seed = *(seed + 1) = *(seed + 2) = 0;
     do {
	  *seed = (char) (random() % 0172);
     } while (! (((*seed >= 056) && (*seed <= 071)) || /* /, ., 0-9 */
		 ((*seed >= 0101) && (*seed <= 0132)) || /* A-Z */
		 ((*seed >= 0141) && (*seed <= 0172)))); /* a-z */
     do {
	  seed[1] = (char) (random() % 0172);
     } while (! (((seed[1] >= 056) && (seed[1] <= 071)) || /* /, ., 0-9 */
		 ((seed[1] >= 0101) && (seed[1] <= 0132)) || /* A-Z */
		 ((seed[1] >= 0141) && (seed[1] <= 0172)))); /* a-z */

     defs.ekey = my_malloc(PASSWDLENGTH + 1, "install_password");
     strcpy(defs.ekey, crypt(defs.key, seed));

     bzero(defs.key, strlen(defs.key));
     XtFree(defs.key);
     defs.key = 0;
}

	  

#ifdef PASSWORD_PORT
initialize_password_port()
{
     struct sockaddr_in name;

     /* Only do this if the port is positive. */
     if (defs.password_port <= 0)
	  return(-1);
     
     /* First, get a file descriptor to work with */
     
     password_port = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
     if (password_port < 0) {
	  /* Forget it, we can't open the socket, so punt trying. */
	  fprintf(stderr, "%s: couldn't create password socket", whoami);
	  if (errno < sys_nerr)
	       fprintf(stderr, ": %s\n", sys_errlist[errno]);
	  else
	       fprintf(stderr, "\n");
	  if (defs.require_port)
	       exit(1);
	  password_port = -1;
	  return(password_port);
     }

     /* set non-blocking IO on the socket */
     fcntl(password_port, F_SETFL, FNDELAY);
     
     /* Now, bind it to a port. */

     name.sin_family = AF_INET;
     name.sin_port =  htons((u_short) defs.password_port);
     name.sin_addr.s_addr = INADDR_ANY;

     if (bind(password_port, (struct sockaddr *) &name, sizeof(name)) < 0) {
	  fprintf(stderr, "%s: couldn't bind to port %d", whoami,
		  defs.password_port);
	  if (errno < sys_nerr)
	       fprintf(stderr, ": %s\n", sys_errlist[errno]);
	  else
	       fprintf(stderr, "\n");
	  if (errno == EADDRINUSE) {
	       fprintf(stderr, "%s: Are you running another %s?\n",
		       whoami, whoami);
	  }
	  if (defs.require_port)
	       exit(1);
	  password_port = -1;
	  return(password_port);
     }
     
     listen(password_port, 5);

     /* All done, we've got a socket now */
     return(password_port);
}



void close_password_port()
{
     /*
      * N.B.: I'm not also doing an XtRemoveInput to remove it from
      * the list of inputs the X toolkit is expecting input from.  I
      * don't think this'll be a problem, but I'm noting it here, just
      * in case...
      */
     (void) close(password_port);
}



 /* Note that two connections made at once will produde garbled */
 /* password. 							*/
/*ARGSUSED*/
static void get_password_data(client_data, source, id)
XtPointer client_data;
int *source;
XtInputId *id;
{
     static int reading = 0;
     static int tries = 0;
     static int num_read;
     static char password[MAXPASSWORD];
     static char readbuf[1];

     if (! is_locked) {
	  if (debug_file)
	       fprintf(debug_file,
		       "Got port data when screen isn't locked, closing.\n");
	  close(*source);
	  XtRemoveInput(*id);
	  return;
     }
     
     if (reading == 0) {
	  if (debug_file)
	       fprintf(debug_file,
		       "Starting to read password from port.\n");
	  reading = 1;
	  num_read = 0;
	  *password = '\0';
     }
     
     /* I know it's heinous to read one character at a time, but it's */
     /* easier to program, and this is, after all, only a hack,       */
     /* anyway. 						      */

     while (read(*source, readbuf, 1) > 0) {
	  password[num_read] = *readbuf;
	  if (*readbuf == '\n') {
	       password[num_read] = '\0';
	       if (correct_password(password)) {
		    if (debug_file)
			 fprintf(debug_file,
				 "Correct password from port, unlocking and closing.\n");
		    write(*source, "Password correct, unlocking screen.\n",
			  36);
		    RemoveRoot((Widget) NULL, (XEvent *) NULL,
			       (String *) NULL, (Cardinal *) NULL);
		    close(*source);
		    XtRemoveInput(*id);
		    bzero(password, strlen(password));
		    return;
	       }
	       else {
		    if (debug_file)
			 fprintf(debug_file,
				 "Incorrect password from port.\n");
		    write(*source, "Password incorrect.\n", 20);
		    tries++;
		    if (tries == 6) {
			 /* Maximum number of tries (6) exceeded */
			 write(*source, "Maximum number of tries exceeded.\n",
			       34);
			 if (debug_file)
			      fprintf(debug_file,
				      "Maximum tries on port exceeded.\n");
			 close(*source);
			 XtRemoveInput(*id);
			 tries = 0;
		    }
		    reading = 0;
		    return;
	       }
	  }
	  if ((num_read < MAXPASSWORD - 1) && (*readbuf != '\r'))
	       num_read += 1;
     }

     if (errno != EWOULDBLOCK) {
	  if (debug_file)
	       fprintf(debug_file,
		       "Error reading from port, closing.\n");
	  close(*source);
	  XtRemoveInput(*id);
	  reading = 0;
     }

     return;
}

     

/*ARGSUSED*/
void check_password_port(client_data, source, id)
caddr_t client_data;
int *source;
XtInputId *id;
{
     int ns;
     struct sockaddr_in name;
     int size;

     size = sizeof(struct sockaddr_in);
     ns = accept(*source, (struct sockaddr *) &name, &size);

     if (ns < 0)
	  return;

     if (debug_file)
	  fprintf(debug_file,
		  "New port connection.\n");
     
     /* If we aren't locked, then punt this connection. */
     if (! is_locked) {
	  if (debug_file)
	       fprintf(debug_file, "Screen's not locked, so punting.\n");
	  close(ns);
	  return;
     }

     /* Tell the user what to do. */
     write(ns, "Type password to unlock screen:\n", 32);
	   
     /* Tell the toolkit to read data off of this socket */
     if (debug_file)
	  fprintf(debug_file, "Adding new port to X toolkit inputs.\n");
     XtAppAddInput(app_context, ns,
		   (XtPointer) (XtInputReadMask | XtInputExceptMask),
		   get_password_data, 0);
     
     return;
}
#endif /* PASSWORD_PORT */
