/**
 *
 * Glewlwyd SSO Server
 *
 * Authentiation server
 * Users are authenticated via various backend available: database, ldap
 * Using various authentication methods available: password, OTP, send code, etc.
 *
 * Random code sent by e-mail authentication scheme module
 *
 * Copyright 2019-2020 Nicolas Mora <mail@babelouest.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU GENERAL PUBLIC LICENSE
 * License as published by the Free Software Foundation;
 * version 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include <string.h>
#include <jansson.h>
#include <yder.h>
#include <orcania.h>
#include <ulfius.h>
#include "glewlwyd-common.h"

#define GLEWLWYD_SCHEME_CODE_TABLE "gs_code"

#define GLEWLWYD_SCHEME_CODE_DEFAULT_LENGTH 6
#define GLEWLWYD_SCHEME_CODE_DURATION 900

struct _email_config {
  pthread_mutex_t insert_lock;
  json_t * j_parameters;
};

static int generate_new_code(struct config_module * config, json_t * j_param, const char * username, char * code, size_t len) {
  json_t * j_query;
  int res, ret;
  char * code_hash = NULL;

  j_query = json_pack("{sss{si}s{sssO}}",
                      "table",
                      GLEWLWYD_SCHEME_CODE_TABLE,
                      "set",
                        "gsc_enabled",
                        0,
                       "where",
                        "gsc_username",
                        username,
                        "gsc_mod_name",
                        json_object_get(j_param, "mod_name"));
  res = h_delete(config->conn, j_query, NULL);
  json_decref(j_query);
  if (res == H_OK) {
    if (rand_code(code, len)) {
      if ((code_hash = generate_hash(config->hash_algorithm, code)) != NULL) {
        j_query = json_pack("{sss{sOssss}}",
                            "table",
                            GLEWLWYD_SCHEME_CODE_TABLE,
                            "values",
                              "gsc_mod_name",
                              json_object_get(j_param, "mod_name"),
                              "gsc_username",
                              username,
                              "gsc_code_hash",
                              code_hash);
        res = h_insert(config->conn, j_query, NULL);
        json_decref(j_query);
        if (res == H_OK) {
          ret = G_OK;
        } else {
          y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_code - Error executing j_query (1)");
          config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL);
          ret = G_ERROR_DB;
        }
        o_free(code_hash);
      } else {
        y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_code - Error generate_hash");
        ret = G_ERROR;
      }
    } else {
      y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_code - Error rand_code");
      ret = G_ERROR;
    }
  } else {
    y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_code - Error executing j_query (2)");
    config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL);
    ret = G_ERROR_DB;
  }
  return ret;
}

static int check_code(struct config_module * config, json_t * j_param, const char * username, const char * code) {
  json_t * j_query, * j_result;
  int res, ret;
  char * code_hash = NULL, * issued_at_clause = NULL;
  time_t now;

  if ((code_hash = generate_hash(config->hash_algorithm, code)) != NULL) {
    time(&now);
    if (config->conn->type==HOEL_DB_TYPE_MARIADB) {
      issued_at_clause = msprintf("> FROM_UNIXTIME(%ld)", (now - (time_t)json_integer_value(json_object_get(j_param, "code-duration"))));
    } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) {
      issued_at_clause = msprintf("> TO_TIMESTAMP(%ld)", (now - (time_t)json_integer_value(json_object_get(j_param, "code-duration"))));
    } else { // HOEL_DB_TYPE_SQLITE
      issued_at_clause = msprintf("> %ld", (now - (time_t)json_integer_value(json_object_get(j_param, "code-duration"))));
    }
    j_query = json_pack("{sss{sOsssssis{ssss}}}",
                        "table",
                        GLEWLWYD_SCHEME_CODE_TABLE,
                        "where",
                          "gsc_mod_name",
                          json_object_get(j_param, "mod_name"),
                          "gsc_username",
                          username,
                          "gsc_code_hash",
                          code_hash,
                          "gsc_enabled",
                          1,
                          "gsc_issued_at",
                            "operator",
                            "raw",
                            "value",
                            issued_at_clause);
    res = h_select(config->conn, j_query, &j_result, NULL);
    o_free(issued_at_clause);
    json_decref(j_query);
    if (res == H_OK) {
      if (json_array_size(j_result)) {
        j_query = json_pack("{sss{si}s{sOssss}}",
                            "table",
                            GLEWLWYD_SCHEME_CODE_TABLE,
                            "set",
                              "gsc_enabled",
                              0,
                             "where",
                               "gsc_mod_name",
                               json_object_get(j_param, "mod_name"),
                               "gsc_username",
                               username,
                               "gsc_code_hash",
                               code_hash);
        res = h_update(config->conn, j_query, NULL);
        json_decref(j_query);
        if (res == H_OK) {
          ret = G_OK;
        } else {
          y_log_message(Y_LOG_LEVEL_ERROR, "check_code - Error executing j_query (2)");
          config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL);
          ret = G_ERROR_DB;
        }
      } else {
        ret = G_ERROR_UNAUTHORIZED;
      }
      json_decref(j_result);
    } else {
      y_log_message(Y_LOG_LEVEL_ERROR, "check_code - Error executing j_query (1)");
      config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL);
      ret = G_ERROR_DB;
    }
    o_free(code_hash);
  } else {
    y_log_message(Y_LOG_LEVEL_ERROR, "check_code - Error generate_hash");
    ret = G_ERROR;
  }
  return ret;
}

static json_t * is_scheme_parameters_valid(json_t * j_params) {
  json_t * j_errors = json_array(), * j_result, * j_template = NULL;
  const char * lang = NULL;
  int nb_default_lang = 0;

  if (j_errors != NULL) {
    if (!json_is_object(j_params)) {
      json_array_append_new(j_errors, json_string("parameters must be a JSON object"));
    } else {
      if (json_object_get(j_params, "code-length") != NULL && (!json_is_integer(json_object_get(j_params, "code-length")) || json_integer_value(json_object_get(j_params, "code-length")) <= 0)) {
        json_array_append_new(j_errors, json_string("code-length is optional and must be a positive integer"));
      } else if (json_object_get(j_params, "code-length") == NULL) {
        json_object_set_new(j_params, "code-length", json_integer(GLEWLWYD_SCHEME_CODE_DEFAULT_LENGTH));
      }
      if (json_object_get(j_params, "code-duration") != NULL && (!json_is_integer(json_object_get(j_params, "code-duration")) || json_integer_value(json_object_get(j_params, "code-duration")) <= 0)) {
        json_array_append_new(j_errors, json_string("code-duration is optional and must be a positive integer"));
      } else if (json_object_get(j_params, "code-duration") == NULL) {
        json_object_set_new(j_params, "code-duration", json_integer(GLEWLWYD_SCHEME_CODE_DEFAULT_LENGTH));
      }
      if (json_string_null_or_empty(json_object_get(j_params, "host"))) {
        json_array_append_new(j_errors, json_string("host is mandatory and must be a non empty string"));
      }
      if (json_object_get(j_params, "port") != NULL && (!json_is_integer(json_object_get(j_params, "port")) || json_integer_value(json_object_get(j_params, "port")) < 0 || json_integer_value(json_object_get(j_params, "port")) > 65535)) {
        json_array_append_new(j_errors, json_string("port is optional and must be a integer between 0 and 65535"));
      } else if (json_object_get(j_params, "port") == NULL) {
        json_object_set_new(j_params, "port", json_integer(0));
      }
      if (json_object_get(j_params, "use-tls") != NULL && !json_is_boolean(json_object_get(j_params, "use-tls"))) {
        json_array_append_new(j_errors, json_string("use-tls is optional and must be a boolean"));
      }
      if (json_object_get(j_params, "check-certificate") != NULL && !json_is_boolean(json_object_get(j_params, "check-certificate"))) {
        json_array_append_new(j_errors, json_string("check-certificate is optional and must be a boolean"));
      }
      if (json_object_get(j_params, "user") != NULL && !json_is_string(json_object_get(j_params, "user"))) {
        json_array_append_new(j_errors, json_string("user is optional and must be a string"));
      }
      if (json_object_get(j_params, "password") != NULL && !json_is_string(json_object_get(j_params, "password"))) {
        json_array_append_new(j_errors, json_string("password is optional and must be a string"));
      }
      if (json_string_null_or_empty(json_object_get(j_params, "from"))) {
        json_array_append_new(j_errors, json_string("from is mandatory and must be a non empty string"));
      }
      if (json_object_get(j_params, "templates") == NULL) {
        if (json_object_get(j_params, "subject") != NULL && json_string_null_or_empty(json_object_get(j_params, "subject"))) {
          json_array_append_new(j_errors, json_string("subject is mandatory and must be a non empty string"));
        }
        if (json_object_get(j_params, "body-pattern") != NULL && json_string_null_or_empty(json_object_get(j_params, "body-pattern"))) {
          json_array_append_new(j_errors, json_string("body-pattern is mandatory and must be a non empty string"));
        }
      } else {
        if (json_object_get(j_params, "content-type") != NULL && json_string_null_or_empty(json_object_get(j_params, "content-type"))) {
          json_array_append_new(j_errors, json_string("content-type is optional and must be a string"));
        }
        if (json_string_null_or_empty(json_object_get(j_params, "user-lang-property"))) {
          json_array_append_new(j_errors, json_string("user-lang-property is mandatory and must be a non empty string"));
        }
        if (!json_is_object(json_object_get(j_params, "templates"))) {
          json_array_append_new(j_errors, json_string("templates is mandatory and must be a JSON object"));
        } else {
          json_object_foreach(json_object_get(j_params, "templates"), lang, j_template) {
            if (!json_is_object(j_template)) {
              json_array_append_new(j_errors, json_string("template content must be a JSON object"));
            } else {
              if (!json_is_boolean(json_object_get(j_template, "defaultLang"))) {
                json_array_append_new(j_errors, json_string("defaultLang is madatory in a template and must be a JSON object"));
              }
              if (json_string_null_or_empty(json_object_get(j_template, "subject"))) {
                json_array_append_new(j_errors, json_string("subject is mandatory for default lang and must be a non empty string"));
              }
              if (json_object_get(j_template, "body") != NULL && json_string_null_or_empty(json_object_get(j_template, "body"))) {
                json_array_append_new(j_errors, json_string("body is mandatory for default lang and must be a non empty string"));
              }
              if (json_object_get(j_template, "defaultLang") == json_true()) {
                nb_default_lang++;
              }
            }
          }
          if (nb_default_lang != 1) {
            json_array_append_new(j_errors, json_string("template list must have only one defaultLang set to true"));
          }
        }
      }
    }
    if (json_array_size(j_errors)) {
      j_result = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_errors);
    } else {
      j_result = json_pack("{si}", "result", G_OK);
    }
    json_decref(j_errors);
  } else {
    y_log_message(Y_LOG_LEVEL_ERROR, "is_scheme_parameters_valid - Error allocating resources for j_errors");
    j_result = json_pack("{si}", "result", G_ERROR_MEMORY);
  }
  return j_result;
}

static const char * get_template_property(json_t * j_params, json_t * j_user, const char * property_field) {
  json_t * j_template = NULL;
  const char * property = NULL, * property_default = NULL, * lang = NULL, * user_lang = json_string_value(json_object_get(j_user, json_string_value(json_object_get(j_params, "user-lang-property"))));

  if (json_object_get(j_params, "templates") == NULL) {
    property = json_string_value(json_object_get(j_params, property_field));
  } else {
    json_object_foreach(json_object_get(j_params, "templates"), lang, j_template) {
      if (0 == o_strcmp(user_lang, lang)) {
        property = json_string_value(json_object_get(j_template, property_field));
      }
      if (json_object_get(j_template, "defaultLang") == json_true()) {
        property_default = json_string_value(json_object_get(j_template, property_field));
      }
    }
    if (property == NULL) {
      property = property_default;
    }
  }
  return property;
}

/**
 *
 * user_auth_scheme_module_load
 *
 * Executed once when Glewlwyd service is loaded
 * Used to identify the module and to show its parameters on init
 * You can also use it to load resources that are required once for all
 * instance modules for example
 *
 * @return value: a json_t * value with the following pattern:
 *                {
 *                  result: number (G_OK on success, another value on error)
 *                  name: string, mandatory, name of the module, must be unique among other scheme modules
 *                  display_name: string, optional, long name of the module
 *                  description: string, optional, description for the module
 *                  parameters: object, optional, parameters description for the module
 *                }
 *
 *                Example:
 *                {
 *                  result: G_OK,
 *                  name: "mock",
 *                  display_name: "Mock scheme module",
 *                  description: "Mock scheme module for glewlwyd tests",
 *                  parameters: {
 *                    mock-value: {
 *                      type: "string",
 *                      mandatory: true
 *                    }
 *                  }
 *                }
 *
 * @parameter config: a struct config_module with acess to some Glewlwyd
 *                    service and data
 *
 */
