/*
 *  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
 *
 *  This is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This software is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 *  USA.
 */

/*
 * vncauth.c - Functions for VNC password management and authentication.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <vncauth.h>
#include <d3des.h>

#include <fcntl.h>

/*
 * Make sure we call srandom() only once.
 */

static int s_srandom_called = 0;

/*
 * We use a fixed key to store passwords, since we assume that our local
 * file system is secure but nonetheless don't want to store passwords
 * as plaintext.
 */

static unsigned char s_fixedkey[8] = {23,82,107,6,35,78,88,7};

int vncEncryptAndStorePasswd2(char *passwd, char *passwdViewOnly, char *fname);
int vncDecryptPasswdFromFile2(char *fname, char *passwdFullControl, char *passwdViewOnly);

/*
 * Encrypt a password and store it in a file.  Returns 0 if successful,
 * 1 if the file could not be written.
 *
 * NOTE: This function is preserved only for compatibility with the original
 * AT&T VNC software.  Use vncEncryptAndStorePasswd2() instead.
 */

int
vncEncryptAndStorePasswd(char *passwd, char *fname)
{
    return (vncEncryptAndStorePasswd2(passwd, NULL, fname) == 0);
}

/*
 * Encrypt one or two passwords and store them in a file.  Returns 1 if
 * successful, 0 if the file could not be written (note that the original
 * vncEncryptAndStorePasswd() function returns inverse values).  The
 * passwdViewOnly pointer may be NULL.
 *
 * NOTE: The file name of "-" denotes stdout.
 */

int
vncEncryptAndStorePasswd2(char *passwd, char *passwdViewOnly, char *fname)
{
    FILE *fp;
    int bytesToWrite, bytesWrote;
    unsigned char encryptedPasswd[16] = {
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0
    };

    if (strcmp(fname, "-") != 0) {
      fp = fopen(fname, "w");
      if (fp == NULL) {
	return 0;
      }
      chmod(fname, S_IRUSR|S_IWUSR);
    } else {
      fp = stdout;
    }

    strncpy(encryptedPasswd, passwd, 8);
    if (passwdViewOnly != NULL)
	strncpy(encryptedPasswd + 8, passwdViewOnly, 8);

    /* Do encryption in-place - this way we overwrite our copies of
       plaintext passwords. */

    deskey(s_fixedkey, EN0);
    des(encryptedPasswd, encryptedPasswd);
    if (passwdViewOnly != NULL)
	des(encryptedPasswd + 8, encryptedPasswd + 8);

    bytesToWrite = (passwdViewOnly == NULL) ? 8 : 16;
    bytesWrote = fwrite(encryptedPasswd, 1, bytesToWrite, fp);
  
    if (fp != stdout) {
      fclose(fp);
    }
    return (bytesWrote == bytesToWrite);
}


/*
 * Decrypt a password from a file.  Returns a pointer to a newly allocated
 * string containing the password or a null pointer if the password could
 * not be retrieved for some reason.
 *
 * NOTE: This function is preserved only for compatibility with the original
 * AT&T VNC software.  Use vncDecryptPasswdFromFile2() instead.
 */

char *
vncDecryptPasswdFromFile(char *fname)
{
    char *passwd;

    passwd = malloc(9);

    if (passwd != NULL) {
	if (vncDecryptPasswdFromFile2(fname, passwd, NULL) == 0) {
	    free(passwd);
	    passwd = NULL;
	}
    }

    return passwd;
}

/*
 * Decrypt one or two passwords from a file.  Returns the number of
 * passwords read (1, 2, or 0 on error).  On success, the passwords are
 * written into buffers passwdFullControl[] and passwdViewOnly[] if
 * they are not NULL.  If the pointers to buffers are not NULL, then
 * the buffers should be at least of 9 bytes length.
 */

