/*
 *****************************************************************************
 *
 * File:    getpasswd.c
 *
 * Purpose: Routines for obtaining a password from a user.
 *
 *  Fwknop is developed primarily by the people listed in the file 'AUTHORS'.
 *  Copyright (C) 2009-2014 fwknop developers and contributors. For a full
 *  list of contributors, see the file 'CREDITS'.
 *
 *  License (GNU General Public License):
 *
 *  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 <signal.h>

#ifdef WIN32
  #include <conio.h>
#else
  #include <termios.h>
#endif

#include "fwknop_common.h"
#include "getpasswd.h"
#include "utils.h"

#define PW_BUFSIZE              128                 /*!< Maximum number of chars an encryption key or a password can contain */

#define PW_BREAK_CHAR           0x03                /*!< Ascii code for the Ctrl-C char */
#define PW_BS_CHAR              0x08                /*!< Ascii code for the backspace char */
#define PW_LF_CHAR              0x0A                /*!< Ascii code for the \n char */
#define PW_CR_CHAR              0x0D                /*!< Ascii code for the \r char */
#define PW_CLEAR_CHAR           0x15                /*!< Ascii code for the Ctrl-U char */

#define ARRAY_FIRST_ELT_ADR(t)  &((t)[0])           /*!< Macro to get the first element of an array */
#define ARRAY_LAST_ELT_ADR(t)   &((t)[sizeof(t)-1]) /*!< Macro to get the last element of an array */

/**
 * @brief Read a password from a stream object
 *
 * @param stream Pointer to a FILE object that identifies an input stream.
 *
 * @return The password buffer or NULL if not set
 */
static char *
read_passwd_from_stream(FILE *stream)
{
    static char     password[PW_BUFSIZE] = {0};
    int             c;
    char           *ptr;

    ptr = ARRAY_FIRST_ELT_ADR(password);

    if(stream == NULL)
        return password;

#ifdef WIN32
    while((c = _getch()) != PW_CR_CHAR)
#else
    while( ((c = getc(stream)) != EOF) && (c != PW_LF_CHAR) && (c != PW_BREAK_CHAR) )
#endif
    {
        /* Handle a backspace without backing up too far. */
        if (c == PW_BS_CHAR)
        {
            if (ptr != ARRAY_FIRST_ELT_ADR(password))
                ptr--;
        }

        /* Handle a Ctrl-U to clear the password entry and start over */
        else if (c == PW_CLEAR_CHAR)
            ptr = ARRAY_FIRST_ELT_ADR(password);

        /* Fill in the password buffer until it reaches the last -1 char.
         * The last char is used to NULL terminate the string. */
        else if (ptr < ARRAY_LAST_ELT_ADR(password))
        {
            *ptr++ = c;
        }

        /* Discard char */
        else;
    }

    /* A CTRL-C char has been detected, we discard the password */
    if (c == PW_BREAK_CHAR)
        password[0] = '\0';

    /* Otherwise we NULL terminate the string here. Overflows are handled
     * previously, so we can add the char without worrying */
    else
        *ptr = '\0';

    return password;
}

/**
 * @brief Function for accepting password input from users
 *
 * The functions reads chars from a buffered stream and store them in a buffer of
 * chars. If a file descriptor is supplied then, the password is read from
 * the associated stream, otherwise a new buffered stream is created and a
 * prompt is displayed to the user.
 *
 * @param prompt String displayed on the terminal to prompt the user for a
 *               password or an encryption key
 * @param fd     File descriptor to use to read the pasword from. If fd is set
 *               to FD_INVALID, then a new stream is opened.
 *
 * @return NULL if a problem occured or the user killed the terminal (Ctrl-C)\n
 *         otherwise the password - empty password is accepted.
 */