json_t * user_auth_scheme_module_load(struct config_module * config) {
  UNUSED(config);
  return json_pack("{sissssss}",
                   "result", G_OK,
                   "name", "email",
                   "display_name", "Email code",
                   "description", "Send a code via email to authenticate the user");
}

/**
 *
 * user_auth_scheme_module_unload
 *
 * Executed once when Glewlwyd service is stopped
 * You can also use it to release resources that are required once for all
 * instance modules for example
 *
 * @return value: G_OK on success, another value on error
 *
 * @parameter config: a struct config_module with acess to some Glewlwyd
 *                    service and data
 *
 */
int user_auth_scheme_module_unload(struct config_module * config) {
  UNUSED(config);
  return G_OK;
}

/**
 *
 * user_auth_scheme_module_init
 *
 * Initialize an instance of this module declared in Glewlwyd service.
 * If required, you must dynamically allocate a pointer to the configuration
 * for this instance and pass it to *cls
 *
 * @return value: a json_t * value with the following pattern:
 *                {
 *                  result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error)
 *                  error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise
 *                }
 *
 * @parameter config: a struct config_module with acess to some Glewlwyd
 *                    service and data
 * @parameter j_parameters: used to initialize an instance in JSON format
 *                          The module must validate itself its parameters
 * @parameter mod_name: module name in glewlwyd service
 * @parameter cls: must return an allocated void * pointer that will be sent back
 *                 as void * in all module functions
 *
 */
