/* Copyright (c) 2016, 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, version 2.0,
   as published by the Free Software Foundation.

   This program is also distributed with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have included with MySQL.

   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, version 2.0, 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 */

#define MYSQL_SERVER
#include <my_global.h>
#include <boost/optional/optional.hpp>
#include <sql_class.h> // THD

#define MAX_KEYRING_UDF_KEY_LENGTH_IN_BITS 16384
#define MAX_KEYRING_UDF_KEY_TEXT_LENGTH MAX_KEYRING_UDF_KEY_LENGTH_IN_BITS/8
#define KEYRING_UDF_KEY_TYPE_LENGTH 3

#ifdef WIN32
#define PLUGIN_EXPORT extern "C" __declspec(dllexport)
#else
#define PLUGIN_EXPORT extern "C"
#endif

static my_bool is_keyring_udf_initialized= FALSE;

static int keyring_udf_init(void *p)
{
  DBUG_ENTER("keyring_udf_init");
  is_keyring_udf_initialized= TRUE;
  DBUG_RETURN(0);
}

static int keyring_udf_deinit(void *p)
{
  DBUG_ENTER("keyring_udf_deinit");
  is_keyring_udf_initialized= FALSE;
  DBUG_RETURN(0);
}

struct st_mysql_daemon keyring_udf_decriptor=
{ MYSQL_DAEMON_INTERFACE_VERSION  };

/*
  Plugin library descriptor
*/

mysql_declare_plugin(keyring_udf)
{
  MYSQL_DAEMON_PLUGIN,
  &keyring_udf_decriptor,
  "keyring_udf",
  "Oracle Corporation",
  "Keyring UDF plugin",
  PLUGIN_LICENSE_GPL,
  keyring_udf_init,           /* Plugin Init */
  keyring_udf_deinit,         /* Plugin Deinit */
  0x0100 /* 1.0 */,
  NULL,                       /* status variables                */
  NULL,                       /* system variables                */
  NULL,                       /* config options                  */
  0,                          /* flags                           */
}
mysql_declare_plugin_end;

static my_bool get_current_user(std::string *current_user)
{
  THD *thd= current_thd;
  MYSQL_SECURITY_CONTEXT sec_ctx;
  LEX_CSTRING user, host;

  if (thd_get_security_context(thd, &sec_ctx) ||
      security_context_get_option(sec_ctx, "priv_user", &user) ||
      security_context_get_option(sec_ctx, "priv_host", &host))
    return TRUE;

  if(user.length)
    current_user->append(user.str, user.length);
  DBUG_ASSERT(host.length);
  current_user->append("@").append(host.str, host.length);

  return FALSE;
}

enum what_to_validate
{
  VALIDATE_KEY= 1,
  VALIDATE_KEY_ID= 2,
  VALIDATE_KEY_TYPE= 4,
  VALIDATE_KEY_LENGTH= 8
};

uint get_args_count_from_validation_request(int to_validate)
{
  uint args_count= 0;

  //Since to_validate is a bit mask - count the number of bits set
  for(; to_validate; to_validate >>= 1)
    if(to_validate & 1)
      ++args_count;

  return args_count;
}

static my_bool validate(UDF_ARGS *args, uint expected_arg_count,
                        int to_validate, char *message)
{
  THD *thd= current_thd;
  MYSQL_SECURITY_CONTEXT sec_ctx;
  my_svc_bool has_current_user_execute_privilege= 0;

  if(is_keyring_udf_initialized == FALSE)
  {
    strcpy(message, "This function requires keyring_udf plugin which is not installed."
                    " Please install keyring_udf plugin and try again.");
    return TRUE;
  }

  if (thd_get_security_context(thd, &sec_ctx) ||
      security_context_get_option(sec_ctx, "privilege_execute",
                                  &has_current_user_execute_privilege))
    return TRUE;

  if (has_current_user_execute_privilege == FALSE)
  {
    strcpy(message, "The user is not privileged to execute this function. "
                    "User needs to have EXECUTE permission.");
    return TRUE;
  }

  if (args->arg_count != expected_arg_count)
  {
    strcpy(message, "Mismatch in number of arguments to the function.");
    return TRUE;
  }

  if (to_validate & VALIDATE_KEY_ID && (args->args[0] == NULL ||
      args->arg_type[0] != STRING_RESULT))
  {
    strcpy(message, "Mismatch encountered. A string argument is expected "
      "for key id.");
    return TRUE;
  }

  if (to_validate & VALIDATE_KEY_TYPE && (args->args[1] == NULL ||
      args->arg_type[1] != STRING_RESULT))
  {
    strcpy(message, "Mismatch encountered. A string argument is expected "
      "for key type.");
    return TRUE;
  }

  if (to_validate & VALIDATE_KEY_LENGTH)
  {
    if(args->args[2] == NULL || args->arg_type[2] != INT_RESULT)
    {
      strcpy(message, "Mismatch encountered. An integer argument is expected "
        "for key length.");
      return TRUE;
    }
    long long key_length= *reinterpret_cast<long long*>(args->args[2]);

    if (key_length > MAX_KEYRING_UDF_KEY_TEXT_LENGTH)
    {
      sprintf(message, "%s%d", "The key is to long. The max length of the key is ",
        MAX_KEYRING_UDF_KEY_TEXT_LENGTH);
      return TRUE;
    }
  }

  if (to_validate & VALIDATE_KEY &&
      (args->args[2] == NULL || args->arg_type[2] != STRING_RESULT))
  {
    strcpy(message, "Mismatch encountered. A string argument is expected "
      "for key.");
    return TRUE;
  }
  return FALSE;
}

