/*
 * 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;
}
