/*
 * Copyright (c) 2008-2010 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com>
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * This program 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
 */

#include <common.h>
#include <password.h>
#include <errno.h>
#include <readkey.h>
#include <fs.h>
#include <fcntl.h>
#include <digest.h>
#include <malloc.h>
#include <xfuncs.h>
#include <clock.h>

#if defined(CONFIG_PASSWD_SUM_MD5)
#define PASSWD_SUM "md5"
#elif defined(CONFIG_PASSWD_SUM_SHA1)
#define PASSWD_SUM "sha1"
#elif defined(CONFIG_PASSWD_SUM_SHA256)
#define PASSWD_SUM "sha256"
#endif

int password(unsigned char *passwd, size_t length, int flags, int timeout)
{
	unsigned char *buf = passwd;
	int pos = 0;
	unsigned char ch;
	uint64_t start, second;

	if (!passwd)
		return -EINVAL;

	start = get_time_ns();
	second = start;

	do {
		if (tstc()) {
			ch = getc();

			switch (ch) {
			case '\r':
			case '\n':
				*buf = '\0';
				puts("\r\n");
				return pos;
			case '\0':
			case '\t':
				continue;
			case CTL_CH('c'):
				passwd[0] = '\0';
				puts("\r\n");
				return 0;
			case CTL_CH('h'):
			case KEY_DEL7:
			case KEY_DEL:
				if (flags & STAR && pos > 0)
					puts("\b \b");
				*buf = '\0';
				buf--;
				pos--;
				continue;
			default:
				if (pos < length - 1) {
					if (flags & STAR)
						putchar('*');
					else if (flags & CLEAR)
						putchar(ch);

					*buf = ch;
					buf++;
					pos++;
				} else {
					if (flags & STAR)
						putchar('\a');
				}
			}
		}
	} while (!is_timeout(start, timeout * SECOND) || timeout == 0);

	return -1;
}
EXPORT_SYMBOL(password);

int is_passwd_enable(void)
{
	int fd;

	fd = open(PASSWD_FILE, O_RDONLY);

	if (fd < 0)
		return 0;

	close(fd);

	return 1;
}
EXPORT_SYMBOL(is_passwd_enable);

int passwd_disable(void)
{
	return unlink(PASSWD_FILE);
}
EXPORT_SYMBOL(passwd_disable);

static unsigned char to_digit(unsigned char c)
{
	if (c >= '0' && c <= '9')
		c -= '0';
	else
		c -= 'a' - 10;

	return c;
}

static unsigned char to_hexa(unsigned char c)
{
	if (c < 10)
		c += '0';
	else
		c += 'a' - 10;

	return c;
}

int read_passwd(unsigned char *sum, size_t length)
{
	int fd;
	int ret = 0;
	unsigned char c;

	if (!sum && length < 1)
		return -EINVAL;

	fd = open(PASSWD_FILE, O_RDONLY);

	if (fd < 0)
		return fd;

	do {
		ret = read(fd, &c, sizeof(char));

		if (ret < 0)
			goto exit;

		*sum = to_digit(c) << 4;

		ret = read(fd, &c, sizeof(char));

		if (ret < 0)
			goto exit;

		*sum |= to_digit(c);
		sum++;
		length--;
	} while(length > 0);

exit:

	ret = 0;

	close(fd);

	return ret;
}
EXPORT_SYMBOL(read_passwd);

int write_passwd(unsigned char *sum, size_t length)
{
	int fd;
	unsigned char c;
	int ret = 0;

	if (!sum && length < 1)
		return -EINVAL;

	fd = open(PASSWD_DIR, O_RDONLY);

	if (fd < 0)
		mkdir(PASSWD_DIR, 644);

	close(fd);

	fd = open(PASSWD_FILE, O_WRONLY | O_CREAT, 600);

	if (fd < 0)
		return fd;

	do {
		c = to_hexa(*sum >> 4 & 0xf);

		ret = write(fd, &c, sizeof(unsigned char));

		if (ret < 0)
			goto exit;

		c = to_hexa(*sum & 0xf);

		ret = write(fd, &c, sizeof(unsigned char));

		if (ret < 0)
			goto exit;

		sum++;
		length--;
	} while(length > 0);

	ret = 0;

exit:
	close(fd);

	return ret;
}
EXPORT_SYMBOL(write_passwd);

int check_passwd(unsigned char* passwd, size_t length)
{
	struct digest *d;
	unsigned char *passwd1_sum;
	unsigned char *passwd2_sum;
	int ret = 0;

	d = digest_get_by_name(PASSWD_SUM);

	passwd1_sum = calloc(d->length, sizeof(unsigned char));

	if (!passwd1_sum)
		return -ENOMEM;

	passwd2_sum = calloc(d->length, sizeof(unsigned char));

	if (!passwd2_sum) {
		ret = -ENOMEM;
		goto err1;
	}

	d->init(d);

	d->update(d, passwd, length);

	d->final(d, passwd1_sum);

	ret = read_passwd(passwd2_sum, d->length);

	if (ret < 0)
		goto err2;

	if (strncmp(passwd1_sum, passwd2_sum, d->length) == 0)
		ret = 1;

err2:
	free(passwd2_sum);
err1:
	free(passwd1_sum);

	return ret;
}
EXPORT_SYMBOL(check_passwd);

int set_passwd(unsigned char* passwd, size_t length)
{
	struct digest *d;
	unsigned char *passwd_sum;
	int ret;

	d = digest_get_by_name(PASSWD_SUM);

	passwd_sum = calloc(d->length, sizeof(unsigned char));

	if (!passwd_sum)
		return -ENOMEM;

	d->init(d);

	d->update(d, passwd, length);

	d->final(d, passwd_sum);

	ret = write_passwd(passwd_sum, d->length);

	free(passwd_sum);

	return ret;
}
EXPORT_SYMBOL(set_passwd);