json_t * user_auth_scheme_module_init(struct config_module * config, json_t * j_parameters, const char * mod_name, void ** cls) {
  UNUSED(config);
  json_t * j_result, * j_return;
  char * str_error;
  pthread_mutexattr_t mutexattr;

  j_result = is_scheme_parameters_valid(j_parameters);
  if (check_result_value(j_result, G_OK)) {
    
    json_object_set_new(j_parameters, "mod_name", json_string(mod_name));
    *cls = o_malloc(sizeof(struct _email_config));
    if (*cls != NULL) {
      ((struct _email_config *)*cls)->j_parameters = json_incref(j_parameters);
      pthread_mutexattr_init ( &mutexattr );
      pthread_mutexattr_settype( &mutexattr, PTHREAD_MUTEX_RECURSIVE );
      if (!pthread_mutex_init(&((struct _email_config *)*cls)->insert_lock, &mutexattr)) {
        j_return = json_pack("{si}", "result", G_OK);
      } else {
        y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init email - Error pthread_mutex_init");
        j_return = json_pack("{siss}", "result", G_ERROR, "error", "Internal error");
      }
    } else {
      y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init email - Error allocating cls");
      j_return = json_pack("{siss}", "result", G_ERROR, "error", "Internal error");
    }
  } else {
    y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init email - Error in parameters");
    str_error = json_dumps(json_object_get(j_result, "error"), JSON_ENCODE_ANY);
    y_log_message(Y_LOG_LEVEL_ERROR, str_error);
    o_free(str_error);
    j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error"));
  }
  json_decref(j_result);
  return j_return;
}

