/*
 * Copyright (c) 2016 MariaDB Corporation Ab
 *
 * Use of this software is governed by the Business Source License included
 * in the LICENSE.TXT file and at www.mariadb.com/bsl11.
 *
 * Change Date: 2025-05-25
 *
 * On the date above, in accordance with the Business Source License, use
 * of this software will be governed by version 2 or later of the General
 * Public License.
 */

/**
 * @file cspasswd.cpp  - Implementation of pasword encoding
 *  Modified from MariaDB internal implementations
 */

#include <cstdio>
#include <getopt.h>
#include <termios.h>
#include <unistd.h>

#include <iostream>

#include "secrets.h"
#include "mcsconfig.h"

using std::cin;
using std::cout;
using std::endl;
using std::flush;
using std::string;

struct option options[] = {{"help", no_argument, nullptr, 'h'},
                           {"decrypt", no_argument, nullptr, 'd'},
                           {"interactive", no_argument, nullptr, 'i'},
                           {nullptr, 0, nullptr, 0}};

void print_usage(const char* executable, const char* directory)
{
  const char msg[] =
      R"(Usage: %s [-h|--help] [-i|--interactive] [-d|--decrypt] [path] password
Encrypt a Columnstore plaintext password using the encryption key in the key file
'%s'. The key file may be generated using the 'cskeys'-utility.
  -h, --help         Display this help.
  -d, --decrypt      Decrypt an encrypted password instead.
  -i, --interactive  - If cspasswd is reading from a pipe, it will read a line and
                       use that as the password.
                     - If cspasswd is connected to a terminal console, it will prompt
                       for the password.
                     If '-i' is specified, a single argument is assumed to be the path
                     and two arguments is treated like an error.
  path      The key file directory (default: '%s')
  password  The password to encrypt or decrypt
)";

  printf(msg, executable, SECRETS_FILENAME, directory);
}

bool read_password(string* pPassword)
{
  bool rv = false;
  string password;

  if (isatty(STDIN_FILENO))
  {
    struct termios tty;
    tcgetattr(STDIN_FILENO, &tty);

    bool echo = (tty.c_lflag & ECHO);
    if (echo)
    {
      tty.c_lflag &= ~ECHO;
      tcsetattr(STDIN_FILENO, TCSANOW, &tty);
    }

    cout << "Enter password : " << flush;
    string s1;
    std::getline(std::cin, s1);
    cout << endl;

    cout << "Repeat password: " << flush;
    string s2;
    std::getline(std::cin, s2);
    cout << endl;

    if (echo)
    {
      tty.c_lflag |= ECHO;
      tcsetattr(STDIN_FILENO, TCSANOW, &tty);
    }

    if (s1 == s2)
    {
      password = s1;
      rv = true;
    }
    else
    {
      cout << "Passwords are not identical." << endl;
    }
  }
  else
  {
    std::getline(std::cin, password);
    rv = true;
  }

  if (rv)
  {
    *pPassword = password;
  }

  return rv;
}

int main(int argc, char** argv)
{
  std::ios::sync_with_stdio();
  const string default_directory = string(MCSDATADIR);

  enum class Mode
  {
    ENCRYPT,
    DECRYPT
  };

  auto mode = Mode::ENCRYPT;
  bool interactive = false;

  int c;
  while ((c = getopt_long(argc, argv, "hdi", options, NULL)) != -1)
  {
    switch (c)
    {
      case 'h': print_usage(argv[0], default_directory.c_str()); return EXIT_SUCCESS;

      case 'd': mode = Mode::DECRYPT; break;

      case 'i': interactive = true; break;

      default: print_usage(argv[0], default_directory.c_str()); return EXIT_FAILURE;
    }
  }

  string input;
  string path = default_directory;

  switch (argc - optind)
  {
    case 2:
      // Two args provided.
      path = argv[optind];
      if (!interactive)
      {
        input = argv[optind + 1];
      }
      else
      {
        print_usage(argv[0], default_directory.c_str());
        return EXIT_FAILURE;
      }
      break;

    case 1:
      // One arg provided.
      if (!interactive)
      {
        input = argv[optind];
      }
      else
      {
        path = argv[optind];
      }
      break;

    case 0:
      if (!interactive)
      {
        print_usage(argv[0], default_directory.c_str());
        return EXIT_FAILURE;
      }
      break;

    default: print_usage(argv[0], default_directory.c_str()); return EXIT_FAILURE;
  }

  if (interactive)
  {
    if (!read_password(&input))
    {
      return EXIT_FAILURE;
    }
  }

  int rval = EXIT_FAILURE;

  string filepath = path;
  filepath.append("/").append(SECRETS_FILENAME);

  auto keydata = secrets_readkeys(filepath);
  if (keydata.ok)
  {
    bool encrypting = (mode == Mode::ENCRYPT);
    bool new_mode = keydata.iv.empty();  // false -> constant IV from file
    if (keydata.key.empty())
    {
      printf("Password encryption key file '%s' not found, cannot %s password.\n", filepath.c_str(),
             encrypting ? "encrypt" : "decrypt");
    }
    else if (encrypting)
    {
      string encrypted = new_mode ? encrypt_password(keydata.key, input)
                                  : encrypt_password_old(keydata.key, keydata.iv, input);
      if (!encrypted.empty())
      {
        printf("%s\n", encrypted.c_str());
        rval = EXIT_SUCCESS;
      }
      else
      {
        printf("Password encryption failed.\n");
      }
    }
    else
    {
      auto is_hex = std::all_of(input.begin(), input.end(), isxdigit);
      if (is_hex && input.length() % 2 == 0)
      {
        string decrypted = new_mode ? decrypt_password(keydata.key, input)
                                    : decrypt_password_old(keydata.key, keydata.iv, input);
        if (!decrypted.empty())
        {
          printf("%s\n", decrypted.c_str());
          rval = EXIT_SUCCESS;
        }
        else
        {
          printf("Password decryption failed.\n");
        }
      }
      else
      {
        printf("Input is not a valid hex-encoded encrypted password.\n");
      }
    }
  }
  else
  {
    printf("Could not read encryption key file '%s'.\n", filepath.c_str());
  }
  return rval;
}