my_bool keyring_udf_func_init(UDF_INIT *initid, UDF_ARGS *args, char *message,
                              int to_validate,
                              const boost::optional<size_t> max_lenth_to_return,
                              const size_t size_of_memory_to_allocate)
{
  initid->ptr= NULL;
  uint expected_arg_count= get_args_count_from_validation_request(to_validate);

  if (validate(args, expected_arg_count , to_validate, message))
    return TRUE;

  if (max_lenth_to_return)
    initid->max_length= *max_lenth_to_return; //if no max_length_to_return passed to the function
                                              //it means that max_length stays default
  initid->maybe_null= 1;

  if (size_of_memory_to_allocate != 0)
  {
    initid->ptr= new(std::nothrow) char[size_of_memory_to_allocate];
    if (initid->ptr == NULL)
      return TRUE;
    else
      memset(initid->ptr, 0, size_of_memory_to_allocate);
  }

  return FALSE;
}

PLUGIN_EXPORT
my_bool keyring_key_store_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
  return keyring_udf_func_init(initid, args, message, (VALIDATE_KEY_ID | VALIDATE_KEY_TYPE | VALIDATE_KEY), 1, 0);
}

PLUGIN_EXPORT
void keyring_key_store_deinit(UDF_INIT *initid)
{}

/**
  Implementation of the UDF:
  INT keyring_key_store(STRING key_id, STRING key_type, STRING key)
  @return 1 on success, NULL and error on failure
*/
PLUGIN_EXPORT
long long keyring_key_store(UDF_INIT *initid, UDF_ARGS *args, char *is_null,
                            char *error)
{
  std::string current_user;

  if(get_current_user(&current_user))
  {
    *error= 1;
    return 0;
  }

  if(my_key_store(args->args[0], args->args[1], current_user.c_str(),
                  args->args[2], strlen(args->args[2])))
  {
    my_error(ER_KEYRING_UDF_KEYRING_SERVICE_ERROR, MYF(0), "keyring_key_store");
    *error= 1;
    return 0;
  }

  // For the UDF 1 == success, 0 == failure.
  return 1;
}

static my_bool fetch(const char* function_name, char *key_id, char **a_key,
                     char **a_key_type, size_t *a_key_len)
{
  std::string current_user;
  if (get_current_user(&current_user))
    return TRUE;

  char *key_type= NULL, *key= NULL;
  size_t key_len= 0;

  if (my_key_fetch(key_id, &key_type, current_user.c_str(),
                   reinterpret_cast<void**>(&key), &key_len))
  {
    my_error(ER_KEYRING_UDF_KEYRING_SERVICE_ERROR, MYF(0), function_name);

    if (key != NULL)
      my_free(key);
    if (key_type != NULL)
      my_free(key_type);
    return TRUE;
  }

  DBUG_ASSERT((key == NULL && key_len == 0) || (key != NULL &&
            key_len <= MAX_KEYRING_UDF_KEY_TEXT_LENGTH && key_type != NULL &&
            strlen(key_type) <= KEYRING_UDF_KEY_TYPE_LENGTH));

  if (a_key != NULL)
    *a_key= key;
  else
    my_free(key);
  if (a_key_type != NULL)
    *a_key_type= key_type;
  else
    my_free(key_type);
  if (a_key_len != NULL)
    *a_key_len= key_len;

  return FALSE;
}

PLUGIN_EXPORT
my_bool keyring_key_fetch_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
 return keyring_udf_func_init(initid, args, message, VALIDATE_KEY_ID,
                               MAX_KEYRING_UDF_KEY_TEXT_LENGTH, MAX_KEYRING_UDF_KEY_TEXT_LENGTH);
}


PLUGIN_EXPORT
void keyring_key_fetch_deinit(UDF_INIT *initid)
{
  if (initid->ptr)
  {
    delete[] initid->ptr;
    initid->ptr= NULL;
  }
}

/**
  Implementation of the UDF:
  STRING keyring_key_fetch(STRING key_id)
  @return key on success, NULL if key does not exist, NULL and error on failure
*/
PLUGIN_EXPORT
char *keyring_key_fetch(UDF_INIT *initid, UDF_ARGS *args, char *result,
                        unsigned long *length, char *is_null, char *error)
{
  char *key= NULL;
  size_t key_len= 0;

  if (fetch("keyring_key_fetch", args->args[0], &key, NULL, &key_len))
  {
    if (key != NULL)
      my_free(key);
    *error= 1;
    return NULL;
  }

  if (key != NULL)
  {
    memcpy(initid->ptr, key, key_len);
    my_free(key);
  }
  else
    *is_null= 1;

  *length= key_len;
  *error= 0;
  return initid->ptr;
}