/**
 *
 * user_auth_scheme_module_close
 *
 * Close an instance of this module declared in Glewlwyd service.
 * You must free the memory previously allocated in
 * the user_auth_scheme_module_init function as void * cls
 *
 * @return value: G_OK on success, another value on error
 *
 * @parameter config: a struct config_module with acess to some Glewlwyd
 *                    service and data
 * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init
 *
 */
int user_auth_scheme_module_close(struct config_module * config, void * cls) {
  UNUSED(config);
  json_decref(((struct _email_config *)cls)->j_parameters);
  pthread_mutex_destroy(&((struct _email_config *)cls)->insert_lock);
  o_free(cls);
  return G_OK;
}

/**
 *
 * user_auth_scheme_module_can_use
 *
 * Validate if the user is allowed to use this scheme prior to the
 * authentication or registration
 *
 * @return value: GLEWLWYD_IS_REGISTERED - User can use scheme and has registered
 *                GLEWLWYD_IS_AVAILABLE - User can use scheme but hasn't registered
 *                GLEWLWYD_IS_NOT_AVAILABLE - User can't use scheme
 *
 * @parameter config: a struct config_module with acess to some Glewlwyd
 *                    service and data
 * @parameter username: username to identify the user
 * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init
 *
 */
int user_auth_scheme_module_can_use(struct config_module * config, const char * username, void * cls) {
  UNUSED(cls);
  json_t * j_user;
  int ret;

  j_user = config->glewlwyd_module_callback_get_user(config, username);
  if (check_result_value(j_user, G_OK)) {
    ret = json_object_get(json_object_get(j_user, "user"), "email") != NULL?GLEWLWYD_IS_REGISTERED:GLEWLWYD_IS_NOT_AVAILABLE;
  } else {
    y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_can_use mail - Error glewlwyd_module_callback_get_user");
    ret = GLEWLWYD_IS_NOT_AVAILABLE;
  }
  json_decref(j_user);
  return ret;
}

