/*
   Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.

   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; version 2 of the License.

   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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
*/

/**
  @file

  @brief
  MySQL Configuration Utility
*/

/* ORACLE_WELCOME_COPYRIGHT_NOTICE */
#include "my_config.h"
#include <welcome_copyright_notice.h>
#include <signal.h>
#include <my_dir.h>
#include <my_rnd.h>
#include "my_aes.h"
#include "client_priv.h"
#include "my_default.h"
#include "my_default_priv.h"

#define MYSQL_CONFIG_EDITOR_VERSION "1.0"
#define MY_LINE_MAX 4096
#define MAX_COMMAND_LIMIT 100
/*
  Header length for the login file.
  4-byte (unused) + LOGIN_KEY_LEN
 */
#define MY_LOGIN_HEADER_LEN (4 + LOGIN_KEY_LEN)

static int g_fd;

/*
  Length of the contents in login file
  excluding the header part.
*/
static size_t file_size;
static const char *opt_user= NULL, *opt_password= NULL, *opt_host=NULL,
            *opt_login_path= "client", *opt_socket= NULL, *opt_port= NULL;

static char my_login_file[FN_REFLEN];
static char my_key[LOGIN_KEY_LEN];

static my_bool opt_verbose, opt_all, tty_password= 0, opt_warn,
               opt_remove_host, opt_remove_pass, opt_remove_user,
               opt_remove_socket, opt_remove_port, login_path_specified= FALSE;

static int execute_commands(int command);
static int set_command(void);
static int remove_command(void);
static int print_command(void);
static void print_login_path(DYNAMIC_STRING *file_buf, const char *path_name);
static void remove_login_path(DYNAMIC_STRING *file_buf, const char *path_name);
static char* locate_login_path(DYNAMIC_STRING *file_buf, const char *path_name);
static my_bool check_and_create_login_file(void);
static void mask_password_and_print(char *buf);
static int reset_login_file(bool gen_key);

static int encrypt_buffer(const char *plain, int plain_len, char cipher[], const int aes_len);
static int decrypt_buffer(const char *cipher, int cipher_len, char plain[]);
static int encrypt_and_write_file(DYNAMIC_STRING *file_buf);
static int read_and_decrypt_file(DYNAMIC_STRING *file_buf);
static int do_handle_options(int argc, char *argv[]);
static void remove_options(DYNAMIC_STRING *file_buf, const char *path_name);
static void remove_option(DYNAMIC_STRING *file_buf, const char *path_name,
                          const char* option_name);
void generate_login_key(void);
static int read_login_key(void);
static int add_header(void);
static void my_perror(const char *msg);

static void verbose_msg(const char *fmt, ...);
static void print_version(void);
static void usage_program(void);
static void usage_command(int command);
extern "C" my_bool get_one_option(int optid, const struct my_option *opt,
                                  char *argument);

enum commands {
  MY_CONFIG_SET,
  MY_CONFIG_REMOVE,
  MY_CONFIG_PRINT,
  MY_CONFIG_RESET,
  MY_CONFIG_HELP
};

struct my_command_data {
  const int id;
  const char *name;
  const char *description;
  my_option *options;
  my_bool (*get_one_option_func)(int optid,
                                 const struct my_option *opt,
                                 char *argument);
};