PLUGIN_EXPORT
my_bool keyring_key_type_fetch_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
  return keyring_udf_func_init(initid, args, message, VALIDATE_KEY_ID,
                               KEYRING_UDF_KEY_TYPE_LENGTH, KEYRING_UDF_KEY_TYPE_LENGTH);
}


PLUGIN_EXPORT
void keyring_key_type_fetch_deinit(UDF_INIT *initid)
{
  if (initid->ptr)
  {
    delete[] initid->ptr;
    initid->ptr= NULL;
  }
}

/**
  Implementation of the UDF:
  STRING keyring_key_type_fetch(STRING key_id)
  @return key's type on success, NULL if key does not exist, NULL and error on failure
*/
PLUGIN_EXPORT
char *keyring_key_type_fetch(UDF_INIT *initid, UDF_ARGS *args, char *result,
                             unsigned long *length, char *is_null, char *error)
{
  char *key_type= NULL;
  if(fetch("keyring_key_type_fetch", args->args[0], NULL, &key_type, NULL))
  {
    if (key_type != NULL)
      my_free(key_type);
    *error= 1;
    return NULL;
  }

  if (key_type != NULL)
  {
    memcpy(initid->ptr, key_type, KEYRING_UDF_KEY_TYPE_LENGTH);
    *length= KEYRING_UDF_KEY_TYPE_LENGTH;
    my_free(key_type);
  }
  else
  {
    *is_null= 1;
    *length= 0;
  }

  *error= 0;
  return initid->ptr;
}

PLUGIN_EXPORT
my_bool keyring_key_length_fetch_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
  return keyring_udf_func_init(initid, args, message, VALIDATE_KEY_ID,
                               boost::none, 0);
}


PLUGIN_EXPORT
void keyring_key_length_fetch_deinit(UDF_INIT *initid)
{
  if (initid->ptr)
  {
    delete[] initid->ptr;
    initid->ptr= NULL;
  }
}

/**
  Implementation of the UDF:
  INT keyring_key_length_fetch(STRING key_id)
  @return key's length on success, NULL if key does not exist, NULL and error on failure
*/
PLUGIN_EXPORT
long long keyring_key_length_fetch(UDF_INIT *initid, UDF_ARGS *args, char *is_null,
                                   char *error)
{
  size_t key_len= 0;
  char* key= NULL;

  *error= fetch("keyring_key_length_fetch", args->args[0], &key, NULL, &key_len);

  if (*error == 0 && key == NULL)
    *is_null= 1;

  if(key != NULL)
    my_free(key);

  // For the UDF 0 == failure.
  return (*error) ? 0 : key_len;
}

PLUGIN_EXPORT
my_bool keyring_key_remove_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
  return keyring_udf_func_init(initid, args, message, VALIDATE_KEY_ID,
                               1, 0);
}

PLUGIN_EXPORT
void keyring_key_remove_deinit(UDF_INIT *initid)
{}

/**
  Implementation of the UDF:
  INT keyring_key_remove(STRING key_id)
  @return 1 on success, NULL on failure
*/
PLUGIN_EXPORT
long long keyring_key_remove(UDF_INIT *initid, UDF_ARGS *args, char *is_null,
                             char *error)
{
  std::string current_user;
  if (get_current_user(&current_user))
  {
    *error=1;
    return 0;
  }
  if (my_key_remove(args->args[0], current_user.c_str()))
  {
    my_error(ER_KEYRING_UDF_KEYRING_SERVICE_ERROR, MYF(0), "keyring_key_remove");
    *error= 1;
    return 0;
  }
  *error= 0;
  return 1;
}

PLUGIN_EXPORT
my_bool keyring_key_generate_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
  return keyring_udf_func_init(initid, args, message,
                               (VALIDATE_KEY_ID | VALIDATE_KEY_TYPE | VALIDATE_KEY_LENGTH),
                               1, 0);
}

PLUGIN_EXPORT
void keyring_key_generate_deinit(UDF_INIT *initid)
{}

/**
  Implementation of the UDF:
  STRING keyring_key_generate(STRING key_id, STRING key_type, INTEGER key_length)
  @return 1 on success, NULL and error on failure
*/
PLUGIN_EXPORT
long long keyring_key_generate(UDF_INIT *initid, UDF_ARGS *args, char *is_null,
                               char *error)
{
  std::string current_user;
  if (get_current_user(&current_user))
    return 0;

  long long key_length= *reinterpret_cast<long long*>(args->args[2]);

  if (my_key_generate(args->args[0], args->args[1], current_user.c_str(),
                      key_length))
  {
    my_error(ER_KEYRING_UDF_KEYRING_SERVICE_ERROR, MYF(0), "keyring_key_generate");
    *error= 1;
    // For the UDF 1 == success, 0 == failure.
    return 0;
  }
  return 1;
}