/**
 *
 * user_auth_scheme_module_register
 *
 * Register the scheme for a user
 * Ex: add a certificate, add new TOTP values, etc.
 *
 * @return value: a json_t * value with the following pattern:
 *                {
 *                  result: number (G_OK on success, another value on error)
 *                  updated: boolean (true if the scheme has been registered or updated, optional)
 *                  response: JSON object, optional
 *                }
 *
 * @parameter config: a struct config_module with acess to some Glewlwyd
 *                    service and data
 * @parameter http_request: the original struct _u_request from the HTTP API
 * @parameter username: username to identify the user
 * @parameter j_scheme_data: additional data used to register the scheme for the user
 *                           in JSON format
 * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init
 *
 */
json_t * user_auth_scheme_module_register(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) {
  UNUSED(config);
  UNUSED(http_request);
  UNUSED(j_scheme_data);
  return json_pack("{si}", "result", (user_auth_scheme_module_can_use(config, username, cls) == GLEWLWYD_IS_REGISTERED?G_OK:G_ERROR_PARAM));
}

/**
 *
 * user_auth_scheme_module_register_get
 *
 * Get the registration value(s) of the scheme for a user
 *
 * @return value: a json_t * value with the following pattern:
 *                {
 *                  result: number (G_OK on success, another value on error)
 *                  response: JSON object, optional
 *                }
 *
 * @parameter config: a struct config_module with acess to some Glewlwyd
 *                    service and data
 * @parameter http_request: the original struct _u_request from the API, must be casted to be available
 * @parameter username: username to identify the user
 * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init
 *
 */
json_t * user_auth_scheme_module_register_get(struct config_module * config, const struct _u_request * http_request, const char * username, void * cls) {
  UNUSED(config);
  UNUSED(http_request);
  return json_pack("{si}", "result", (user_auth_scheme_module_can_use(config, username, cls) == GLEWLWYD_IS_REGISTERED)?G_OK:G_ERROR_PARAM);
}