int
vncDecryptPasswdFromFile2(char *fname,
			  char *passwdFullControl, char *passwdViewOnly)
{
    FILE *fp;
    int i, ch;
    char passwd[16];

    if (strcmp(fname, "-") != 0) {
	if ((fp = fopen(fname,"r")) == NULL)
	    return 0;		/* Could not open the file */
    } else {
	fp = stdin;
    }

    for (i = 0; i < 16; i++) {
	ch = getc(fp);
	if (ch == EOF)
	    break;
	passwd[i] = ch;
    }

    if (fp != stdin)
	fclose(fp);

    if (i < 8)
	return 0;		/* Could not read eight bytes */

    deskey(s_fixedkey, DE1);

    /* Decoding first (full-control) password */
    if (passwdFullControl != NULL) {
	des(passwd, passwd);
	memcpy(passwdFullControl, passwd, 8);
	passwdFullControl[8] = '\0';
    }

    /* Decoding second (view-only) password if available */
    if (i == 16 && passwdViewOnly != NULL) {
	des(&passwd[8], &passwd[8]);
	memcpy(passwdViewOnly, &passwd[8], 8);
	passwdViewOnly[8] = '\0';
    }

    /* Destroying our copy of clear-text passwords */
    memset(passwd, 0, 16);

    return (i < 16) ? 1 : 2;
}

unsigned int urandom(void) {
	unsigned int val = 0;
	struct stat sb;
	int fd = -1;
	if (fd < 0 && stat("/dev/urandom", &sb) == 0) {
		fd = open("/dev/urandom", O_RDONLY);
	}
	if (fd < 0 && stat("/dev/random", &sb) == 0) {
		fd = open("/dev/random", O_RDONLY);
	}
	if (fd < 0 && stat("/proc/loadavg", &sb) == 0) {
		fd = open("/proc/loadavg", O_RDONLY);
	}
	if (fd < 0 && stat("/bin/bash", &sb) == 0) {
		fd = open("/bin/bash", O_RDONLY);
		lseek(fd, (off_t) (unsigned int) getpid(), SEEK_SET);
	}
	if (fd >= 0) {
		int i;
		for (i=0; i < 3; i++) {
			char buf[2];
			if (read(fd, buf, 1) > 0) {
				unsigned char uc = (unsigned char) buf[0];  
				if (i==0) {
					val += uc; 
				} else if (i==1) {
					val += uc * 256; 
				} else if (i==2) {
					val += uc * 256 * 256; 
				}
			}
		}
		close(fd);
	} else {
		val = (unsigned int) getpid();
	}
	return val;
}

/*
 * Generate CHALLENGESIZE random bytes for use in challenge-response
 * authentication.
 */

void
vncRandomBytes(unsigned char *bytes)
{
    int i;
    unsigned int seed;

	if (!s_srandom_called) {
		seed = (unsigned int)time(0) ^ (unsigned int)getpid();
		seed += urandom();

		srandom(seed);
		s_srandom_called = 1;
	}

    for (i = 0; i < CHALLENGESIZE; i++) {
	bytes[i] = (unsigned char)(random() & 255);    
    }
}


/*
 * Encrypt CHALLENGESIZE bytes in memory using a password.
 */

void
vncEncryptBytes(unsigned char *bytes, char *passwd)
{
    unsigned char key[8];
    int i;

    /* key is simply password padded with nulls */

    for (i = 0; i < 8; i++) {
	if (i < strlen(passwd)) {
	    key[i] = passwd[i];
	} else {
	    key[i] = 0;
	}
    }

    deskey(key, EN0);

    for (i = 0; i < CHALLENGESIZE; i += 8) {
	des(bytes+i, bytes+i);
    }
}

void UvncEncryptPasswd_MSLOGON(unsigned char *encryptedPasswd, char *passwd) {
	unsigned int i;
	for (i=0; i < 32; i++) {
		if (i < strlen(passwd)) {
			encryptedPasswd[i] = passwd[i];
		} else {
			encryptedPasswd[i] = '\0';
		}
	}
	deskey(s_fixedkey, EN0);
	des(encryptedPasswd, encryptedPasswd);
}

void UvncEncryptBytes2(unsigned char *where, int length, unsigned char *key) {
	int i, j;
	deskey(key, EN0);
	for (i=0; i < 8; i++) {
		where[i] ^= key[i];
	}
	des(where, where);
	for (i=8; i < length; i += 8) {
		for (j=0; j < 8; j++) {
			where[i+j] ^= where[i+j-8];
		}
		des(where+i, where+i);
	}
}

void UvncDecryptBytes2(unsigned char *where, int length, unsigned char *key) {
	int i, j;
	deskey(key, DE1);
	for (i = length - 8; i > 0; i -= 8) {
		des(where + i, where + i);
		for (j=0; j < 8; j++) {
			where[i+j] ^= where[i+j-8];
		}
	}
	/* i=0 */
	des(where, where);
	for (i=0; i < 8; i++) {
		where[i] ^= key[i];
	}
}