/* mysql_config_editor utility options. */
static struct my_option my_program_long_options[]=
{
#ifdef DBUG_OFF
  {"debug", '#', "This is a non-debug version. Catch this and exit.",
  0, 0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0},
#else
  {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.",
   0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
#endif
  {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG,
   NO_ARG, 0, 0, 0, 0, 0, 0},
  {"verbose", 'v', "Write more information.", &opt_verbose,
   &opt_verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
  {"version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG,
   NO_ARG, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};

/* Command-specific options. */

/* SET command options. */
static struct my_option my_set_command_options[]=
{
  {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG,
   NO_ARG, 0, 0, 0, 0, 0, 0},
  {"host", 'h', "Host name to be entered into the login file.", &opt_host,
   &opt_host, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
  {"login-path", 'G', "Name of the login path to use in the login file. "
   "(Default : client)", &opt_login_path, &opt_login_path, 0, GET_STR,
   REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
  {"password", 'p', "Prompt for password to be entered into the login file.",
   0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
  {"user", 'u', "User name to be entered into the login file.", &opt_user,
   &opt_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
  {"socket", 'S', "Socket path to be entered into login file.", &opt_socket,
   &opt_socket, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
  {"port", 'P', "Port number to be entered into login file.", &opt_port,
   &opt_port, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
  {"warn", 'w', "Warn and ask for confirmation if set command attempts to "
   "overwrite an existing login path (enabled by default).",
   &opt_warn, &opt_warn, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};

/* REMOVE command options. */
static struct my_option my_remove_command_options[]=
{
  {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG,
   NO_ARG, 0, 0, 0, 0, 0, 0},
  {"host", 'h', "Remove host name from the login path.",
   &opt_remove_host, &opt_remove_host, 0, GET_BOOL, NO_ARG, 0, 0, 0,
   0, 0, 0},
  {"login-path", 'G', "Name of the login path from which options to "
   "be removed (entire path would be removed if none of user, password, "
   "host, socket, or port options are specified). (Default : client)",
   &opt_login_path, &opt_login_path, 0, GET_STR, REQUIRED_ARG,
   0, 0, 0, 0, 0, 0},
  {"password", 'p', "Remove password from the login path.",
   &opt_remove_pass, &opt_remove_pass, 0, GET_BOOL, NO_ARG, 0, 0, 0,
   0, 0, 0},
  {"user", 'u', "Remove user name from the login path.", &opt_remove_user,
   &opt_remove_user, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
  {"warn", 'w', "Warn and ask for confirmation if remove command attempts "
   "to remove the default login path (client) if no login path is specified "
   "(enabled by default).", &opt_warn, &opt_warn, 0, GET_BOOL, NO_ARG, 1,
   0, 0, 0, 0, 0},
  {"socket", 'S', "Remove socket path from the login path.", &opt_remove_socket,
   &opt_remove_socket, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
  {"port", 'P', "Remove port number from the login path.", &opt_remove_port,
   &opt_remove_port, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};

/* PRINT command options. */
static struct my_option my_print_command_options[]=
{
  {"all", OPT_CONFIG_ALL, "Used with print command to print all login paths.",
   &opt_all, &opt_all, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
  {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG,
   NO_ARG, 0, 0, 0, 0, 0, 0},
  {"login-path", 'G', "Name of the login path to use in the login file. "
   "(Default : client)", &opt_login_path, &opt_login_path, 0, GET_STR,
   REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};

/* RESET command options. */
static struct my_option my_reset_command_options[]=
{
  {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG,
   NO_ARG, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};

/* HELP command options. */
static struct my_option my_help_command_options[]=
{
  {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};

my_bool
my_program_get_one_option(int optid,
                          const struct my_option *opt __attribute__((unused)),
                          char *argument)
{
  switch(optid) {
  case '#':
    DBUG_PUSH(argument ? argument : "d:t:o,/tmp/mysql_config_editor.trace");
    break;
  case 'V':
    print_version();
    exit(0);
    break;
  case '?':
    usage_program();
    exit(0);
    break;
  }
  return 0;
}

my_bool
my_set_command_get_one_option(int optid,
                              const struct my_option *opt __attribute__((unused)),
                              char *argument)
{
  switch(optid) {
  case 'p':
    tty_password= 1;
    break;
  case 'G':
    if (login_path_specified)
    {
      /* Error, we do not support multiple login paths. */
      my_perror("Error: Use of multiple login paths is not supported. "
                "Exiting..");
      return 1;
    }
    login_path_specified= TRUE;
    break;
  case '?':
    usage_command(MY_CONFIG_SET);
    exit(0);
    break;
  }
  return 0;
}

my_bool
my_remove_command_get_one_option(int optid,
                                 const struct my_option *opt __attribute__((unused)),
                                 char *argument)
{
  switch(optid) {
  case 'G':
    if (login_path_specified)
    {
      /* Error, we do not support multiple login paths. */
      my_perror("Error: Use of multiple login paths is not supported. "
                "Exiting..");
      return 1;
    }
    login_path_specified= TRUE;
    break;
  case '?':
    usage_command(MY_CONFIG_REMOVE);
    exit(0);
    break;
  }
  return 0;
}

my_bool
my_print_command_get_one_option(int optid,
                                const struct my_option *opt __attribute__((unused)),
                                char *argument)
{
  switch(optid) {
  case 'G':
    if (login_path_specified)
    {
      /* Error, we do not support multiple login paths. */
      my_perror("Error: Use of multiple login paths is not supported. "
                "Exiting..");
      return 1;
    }
    login_path_specified= TRUE;
    break;
  case '?':
    usage_command(MY_CONFIG_PRINT);
    exit(0);
    break;
  }
  return 0;
}

my_bool
my_reset_command_get_one_option(int optid,
                                const struct my_option *opt __attribute__((unused)),
                                char *argument)
{
  switch(optid) {
  case '?':
    usage_command(MY_CONFIG_RESET);
    exit(0);
    break;
  }
  return 0;
}

static struct my_command_data command_data[]=
{
  {
    MY_CONFIG_SET,
    "set",
    "Write a login path to the login file.",
    my_set_command_options,
    my_set_command_get_one_option
  },
  {
    MY_CONFIG_REMOVE,
    "remove",
    "Remove a login path from the login file.",
    my_remove_command_options,
    my_remove_command_get_one_option
  },
  {
    MY_CONFIG_PRINT,
    "print",
    "Print the contents of login file in unencrypted form.",
    my_print_command_options,
    my_print_command_get_one_option
  },
  {
    MY_CONFIG_RESET,
    "reset",
    "Empty the contents of the login file. The file is created\n"
    "if it does not exist.",
    my_reset_command_options,
    my_reset_command_get_one_option
  },
  {
    MY_CONFIG_HELP,
    "help",
    "Display a help message and exit.",
    my_help_command_options,
    NULL
  },
  {
    0, NULL, NULL, NULL, NULL
  }
};


int main(int argc, char *argv[])
{
  MY_INIT(argv[0]);
  DBUG_ENTER("main");
  int command, rc= 0;

  command= do_handle_options(argc, argv);

  if (command > -1)
    rc= execute_commands(command);

  if (rc != 0)
  {
    my_perror("operation failed.");
    DBUG_RETURN(1);
  }
  DBUG_RETURN(0);
}


/**
  Handle all the command line arguments.

  program_name [program options] [command [command options]]

*/
static int do_handle_options(int argc, char *argv[])
{
  DBUG_ENTER("do_handle_options");

  const char *command_list[MAX_COMMAND_LIMIT + 1];
  char **saved_argv= argv;
  char **argv_cmd;
  char *ptr;                                    /* for free. */
  int argc_cmd;
  int rc, i, command= -1;

  if (argc < 2)
  {
    usage_program();
    exit(1);
  }

  if (!(ptr= (char *) my_malloc(PSI_NOT_INSTRUMENTED,
                                (argc + 2) * sizeof(char *),
                                MYF(MY_WME))))
    goto error;

  /* Handle program options. */

  /* Prepare a list of supported commands to be used by my_handle_options(). */
  for (i= 0; (command_data[i].name != NULL) && (i < MAX_COMMAND_LIMIT); i++)
    command_list[i]= (char *) command_data[i].name;
  command_list[i]= NULL;

  if ((rc= my_handle_options(&argc, &argv, my_program_long_options,
                             my_program_get_one_option, command_list, FALSE)))
    exit(rc);

  if (argc == 0)                                /* No command specified. */
    goto done;

  for (i= 0; command_data[i].name != NULL; i++)
  {
    if (!strcmp(command_data[i].name, argv[0]))
    {
      command= i;
      break;
    }
  }

  if (command == -1)
    goto error;

  /* Handle command options. */

  argv_cmd= (char **)ptr;
  argc_cmd= argc + 1;

  /* Prepare a command line (argv) using the rest of the options. */
  argv_cmd[0]= saved_argv[0];
  memcpy((uchar *) (argv_cmd + 1), (uchar *) (argv),
         (argc * sizeof(char *)));
  argv_cmd[argc_cmd]= 0;

  if ((rc= handle_options(&argc_cmd, &argv_cmd,
                          command_data[command].options,
                          command_data[command].get_one_option_func)))
    exit(rc);

  /* Do not allow multiple commands. */
  if ( argc_cmd > 1)
    goto error;

done:
  my_free(ptr);
  DBUG_RETURN(command);

error:
  my_free(ptr);
  usage_program();
  exit(1);
}


static int execute_commands(int command)
{
  DBUG_ENTER("execute_commands");
  int rc= 0;

  if ((rc= check_and_create_login_file()))
    goto done;

  switch(command_data[command].id) {
    case MY_CONFIG_SET :
      verbose_msg("Executing set command.\n");
      rc= set_command();
      break;

    case MY_CONFIG_REMOVE :
      verbose_msg("Executing remove command.\n");
      rc= remove_command();
      break;

    case MY_CONFIG_PRINT :
      verbose_msg("Executing print command.\n");
      rc= print_command();
     break;

    case MY_CONFIG_RESET :
      verbose_msg("Resetting login file.\n");
      rc= reset_login_file(1);
      break;

    case MY_CONFIG_HELP :
      verbose_msg("Printing usage info.\n");
      usage_program();
      break;

    default :
      my_perror("unknown command.");
      exit(1);
  }

done:
  my_close(g_fd, MYF(MY_WME));

  DBUG_RETURN(rc);
}


/**
  Execute 'set' command.

  @param void

  @return -1              Error
           0              Success
*/

static int set_command(void)
{
  DBUG_ENTER("set_command");

  DYNAMIC_STRING file_buf, path_buf;

  init_dynamic_string(&path_buf, "", MY_LINE_MAX, MY_LINE_MAX);
  init_dynamic_string(&file_buf, "", file_size, 3 * MY_LINE_MAX);

  if (tty_password)
    opt_password= get_tty_password(NullS);

  if (file_size)
  {
    if (read_and_decrypt_file(&file_buf) == -1)
      goto error;
  }

  dynstr_append(&path_buf, "[");                /* --login=path */
  if (opt_login_path)
    dynstr_append(&path_buf, opt_login_path);
  else
    dynstr_append(&path_buf, "client");
  dynstr_append(&path_buf, "]");

  if (opt_user)                                 /* --user */
  {
    dynstr_append(&path_buf, "\nuser = ");
    dynstr_append(&path_buf, opt_user);
  }

  if (opt_password)                             /* --password */
  {
    dynstr_append(&path_buf, "\npassword = ");
    dynstr_append(&path_buf, opt_password);
  }

  if (opt_host)                                 /* --host */
  {
    dynstr_append(&path_buf, "\nhost = ");
    dynstr_append(&path_buf, opt_host);
  }

  if (opt_socket)
  {
    dynstr_append(&path_buf, "\nsocket = ");
    dynstr_append(&path_buf, opt_socket);
  }

  if (opt_port)
  {
    dynstr_append(&path_buf, "\nport = ");
    dynstr_append(&path_buf, opt_port);
  }

  dynstr_append(&path_buf, "\n");

  /* Warn if login path already exists */
  if (opt_warn && ((locate_login_path (&file_buf, opt_login_path))
                   != NULL))
  {
    int choice;
    printf ("WARNING : \'%s\' path already exists and will be "
            "overwritten. \n Continue? (Press y|Y for Yes, any "
            "other key for No) : ",
            opt_login_path);
    choice= getchar();

    if (choice != (int) 'y' && choice != (int) 'Y')
      goto done;                                /* skip */
  }

  /* Remove the login path. */
  remove_login_path(&file_buf, opt_login_path);

  /* Append the new login path to the file buffer. */
  dynstr_append(&file_buf, path_buf.str);

  if (encrypt_and_write_file(&file_buf) == -1)
    goto error;

done:
  dynstr_free(&file_buf);
  dynstr_free(&path_buf);
  DBUG_RETURN(0);

error:
  dynstr_free(&file_buf);
  dynstr_free(&path_buf);
  DBUG_RETURN(-1);
}

static int remove_command(void) {
  DBUG_ENTER("remove_command");

  DYNAMIC_STRING file_buf, path_buf;

  init_dynamic_string(&path_buf, "", MY_LINE_MAX, MY_LINE_MAX);
  init_dynamic_string(&file_buf, "", file_size, 3 * MY_LINE_MAX);

  if (file_size)
  {
    if (read_and_decrypt_file(&file_buf) == -1)
      goto error;
  }
  else
    goto done;                                  /* Nothing to remove, skip.. */

  /* Warn if no login path is specified. */
  if (opt_warn &&
      ((locate_login_path (&file_buf, opt_login_path)) != NULL) &&
      (login_path_specified == FALSE)
      )
  {
    int choice;
    printf ("WARNING : No login path specified, so options from the default "
            "login path will be removed.\nContinue? (Press y|Y for Yes, "
            "any other key for No) : ");
    choice= getchar();

    if (choice != (int) 'y' && choice != (int) 'Y')
      goto done;                                /* skip */
  }

  remove_options(&file_buf, opt_login_path);

  if (encrypt_and_write_file(&file_buf) == -1)
    goto error;

done:
  dynstr_free(&file_buf);
  dynstr_free(&path_buf);
  DBUG_RETURN(0);

error:
  dynstr_free(&file_buf);
  dynstr_free(&path_buf);
  DBUG_RETURN(-1);
}

/**
  Execute 'print' command.

  @param void

  @return -1              Error
           0              Success
*/

static int print_command(void)
{
  DBUG_ENTER("print_command");
  DYNAMIC_STRING file_buf;

  init_dynamic_string(&file_buf, "", file_size, 3 * MY_LINE_MAX);

  if (file_size)
  {
    if (read_and_decrypt_file(&file_buf) == -1)
      goto error;
  }
  else
    goto done;                                  /* Nothing to print, skip..*/

  print_login_path(&file_buf, opt_login_path);

done:
  dynstr_free(&file_buf);
  DBUG_RETURN(0);

error:
  dynstr_free(&file_buf);
  DBUG_RETURN(-1);
}


/**
  Create the login file if it does not exist, check
  and set its permissions and modes.

  @param void

  @return  TRUE           Error
           FALSE          Success
*/

static my_bool check_and_create_login_file(void)
{
  DBUG_ENTER("check_and_create_login_file");

  MY_STAT stat_info;

// This is a hack to make it compile. File permissions are different on Windows.
#ifdef _WIN32
#define S_IRUSR  00400
#define S_IWUSR  00200
#define S_IRWXU  00700
#define S_IRWXG  00070
#define S_IRWXO  00007
#endif

  const int access_flag= (O_RDWR | O_BINARY);
  const ushort create_mode= (S_IRUSR | S_IWUSR );

  /* Get the login file name. */
  if (! my_default_get_login_file(my_login_file, sizeof(my_login_file)))
  {
    my_perror("failed to set login file name");
    goto error;
  }

  /*
    NOTE : MYSQL_TEST_LOGIN_FILE env must be a full path,
    where the directory structure must exist. However the
    login file will be created if it does not exist.
  */
#ifdef _WIN32
  if (! (getenv("MYSQL_TEST_LOGIN_FILE")))
  {
    /* Check if 'MySQL' directory is in place. */
    MY_STAT stat_info_dir;
    char login_dir[FN_REFLEN];
    size_t size;

    dirname_part(login_dir, my_login_file, &size);
    /* Remove the trailing '\' */
    if (login_dir[-- size] == FN_LIBCHAR)
      login_dir[size]= 0;

    /* Now check if directory exists? */
    if ( my_stat(login_dir, &stat_info_dir, MYF(0)))
    {
      verbose_msg("%s directory exists.\n", login_dir);
    }
    else
    {
      /* Create the login directory. */
      verbose_msg("%s directory doesn't exist, creating it.\n", login_dir);
      if (my_mkdir(login_dir, 0, MYF(0)))
      {
        my_perror("failed to create the directory");
        goto error;
      }
    }
  }
#endif

  /* Check for login file's existence and permissions (0600). */
  if (my_stat(my_login_file, &stat_info, MYF(0)))
  {
    verbose_msg("File exists.\n");

    file_size= (size_t) stat_info.st_size;

#ifdef _WIN32
    if (1)
#else
    if (!(stat_info.st_mode & (S_IXUSR | S_IRWXG | S_IRWXO)))
#endif
    {
      verbose_msg("File has the required permission.\nOpening the file.\n");
      if ((g_fd= my_open(my_login_file, access_flag, MYF(MY_WME))) == -1)
      {
        my_perror("couldn't open the file");
        goto error;
      }
    }
    else
    {
      verbose_msg("File does not have the required permission.\n");
      printf ("WARNING : Login file does not have the required"
              " file permissions.\nPlease set the mode to 600 &"
              " run the command again.\n");
      goto error;
    }
  }
  else
  {
    verbose_msg("File does not exist.\nCreating login file.\n");
    if ((g_fd= my_create(my_login_file, create_mode, access_flag,
                       MYF(MY_WME))) == -1)
    {
      my_perror("couldn't create the login file");
      goto error;
    }
    else
    {
      verbose_msg("Login file created.\n");
      my_close(g_fd, MYF(MY_WME));
      verbose_msg("Opening the file.\n");

      if((g_fd= my_open(my_login_file, access_flag, MYF(MY_WME))) == -1)
      {
        my_perror("couldn't open the file");
        goto error;
      }
      file_size= 0;
    }
  }

  if (file_size == 0)
  {
    generate_login_key();
    if(add_header() == -1)
      goto error;
  }
  else
  {
    if (read_login_key() == -1)
      goto error;
  }

  DBUG_RETURN(FALSE);

error:
  DBUG_RETURN(TRUE);
}


/**
  Print options under the specified login path. If '--all'
  option is used, print all the optins stored in the login
  file.

  @param file_buf  [in]   Buffer storing the unscrambled login
                          file contents.
  @param path_name [in]   Path name.

  @return                 void

*/

static void print_login_path(DYNAMIC_STRING *file_buf, const char *path_name)
{
  DBUG_ENTER("print_login_path");

  char *start= NULL, *end= NULL, temp= '\0';

  if (file_buf->length == 0)
    goto done;                                  /* Nothing to print. */

  if (opt_all)
  {
    start= file_buf->str;
    end= file_buf->str + file_buf->length;
  }
  else
  {
    start= locate_login_path(file_buf, path_name);
    if (! start)
      /* login path not found, skip..*/
      goto done;

    end= strstr(start, "\n[");
  }

  if (end)
  {
    temp= *end;
    *end= '\0';
  }

  mask_password_and_print(start);

  if (temp != '\0')
    *end= temp;

done:
  DBUG_VOID_RETURN;
}


/**
  Print the specified buffer by masking the actual
  password string.

  @param buf [in]         Buffer to be printed.

  @raturn                 void
*/

static void mask_password_and_print(char *buf)
{
  DBUG_ENTER("mask_password_and_print");
  const char *password_str= "\npassword = ", *mask = "*****";
  char *next= NULL;

  while ((next= strstr(buf, password_str)) != NULL)
  {
    while ( *buf != 0 && buf != next)
      putc( *(buf ++), stdout);
    printf("%s", password_str);
    printf("%s\n", mask);
    if (*buf == '\n')                           /* Move past \n' */
      buf ++;

    /* Ignore the password. */
    while( *buf && *(buf ++) != '\n')
    {}

    if ( !opt_all)
      break;
  }

  /* Now print the rest of the buffer. */
  while ( *buf) putc( *(buf ++), stdout);
  // And a new line.. if required.
  if (* (buf - 1) != '\n')
    putc('\n', stdout);

  DBUG_VOID_RETURN;
}


/**
  Remove multiple options from a login path.
*/
static void remove_options(DYNAMIC_STRING *file_buf, const char *path_name)
{
  /* If nope of the options are specified remove the entire path. */
  if (!opt_remove_host && !opt_remove_pass && !opt_remove_user
      && !opt_remove_socket && !opt_remove_port)
  {
    remove_login_path(file_buf, path_name);
    return;
  }

  if (opt_remove_user)
    remove_option(file_buf, path_name, "user");

  if (opt_remove_pass)
    remove_option(file_buf, path_name, "password");

  if (opt_remove_host)
    remove_option(file_buf, path_name, "host");

  if (opt_remove_socket)
    remove_option(file_buf, path_name, "socket");

  if (opt_remove_port)
    remove_option(file_buf, path_name, "port");
}


/**
  Remove an option from a login path.
*/
static void remove_option(DYNAMIC_STRING *file_buf, const char *path_name,
                          const char* option_name)
{
  DBUG_ENTER("remove_option");

  char *start= NULL, *end= NULL;
  char *search_str;
  size_t search_len, shift_len;
  bool option_found= FALSE;

  search_str= (char *) my_malloc(PSI_NOT_INSTRUMENTED,
                                 (uint) strlen(option_name) + 2, MYF(MY_WME));
  sprintf(search_str, "\n%s", option_name);

  if ((start= locate_login_path(file_buf, path_name)) == NULL)
    /* login path was not found, skip.. */
    goto done;

  end= strstr(start, "\n[");                    /* Next path. */

  if (end)
    search_len= end - start;
  else
    search_len= file_buf->length - (start - file_buf->str);

  while(search_len > 1)
  {
    if (!strncmp(start, search_str, strlen(search_str)))
    {
      /* Option found. */
      end= start;
      while(*(++ end) != '\n')
      {}
      option_found= TRUE;
      break;
    }
    else
    {
      /* Move to next line. */
      while( (-- search_len > 1) && (*(++ start) != '\n'))
      {}
    }
  }

  if (option_found)
  {
    shift_len= file_buf->length - (end - file_buf->str);

    file_buf->length -= (end - start);

    while(shift_len --)
      *(start ++)= *(end ++);
    *start= '\0';
  }

done:
  my_free(search_str);
  DBUG_VOID_RETURN;
}


/**
  Remove the specified login path from the login file.

  @param file_buf  [in]   Buffer storing the unscrambled login
                          file contents.
  @param path_name [in]   Path name.

  @return                 void

*/

static void remove_login_path(DYNAMIC_STRING *file_buf, const char *path_name)
{
  DBUG_ENTER("remove_login_path");

  char *start=NULL, *end= NULL;
  int to_move, len, diff;

  if((start= locate_login_path(file_buf, path_name)) == NULL)
    /* login path was not found, skip.. */
    goto done;

  end= strstr(start, "\n[");

  if (end)
  {
    end ++;                                     /* Move past '\n' */
    len= ((diff= (start - end)) > 0) ? diff : - diff;
    to_move= file_buf->length - (end - file_buf->str) ;
  }
  else
  {
    *start= '\0';
    file_buf->length= ((diff= (file_buf->str - start)) > 0) ? diff : - diff;
    goto done;
  }

  while(to_move --)
    *(start ++)= *(end ++);

  *start= '\0';
  file_buf->length -= len;

done:
  DBUG_VOID_RETURN;
}

/**
  Remove all the contents from the login file.

  @param gen_key [in]     Flag to control the generation of
                          a new key.

  @return -1              Error
           0              Success
*/

static int reset_login_file(bool gen_key)
{
  DBUG_ENTER("reset_login_file");

  if (my_chsize(g_fd, 0, 0, MYF(MY_WME)))
  {
    verbose_msg("Error while truncating the file.\n");
    goto error;
  }

  /* Seek to the beginning of the file. */
  if (my_seek(g_fd, 0L, SEEK_SET, MYF(MY_WME) == MY_FILEPOS_ERROR))
    goto error;                                 /* Error. */

  if (gen_key)
    generate_login_key();                       /* Generate a new key. */

  if (add_header() == -1)
    goto error;

  DBUG_RETURN(0);

error:
  DBUG_RETURN(0);
}


/**
  Find the specified login path in the login file buffer
  and return the starting address.

  @param file_buf  [in]   Buffer storing the unscrambled login
                          file contents.
  @param path_name [in]   Path name.

  @return                 If found, the starting address of the
                          login path, NULL otherwise.
*/

static char* locate_login_path(DYNAMIC_STRING *file_buf, const char *path_name)
{
  DBUG_ENTER("locate_login_path");

  char *addr= NULL;
  DYNAMIC_STRING dy_path_name;

  init_dynamic_string(&dy_path_name, "", 512, 512);

  dynstr_append(&dy_path_name, "\n[");
  dynstr_append(&dy_path_name, path_name);
  dynstr_append(&dy_path_name, "]");

  /* First check if it is the very first login path. */
  if (file_buf->str == strstr(file_buf->str, dy_path_name.str + 1))
    addr= file_buf->str;
  /* If not, scan through the file. */
  else
  {
    addr= strstr(file_buf->str, dy_path_name.str);
    if (addr)
      addr ++;                                  /* Move past '\n' */
  }

  dynstr_free(&dy_path_name);
  DBUG_RETURN(addr);
}


/**
  Encrypt the file buffer and write it to the login file.

  @param file_buf  [in]   Buffer storing the unscrambled login
                          file contents.

  @return -1 Error
           0 Success

  @note The contents of the file buffer are encrypted
        on a line-by-line basis with each line having
        the following format :
        [<first 4 bytes store cipher-length>|<Next cipher-length
        bytes store actual cipher>]
*/

static int encrypt_and_write_file(DYNAMIC_STRING *file_buf)
{
  DBUG_ENTER("encrypt_and_write_file");

  my_bool done= FALSE;
  char cipher[MY_LINE_MAX], *tmp= NULL;
  uint bytes_read=0, len= 0;
  int enc_len= 0;                               // Can be negative.

  if (reset_login_file(0) == -1)
    goto error;

  /* Move past key first. */
  if (my_seek(g_fd, MY_LOGIN_HEADER_LEN, SEEK_SET, MYF(MY_WME))
      != (MY_LOGIN_HEADER_LEN))
    goto error;                                 /* Error while seeking. */


  tmp= &file_buf->str[bytes_read];

  while(! done)
  {
    len= 0;

    while(*tmp++ != '\n')
      if (len < (file_buf->length - bytes_read))
        len ++;
      else
      {
        done= TRUE;
        break;
      }

    if (done)
      break;

    if ((enc_len= my_aes_get_size(len + 1, my_aes_128_ecb)) >
        (MY_LINE_MAX - (int)MAX_CIPHER_STORE_LEN))
    {
      my_perror("A parameter to mysql_config_editor exceeds the maximum "
                "accepted length. Please review the data you've supplied "
                "and try to shorten them permissible length.\n");
      goto error;
    }

    if (encrypt_buffer(&file_buf->str[bytes_read], ++len,
                       cipher + MAX_CIPHER_STORE_LEN, enc_len) < 0)
      goto error;

    bytes_read += len;

    /* Store cipher length first. */
    int4store(cipher, enc_len);

    if ((my_write(g_fd, (const uchar *)cipher, enc_len + MAX_CIPHER_STORE_LEN,
                  MYF(MY_WME))) != (enc_len + MAX_CIPHER_STORE_LEN))
      goto error;
  }

  verbose_msg("Successfully written encrypted data to the login file.\n");

  /* Update file_size */
  file_size= bytes_read;

  DBUG_RETURN(0);

error:
  my_perror("couldn't encrypt the file");
  DBUG_RETURN(-1);
}


/**
  Read the login file, unscramble its contents and store
  them into the file buffer.

  @param file_buf  [in]   Buffer for storing the unscrambled login
                          file contents.

  @return -1 Error
           0 Success
*/

static int read_and_decrypt_file(DYNAMIC_STRING *file_buf)
{
  DBUG_ENTER("read_and_decrypt_file");

  char cipher[MY_LINE_MAX], plain[MY_LINE_MAX];
  uchar len_buf[MAX_CIPHER_STORE_LEN];
  int cipher_len= 0, dec_len= 0;

  /* Move past key first. */
  if (my_seek(g_fd, MY_LOGIN_HEADER_LEN, SEEK_SET, MYF(MY_WME))
      != (MY_LOGIN_HEADER_LEN))
    goto error;                                 /* Error while seeking. */

  /* First read the length of the cipher. */
  while (my_read(g_fd, len_buf, MAX_CIPHER_STORE_LEN,
                 MYF(MY_WME)) == MAX_CIPHER_STORE_LEN)
  {
    cipher_len= sint4korr(len_buf);

    if (cipher_len > MY_LINE_MAX)
      goto error;

    /* Now read 'cipher_len' bytes from the file. */
    if ((int) my_read(g_fd, (uchar *) cipher, cipher_len, MYF(MY_WME)) == cipher_len)
    {
      if ((dec_len= decrypt_buffer(cipher, cipher_len, plain)) < 0)
        goto error;

      plain[dec_len]= 0;
      dynstr_append(file_buf, plain);
    }
  }

  verbose_msg("Successfully decrypted the login file.\n");
  DBUG_RETURN(0);

error:
  my_perror("couldn't decrypt the file");
  DBUG_RETURN(-1);
}


/**
  Encrypt the given plain text.

  @param plain     [in]   Plain text to be encrypted.
  @param plain_len [in]   Length of the plain text.
  @param cipher    [out]  Encrypted cipher text.

  @return                 -1 if error encountered,
                          length encrypted, otherwise.
*/

static int encrypt_buffer(const char *plain, int plain_len, char cipher[], const int aes_len)
{
  DBUG_ENTER("encrypt_buffer");

  if (my_aes_encrypt((const unsigned char *) plain, plain_len,
                     (unsigned char *) cipher,
                     (const unsigned char *) my_key, LOGIN_KEY_LEN,
                     my_aes_128_ecb, NULL) == aes_len)
    DBUG_RETURN(aes_len);

  verbose_msg("Error! Couldn't encrypt the buffer.\n");
  DBUG_RETURN(-1);                              /* Error */
}


/**
  Decrypt the given cipher text.

  @param cipher     [in]  Cipher text to be decrypted.
  @param cipher_len [in]  Length of the cipher text.
  @param plain      [out] Decrypted plain text.

  @return                 -1 if error encountered,
                          length decrypted, otherwise.
*/

static int decrypt_buffer(const char *cipher, int cipher_len, char plain[])
{
  DBUG_ENTER("decrypt_buffer");
  int aes_length;

  if ((aes_length= my_aes_decrypt((const unsigned char *) cipher, cipher_len,
                                  (unsigned char *) plain,
                                  (const unsigned char *) my_key,
                                  LOGIN_KEY_LEN,
                                  my_aes_128_ecb, NULL)) > 0)
    DBUG_RETURN(aes_length);

  verbose_msg("Error! Couldn't decrypt the buffer.\n");
  DBUG_RETURN(-1);                              /* Error */
}


/**
  Add unused bytes alongwith the to the login key
  to the login file.

  @return                 -1 if error encountered,
                          length written, otherwise.
*/

static int add_header(void)
{
  DBUG_ENTER("add_header");

  /* Reserved for future use. */
  const char unused[]= {'\0','\0','\0','\0'};

  /* Write 'unused' bytes first. */
  if ((my_write(g_fd, (const uchar *) unused, 4, MYF(MY_WME))) != 4)
    goto error;

  /* Write the login key. */
  if ((my_write(g_fd, (const uchar *)my_key, LOGIN_KEY_LEN, MYF(MY_WME)))
      != LOGIN_KEY_LEN)
    goto error;

  verbose_msg("Key successfully written to the file.\n");
  DBUG_RETURN(MY_LOGIN_HEADER_LEN);

error:
  my_perror("file write operation failed");
  DBUG_RETURN(-1);
}


/**
  Algorithm to generate key.
*/

void generate_login_key()
{
  DBUG_ENTER("generate_login_key");
  struct rand_struct rnd;

  verbose_msg("Generating a new key.\n");
  /* Get a sequence of random non-printable ASCII */
  for (uint i= 0; i < LOGIN_KEY_LEN; i++)
    my_key[i]= (char)((int)(my_rnd_ssl(&rnd) * 100000) % 32);

  DBUG_VOID_RETURN;
}

/**
  Read the stored login key.

  @return -1              Error
           0              Success
*/

static int read_login_key(void)
{
  DBUG_ENTER("read_login_key");

  verbose_msg("Reading the login key.\n");
  /* Move past the unused buffer. */
  if (my_seek(g_fd, 4, SEEK_SET, MYF(MY_WME)) != 4)
    goto error;                                 /* Error while seeking. */

  if (my_read(g_fd, (uchar *)my_key, LOGIN_KEY_LEN, MYF(MY_WME))
      != LOGIN_KEY_LEN)
    goto error;                                 /* Error while reading. */

  verbose_msg("Login key read successfully.\n");
  DBUG_RETURN(0);

error:
  my_perror("file read operation failed");
  DBUG_RETURN(-1);
}


static void verbose_msg(const char *fmt, ...)
{
  DBUG_ENTER("verbose_msg");
  va_list args;

  if (!opt_verbose)
    DBUG_VOID_RETURN;

  va_start(args, fmt);
  vfprintf(stderr, fmt, args);
  va_end(args);
  fflush(stderr);

  DBUG_VOID_RETURN;
}

static void my_perror(const char *msg)
{
  char errbuf[MYSYS_STRERROR_SIZE];

  if (errno == 0)
    fprintf(stderr, "%s\n", (msg) ? msg : "");
  else
    fprintf(stderr, "%s : %s\n", (msg) ? msg : "",
            my_strerror(errbuf, sizeof(errbuf), errno));
  // reset errno
  errno= 0;
}

static void usage_command(int command)
{
  print_version();
  puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2012"));
  puts("MySQL Configuration Utility.");
  printf("\nDescription: %s\n", command_data[command].description);
  printf("Usage: %s [program options] [%s [command options]]\n",
         my_progname, command_data[command].name);
  my_print_help(command_data[command].options);
  my_print_variables(command_data[command].options);
}


static void usage_program(void)
{
  print_version();
  puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2012"));
  puts("MySQL Configuration Utility.");
  printf("Usage: %s [program options] [command [command options]]\n",
         my_progname);
  my_print_help(my_program_long_options);
  my_print_variables(my_program_long_options);
  puts("\nWhere command can be any one of the following :\n\
       set [command options]     Sets user name/password/host name/socket/port\n\
                                 for a given login path (section).\n\
       remove [command options]  Remove a login path from the login file.\n\
       print [command options]   Print all the options for a specified\n\
                                 login path.\n\
       reset [command options]   Deletes the contents of the login file.\n\
       help                      Display this usage/help information.\n");
}


static void print_version(void) {
  printf ("%s Ver %s Distrib %s, for %s on %s\n", my_progname,
          MYSQL_CONFIG_EDITOR_VERSION, MYSQL_SERVER_VERSION,
          SYSTEM_TYPE, MACHINE_TYPE);
}