/**
 *
 * user_auth_scheme_module_deregister
 *
 * Deregister the scheme for a user
 * Ex: remove certificates, TOTP values, etc.
 *
 * @return value: G_OK on success, even if no data has been removed
 *                G_ERROR on another error
 *
 * @parameter config: a struct config_module with acess to some Glewlwyd
 *                    service and data
 * @parameter username: username to identify the user
 * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init
 *
 */
int user_auth_scheme_module_deregister(struct config_module * config, const char * username, void * cls) {
  UNUSED(config);
  UNUSED(username);
  UNUSED(cls);
  return G_OK;
}

/**
 *
 * user_auth_scheme_module_trigger
 *
 * Trigger the scheme for a user
 * Ex: send the code to a device, generate a challenge, etc.
 *
 * @return value: a json_t * value with the following pattern:
 *                {
 *                  result: number (G_OK on success, another value on error)
 *                  response: JSON object, optional
 *                }
 *
 * @parameter config: a struct config_module with acess to some Glewlwyd
 *                    service and data
 * @parameter http_request: the original struct _u_request from the API, must be casted to be available
 * @parameter username: username to identify the user
 * @parameter scheme_trigger: data sent to trigger the scheme for the user
 *                           in JSON format
 * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init
 *
 */
json_t * user_auth_scheme_module_trigger(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_trigger, void * cls) {
  UNUSED(j_scheme_trigger);
  json_t * j_user, * j_return;
  struct _email_config * email_config = (struct _email_config *)cls;
  int ret;
  char * code = NULL, * body;
  const char * ip_source = get_ip_source(http_request, config->glewlwyd_config->originating_ip_header);
  size_t code_length = (size_t)json_integer_value(json_object_get(email_config->j_parameters, "code-length"));

  if (user_auth_scheme_module_can_use(config, username, cls) == GLEWLWYD_IS_REGISTERED) {
    j_user = config->glewlwyd_module_callback_get_user(config, username);
    if (check_result_value(j_user, G_OK)) {
      if (!pthread_mutex_lock(&email_config->insert_lock)) {
        if ((code = o_malloc(((code_length*2) + 1)*sizeof(char))) != NULL) {
          memset(code, 0, ((code_length*2) + 1));
          if (generate_new_code(config, email_config->j_parameters, username, code, (code_length*2)) == G_OK) {
            if ((body = str_replace(get_template_property(email_config->j_parameters, json_object_get(j_user, "user"), "body-pattern"), "{CODE}", code+code_length)) != NULL) {
              if (ulfius_send_smtp_rich_email(json_string_value(json_object_get(email_config->j_parameters, "host")),
                                             (int)json_integer_value(json_object_get(email_config->j_parameters, "port")),
                                             json_object_get(email_config->j_parameters, "use-tls")==json_true()?1:0,
                                             json_object_get(email_config->j_parameters, "verify-certificate")==json_false()?0:1,
                                             !json_string_null_or_empty(json_object_get(email_config->j_parameters, "user"))?json_string_value(json_object_get(email_config->j_parameters, "user")):NULL,
                                             !json_string_null_or_empty(json_object_get(email_config->j_parameters, "password"))?json_string_value(json_object_get(email_config->j_parameters, "password")):NULL,
                                             json_string_value(json_object_get(email_config->j_parameters, "from")),
                                             json_string_value(json_object_get(json_object_get(j_user, "user"), "email")),
                                             NULL,
                                             NULL,
                                             !json_string_null_or_empty(json_object_get(email_config->j_parameters, "content-type"))?json_string_value(json_object_get(email_config->j_parameters, "content-type")):"text/plain; charset=utf-8",
                                             get_template_property(email_config->j_parameters, json_object_get(j_user, "user"), "subject"),
                                             body) == G_OK) {
                y_log_message(Y_LOG_LEVEL_WARNING, "Security - Scheme email - code sent for username %s at IP Address %s", username, ip_source);
                ret = G_OK;
              } else {
                y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger mail - Error ulfius_send_smtp_email");
                ret = G_ERROR_MEMORY;
              }
              o_free(body);
            } else {
              y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger mail - Error str_replace");
              ret = G_ERROR_MEMORY;
            }
          } else {
            y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger mail - Error generate_new_code");
            ret = G_ERROR_MEMORY;
          }
        } else {
          y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger mail - Error allocating resources for code");
          ret = G_ERROR_MEMORY;
        }
        pthread_mutex_unlock(&email_config->insert_lock);
      } else {
        y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger mail - Error pthread_mutex_lock");
        ret = G_ERROR;
      }
    } else {
      y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger mail - Error glewlwyd_module_callback_get_user");
      ret = G_ERROR;
    }
    json_decref(j_user);
  } else {
    ret = G_ERROR_PARAM;
  }
  if (ret == G_OK) {
    j_return = json_pack("{sis{ss%}}", "result", G_OK, "response", "prefix", code, code_length);
  } else {
    j_return = json_pack("{si}", "result", ret);
  }
  o_free(code);
  return j_return;
}

