/**
 * Copyright (C) 2006 International Business Machines
 * Author(s): Trevor Highland <tshighla@us.ibm.com>
 *            Theresa Nelson <tmnelson@us.ibm.com>
 *            Tyler Hicks <tyhicks@ou.edu>
 *
 * I/O functions for mount helper
 *
 * This program 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 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 <stdio.h>
#include <string.h>
#include <termios.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
#include "ecryptfs.h"
#include "io.h"

static int disable_echo(struct termios *saved_settings)
{
	struct termios current_settings;
	int rc = 0;

	rc = tcgetattr(0, &current_settings);
	if (rc)
		return rc;
	*saved_settings = current_settings;
	current_settings.c_lflag &= ~ECHO;
	rc = tcsetattr(0, TCSANOW, &current_settings);
	return rc;
}

static int enable_echo(struct termios *saved_settings)
{
	return tcsetattr(0, TCSANOW, saved_settings);
}

int get_string_stdin(char **val, char *prompt, int echo)
{
#define DEFAULT_STRING_LENGTH 16
	int count = 0;
	struct termios saved_settings;
	int length = DEFAULT_STRING_LENGTH;
	char *temp;
	int rc = 0;

	printf("%s: ", prompt);
	temp = malloc(length);
	if (!temp) {
		rc = -ENOMEM;
		goto out;
	}
	*val = temp;
	if (!echo) {
		rc = disable_echo(&saved_settings);
		if (rc)
			goto out;
	}
	do {
		if (count == length) {
			temp = malloc(length * 2);
			if (!temp) {
				rc = -ENOMEM;
				goto out;
			}
			memcpy(temp, *val, length);
			memset(*val, 0, length);
			length *= 2;
			free(*val);
			*val = temp;
		}
		(*val)[count] = getchar();
		count++;
	} while((*val)[count-1] != '\n');
	(*val)[count - 1] = '\0';
	if (!echo) {
		printf("\n");
		rc = enable_echo(&saved_settings);
	}
out:
	return rc;
}

int get_string(char *val, int len, int echo)
{
	int count = 0;
	struct termios saved_settings;
	int rc = 0;

	if (echo == ECRYPTFS_ECHO_OFF) {
		rc = disable_echo(&saved_settings);
		if (rc)
			goto out;
	}
	do {
		val[count] = getchar();
		count++;
	} while(val[count-1] != '\n' && (count < len));
	if (count > len)
		val[len - 1] = '\0';
	else
		val[count - 1] = '\0';
	if (echo == ECRYPTFS_ECHO_OFF) {
		printf("\n");
		rc = enable_echo(&saved_settings);
	}
out:
	return rc;
}

static inline int munch_newline(void)
{
	if (getchar() == '\n')
		return 0;
	while (getchar() != '\n');
	return -1;
}

int manager_menu(void)
{
	char str[8];
	int selection;

	printf("\neCryptfs key management menu\n");
	printf("-------------------------------\n");
	printf("\t%d. Add passphrase key to keyring\n", MME_MOUNT_PASSPHRASE);
	printf("\t%d. Add public key to keyring\n", MME_MOUNT_PUBKEY);
	printf("\t%d. Generate new public/private keypair\n", MME_GEN_PUBKEY);
	printf("\t%d. Abort management\n", MME_ABORT);
try_again:
	printf("\nMake selection: ");
	str[0] = getchar();
	if (munch_newline()) {
		printf("Invalid selection\n");
		goto try_again;
	}
	str[strlen(str)] = '\0';
	selection = atoi(str);
	switch (selection) {
	case MME_MOUNT_PASSPHRASE:
	case MME_MOUNT_PUBKEY:
	case MME_GEN_PUBKEY:
	case MME_ABORT:
		break;
	default:
		printf("Invalid selection\n");
		goto try_again;
	}
	return selection;
}

int read_passphrase_salt(char *pass, char *salt)
{
	char *confirmed_pass;
	int rc = 0;

	confirmed_pass = malloc(ECRYPTFS_MAX_PASSWORD_LENGTH);
	if (!confirmed_pass) {
		rc = -ENOMEM;
		ecryptfs_syslog(LOG_ERR, "Failed to allocate memory\n");
		goto out;
	}
	mlock(confirmed_pass, ECRYPTFS_MAX_PASSWORD_LENGTH);
	printf("\n\tMount-wide passphrase: ");
	rc = get_string(pass, ECRYPTFS_MAX_PASSWORD_LENGTH, ECRYPTFS_ECHO_OFF);
	if (rc)
		goto out;
	if (pass[0] == '\0') {
		printf("Invalid passphrase. Aborting mount.\n");
		rc = -EINVAL;
		goto out;
	}
	printf("\tConfirm passphrase: ");
	rc = get_string(confirmed_pass, ECRYPTFS_MAX_PASSWORD_LENGTH,
			ECRYPTFS_ECHO_OFF);
	if (rc) {
		ecryptfs_syslog(LOG_ERR, "Failed to read passphrase\n");
		goto out;
	}
	if (strcmp(pass, confirmed_pass) != 0) {
		printf("Passphrase mismatch. Aborting mount\n");
		rc = -EINVAL;
		goto out;
	}
	printf("\tUsing the default salt value\n");
out:
	memset(confirmed_pass, 0, ECRYPTFS_MAX_PASSWORD_LENGTH);
	free(confirmed_pass);
	return rc;
}

int ecryptfs_set_name_value_pairs(struct ecryptfs_name_val_pair *nvp)
{
	char *val_input = NULL;
	int rc = 0;

	if (!(nvp->next)) {
		printf("No PKI parameters required\n");
		rc = 0;
		goto out;
	}
	val_input = malloc(MAX_PKI_VALUE_SIZE + 1);
	if (!val_input) {
		rc = -errno;
		ecryptfs_syslog(LOG_ERR, "Failed to allocate memory: %s\n",
				strerror(errno));
		goto out;
	}
	mlock(val_input, MAX_PKI_VALUE_SIZE + 1);
	nvp = nvp->next;
	while (nvp) {
		if(nvp->flags & ECRYPTFS_DEFAULT_VALUE_SET)
			printf("\t%s [%s]: ", nvp->name, nvp->value);
		else
			printf("\t%s: ", nvp->name);
		if (get_string(val_input, MAX_PKI_VALUE_SIZE,
			       nvp->flags & ECRYPTFS_ECHO)) {
			rc = -EIO;
			fprintf(stderr, "Failed to read user input\n");
			goto out;
		}
		if(!(nvp->flags & ECRYPTFS_DEFAULT_VALUE_SET)
		   || *val_input !='\0') {
			rc = asprintf(&nvp->value, "%s", val_input);
			if (rc < 0) {
				rc = -ENOMEM;
				ecryptfs_syslog(LOG_ERR, "Failed to allocate memory\n");
				goto out;
			}
		}
		if (nvp->flags & ECRYPTFS_MLOCK) {
			if (mlock(nvp->value, rc) < 0) {
				rc = -errno;
				ecryptfs_syslog(LOG_ERR, "Failed to lock "
						"memory used to store user "
						"input: %s\n", strerror(errno));
				goto out;
			}
		}
		nvp = nvp->next;
	}
	rc = 0;
out:
	if (val_input) {
		memset(val_input, 0, MAX_PKI_VALUE_SIZE);
		free(val_input);
	}
	return rc;
}

int ecryptfs_select_pki(struct ecryptfs_ctx *ctx,
                        struct ecryptfs_pki_elem **selected_pki)
{
        int rc;
        int pki_type;
        int count;
        struct ecryptfs_pki_elem *curr;
        char str[8];
	int default_pki = 1;

prompt_user:
        count = 1;
        curr = ctx->pki_list_head.next;
        if (!curr) {
                rc = 1;
                goto out;
        }
        if (!(curr->next))
                goto success;
	printf("\nThe following PKI modules are available:\n");
        while (curr) {
                printf("\t%i. %s\n", count, curr->pki_name);
                count++;
                curr = curr->next;
        }
	printf("\nSelect desired PKI [%d]: ", default_pki);
        fgets(str, 4, stdin);
	printf("\n");
        str[strlen(str)] = '\0';
	if (str[0] == '\n')
		pki_type = default_pki;
	else
		pki_type = atoi(str);
        if (pki_type < 1 || pki_type >= count) {
                char *pch = strstr(str, "\n");

                printf("Invalid selection\n");
                if (!pch) {
                        int ch;

                        while ((ch = getchar()) != '\n');
                }
                goto prompt_user;
        }
        curr = ctx->pki_list_head.next;
        while(pki_type > 1) {
                curr = curr->next;
                pki_type--;
        }
success:
        *selected_pki = curr;
        rc = 0;
out:
        return rc;
}