char*
getpasswd(const char *prompt, int fd)
{
    char           *ptr = NULL;
    FILE           *fp  = NULL;

#ifndef WIN32
    sigset_t        sig, old_sig;
    struct termios  ts;
    tcflag_t        old_c_lflag = 0;
#else
	/* Force stdin on windows. */
	fd = 0;
#endif

    /* If a valid file descriptor is supplied, we try to open a stream from it */
    if (FD_IS_VALID(fd))
    {
        fp = fdopen(fd, "r");
        if (fp == NULL)
        {
            log_msg(LOG_VERBOSITY_ERROR, "getpasswd() - "
                "Unable to create a stream from the file descriptor : %s",
                strerror(errno));
            return(NULL);
        }
    }

#ifndef WIN32
    /* Otherwise we are going to open a new stream */
    else
    {
        if((fp = fopen(ctermid(NULL), "r+")) == NULL)
            return(NULL);

        setbuf(fp, NULL);

        /* Setup blocks for SIGINT and SIGTSTP and save the original signal
        * mask.
        */
        sigemptyset(&sig);
        sigaddset(&sig, SIGINT);
        sigaddset(&sig, SIGTSTP);
        sigprocmask(SIG_BLOCK, &sig, &old_sig);

        /*
        * Save current tty state for later restoration after we :
        *   - disable echo of characters to the tty
        *   - disable signal generation
        *   - disable cannonical mode (input read line by line mode)
        */
        tcgetattr(fileno(fp), &ts);
        old_c_lflag = ts.c_lflag;
        ts.c_lflag &= ~(ECHO | ICANON | ISIG);
        tcsetattr(fileno(fp), TCSAFLUSH, &ts);

        fputs(prompt, fp);
    }
#else
    _cputs(prompt);
#endif
    /* Read the password */
    ptr = read_passwd_from_stream(fp);

#ifdef WIN32
    /* In Windows, it would be a CR-LF
     */
    _putch(PW_CR_CHAR);
    _putch(PW_LF_CHAR);
#else
    if(! FD_IS_VALID(fd))
    {
        /* Reset terminal settings
        */
        fputs("\n", fp);
        ts.c_lflag = old_c_lflag;
        tcsetattr(fileno(fp), TCSAFLUSH, &ts);
    }
#endif

    fclose(fp);

    return (ptr);
}

/* Function for accepting password input from a file
*/
int
get_key_file(char *key, int *key_len, const char *key_file,
    fko_ctx_t ctx, const fko_cli_options_t *options)
{
    FILE           *pwfile_ptr;
    unsigned int    numLines = 0, i = 0, found_dst;

    char            conf_line_buf[MAX_LINE_LEN] = {0};
    char            tmp_char_buf[MAX_LINE_LEN]  = {0};
    char           *lptr;

    memset(key, 0x00, MAX_KEY_LEN+1);

    if ((pwfile_ptr = fopen(key_file, "r")) == NULL)
    {
        log_msg(LOG_VERBOSITY_ERROR, "Could not open config file: %s", key_file);
        return 0;
    }

    while ((fgets(conf_line_buf, MAX_LINE_LEN, pwfile_ptr)) != NULL)
    {
        numLines++;
        conf_line_buf[MAX_LINE_LEN-1] = '\0';
        lptr = conf_line_buf;

        memset(tmp_char_buf, 0x0, MAX_LINE_LEN);

        while (*lptr == ' ' || *lptr == '\t' || *lptr == '=')
            lptr++;

        /* Get past comments and empty lines.
        */
        if (*lptr == '#' || *lptr == '\n' || *lptr == '\r' || *lptr == '\0' || *lptr == ';')
            continue;

        /* Look for a line like "<SPA destination IP>: <password>" - this allows
        * multiple keys to be placed within the same file, and the client will
        * reference the matching one for the SPA server we are contacting
        */
        found_dst = 1;
        for (i=0; i < strlen(options->spa_server_str); i++)
            if (*lptr++ != options->spa_server_str[i])
                found_dst = 0;

        if (! found_dst)
            continue;

        if (*lptr == ':')
            lptr++;
        else
            continue;

        /* Skip whitespace until we get to the password
        */
        while (*lptr == ' ' || *lptr == '\t' || *lptr == '=')
            lptr++;

        i = 0;
        while (*lptr != '\0' && *lptr != '\n') {
            key[i] = *lptr;
            lptr++;
            i++;
        }
        key[i] = '\0';
    }

    fclose(pwfile_ptr);

    if (key[0] == '\0') {
        log_msg(LOG_VERBOSITY_ERROR, "Could not get key for IP: %s from: %s",
            options->spa_server_str, key_file);
        return 0;
    }

    *key_len = strlen(key);

    return 1;
}

/***EOF***/