/**
 *
 * user_auth_scheme_module_validate
 *
 * Validate the scheme for a user
 * Ex: check the code sent to a device, verify the challenge, etc.
 *
 * @return value: G_OK on success
 *                G_ERROR_UNAUTHORIZED if validation fails
 *                G_ERROR_PARAM if error in parameters
 *                G_ERROR on another error
 *
 * @parameter config: a struct config_module with acess to some Glewlwyd
 *                    service and data
 * @parameter http_request: the original struct _u_request from the API, must be casted to be available
 * @parameter username: username to identify the user
 * @parameter j_scheme_data: data sent to validate the scheme for the user
 *                           in JSON format
 * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init
 *
 */
int user_auth_scheme_module_validate(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) {
  UNUSED(config);
  UNUSED(http_request);
  int ret, res;
  struct _email_config * email_config = (struct _email_config *)cls;

  if (user_auth_scheme_module_can_use(config, username, cls) != GLEWLWYD_IS_REGISTERED) {
    ret = G_ERROR_UNAUTHORIZED;
  } else if (json_object_get(j_scheme_data, "code") != NULL && json_is_string(json_object_get(j_scheme_data, "code")) && (unsigned int)(json_integer_value(json_object_get(email_config->j_parameters, "code-length"))*2) == json_string_length(json_object_get(j_scheme_data, "code"))) {
    if ((res = check_code(config, email_config->j_parameters, username, json_string_value(json_object_get(j_scheme_data, "code")))) == G_OK) {
      ret = G_OK;
    } else if (res == G_ERROR_UNAUTHORIZED) {
      ret = G_ERROR_UNAUTHORIZED;
    } else {
      y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate mail - Error check_code");
      ret = res;
    }
  } else {
    ret = G_ERROR_PARAM;
  }
  return ret;
}

/**
 *
 * user_auth_scheme_module_identify
 *
 * Identify the user using the scheme without the username to be previously given
 * This functionality isn't available for all schemes, because the scheme authentification
 * must be triggered without username and the authentication result must contain the username
 *
 * @return value: a json_t * value with the following pattern:
 *                {
 *                  result: number (G_OK on success, another value on error)
 *                  username: string value of the user identified - if the function is called within /auth
 *                  response: JSON object, optional - if the function is called within /auth/scheme/trigger
 *                }
 *
 * @parameter config: a struct config_module with acess to some Glewlwyd
 *                    service and data
 * @parameter http_request: the original struct _u_request from the API, must be casted to be available
 * @parameter j_scheme_data: data sent to validate the scheme for the user
 *                           in JSON format
 * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init
 *
 */
json_t * user_auth_scheme_module_identify(struct config_module * config, const struct _u_request * http_request, json_t * j_scheme_data, void * cls) {
  UNUSED(config);
  UNUSED(http_request);
  UNUSED(j_scheme_data);
  UNUSED(cls);
  return json_pack("{si}", "result", G_ERROR_UNAUTHORIZED);
}
