/*
 * Copyright (C) 2022 iopsys Software Solutions AB
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation
 *
 *      Author: Suvendhu Hansa <suvendhu.hansa@iopsys.eu>
 */

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE
#endif

#include <unistd.h>
#include <limits.h>
#include <pwd.h>
#include <shadow.h>
#include <openssl/evp.h>
#include <openssl/rand.h>

#include "users.h"
#include "helper.h"

extern struct list_head user_head;
extern struct list_head group_head;

enum group_operations {
	NAME_CHANGE,
	DELETE,
	__OP_MAX
};

typedef enum {
	Credentials_Good,
	Credentials_Bad_Requested_Username_Not_Supported,
	Credentials_Bad_Requested_Password_Incorrect,
	Credentials_Missing,
	Error_Invalid_Input,
	Error_Other
} e_cred_t;

static bool is_escape_char(char c)
{
	if (c == '\a' ||
	    c == '\b' ||
	    c == '\f' ||
	    c == '\t' ||
	    c == '\v' ||
	    c == '\r' ||
	    c == '\n')
		return true;

	return false;
}

static bool escape_char_exist(const char *val)
{
	bool ret = false;
	int len = DM_STRLEN(val);
	int i;

	if (len == 0)
		return ret;

	for (i = 0; i < len; i++) {
		if (is_escape_char(val[i])) {
			ret = true;
			break;
		}
	}

	return ret;
}

e_cred_t check_user_cred(const char *username, const char *password)
{
	char salt_hash[256] = {0};
	char cmd[512] = {0};
	char output[256] = {0};
	char *algo = NULL, *salt = NULL, *tmp = NULL;
	struct spwd *spwd;
	struct passwd *pwd;

	if (DM_STRLEN(username) == 0 || DM_STRLEN(password) == 0) {
		return Error_Invalid_Input;
	}

	/* Look up password and shadow password records for username */
	pwd = getpwnam(username);
	if (pwd == NULL) {
		return Credentials_Bad_Requested_Username_Not_Supported;
	}

	spwd = getspnam(username);
	if (spwd == NULL && errno == EACCES) {
		return Error_Other;
	}

	/* Use the shadow password */
	if (DM_STRLEN(spwd->sp_pwdp) == 0) {
		return Error_Other;
	}

	snprintf(salt_hash, sizeof(salt_hash), "%s", spwd->sp_pwdp);

	algo = salt_hash + 1;
	tmp = strchr(algo, '$');
	if (tmp == NULL) {
		return Error_Other;
	}
	*tmp = '\0';

	salt = tmp + 1;
	tmp = strchr(salt, '$');
	if (tmp == NULL) {
		return Error_Other;
	}
	*tmp = '\0';

	if (DM_STRCMP(algo, "1") != 0 && DM_STRCMP(algo, "5") != 0 && DM_STRCMP(algo, "6") != 0) {
		return Error_Other;
	}

	// Convert the resulting hash to a hexadecimal string
	snprintf(cmd, sizeof(cmd), "openssl passwd -%s -salt %s %s", algo, salt, password);

	FILE *pp = popen(cmd, "r"); // flawfinder: ignore
	if (pp != NULL) {
		fgets(output, sizeof(output), pp);
		pclose(pp);
	}

	strip_newline(output);

	if (DM_STRCMP(output, spwd->sp_pwdp) != 0) {
		return Credentials_Bad_Requested_Password_Incorrect;
	}

	return Credentials_Good;
}

static operation_args checkCred_args = {
	.in = (const char *[]) {
		"Username",
		"Password",
		NULL
	},
	.out = (const char *[]) {
		"Status",
		NULL
	}
};

static int get_operate_args_CheckCred(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = (char *)&checkCred_args;
	return 0;
}

static char *get_cred_result(e_cred_t ret)
{
	switch(ret) {
		case Credentials_Good:
			return "Credentials_Good";
		case Credentials_Bad_Requested_Username_Not_Supported:
			return "Credentials_Bad_Requested_Username_Not_Supported";
		case Credentials_Bad_Requested_Password_Incorrect:
			return "Credentials_Bad_Requested_Password_Incorrect";
		case Credentials_Missing:
			return "Credentials_Missing";
		case Error_Invalid_Input:
			return "Error_Invalid_Input";
		case Error_Other:
			return "Error_Other";
		default:
			return "Error_Other";
	}

	return "Error_Other";
}

static int operate_CheckCred(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	e_cred_t result = 0;

	char *username = dmjson_get_value((json_object *)value, 1, "Username");
	char *password = dmjson_get_value((json_object *)value, 1, "Password");

	result = check_user_cred(username, password);
	char *status = get_cred_result(result);
	fill_blob_param(&ctx->bb, "Status", status, DMT_TYPE[DMT_STRING], 0);

	return 0;
}
static void update_group_participation(const char *old_gr_name, const char *new_gr_name, int op)
{
	if (old_gr_name == NULL)
		return;

	struct uci_section *s = NULL, *stmp = NULL;
	bbf_uci_foreach_sections_safe("users", "user", stmp, s) {
		char *pch = NULL, *spch = NULL, lbuf[1024] = {0};
		unsigned pos = 0;
		char *group_part = NULL;

		bbf_uci_get_value_by_section(s, "member_groups", &group_part);
		if (DM_STRLEN(group_part) == 0)
			continue;

		switch (op) {
		case NAME_CHANGE:
			if (new_gr_name == NULL)
				return;

			memset(lbuf, 0, sizeof(lbuf));
			for (pch = strtok_r(group_part, ",", &spch); pch != NULL; pch = strtok_r(NULL, ",", &spch)) {
				if (strcmp(pch, old_gr_name) == 0) {
					pos += snprintf(&lbuf[pos], sizeof(lbuf) - pos, "%s,", new_gr_name);
				} else {
					pos += snprintf(&lbuf[pos], sizeof(lbuf) - pos, "%s,", pch);
				}
			}

			if (pos) {
				lbuf[pos - 1] = 0;
				bbf_uci_set_value_by_section(s, "member_groups", lbuf);
			}
			break;
		case DELETE:
			memset(lbuf, 0, sizeof(lbuf));
			for (pch = strtok_r(group_part, ",", &spch); pch != NULL; pch = strtok_r(NULL, ",", &spch)) {
				if (strcmp(pch, old_gr_name) != 0) {
					pos += snprintf(&lbuf[pos], sizeof(lbuf) - pos, "%s,", pch);
				}
			}

			if (pos)
				lbuf[pos - 1] = 0;

			bbf_uci_set_value_by_section(s, "member_groups", lbuf);
			break;
		}
	}
}

static void update_role_participation(const char *old_name, const char *new_name, int op, const char *sect_type)
{
	if (old_name == NULL || sect_type == NULL)
		return;

	// Update roles of users or groups
	struct uci_section *s = NULL, *stmp = NULL;
	bbf_uci_foreach_sections_safe("users", sect_type, stmp, s) {
		char *pch = NULL, *spch = NULL, lbuf[1024] = {0};
		unsigned pos = 0;
		char *role_part = NULL;

		bbf_uci_get_value_by_section(s, "member_roles", &role_part);
		if (DM_STRLEN(role_part) == 0)
			continue;

		switch (op) {
		case NAME_CHANGE:
			if (new_name == NULL)
				return;

			memset(lbuf, 0, sizeof(lbuf));
			for (pch = strtok_r(role_part, ",", &spch); pch != NULL; pch = strtok_r(NULL, ",", &spch)) {
				if (strcmp(pch, old_name) == 0) {
					pos += snprintf(&lbuf[pos], sizeof(lbuf) - pos, "%s,", new_name);
				} else {
					pos += snprintf(&lbuf[pos], sizeof(lbuf) - pos, "%s,", pch);
				}
			}

			if (pos) {
				lbuf[pos - 1] = 0;
				bbf_uci_set_value_by_section(s, "member_roles", lbuf);
			}
			break;
		case DELETE:
			memset(lbuf, 0, sizeof(lbuf));
			for (pch = strtok_r(role_part, ",", &spch); pch != NULL; pch = strtok_r(NULL, ",", &spch)) {
				if (strcmp(pch, old_name) != 0) {
					pos += snprintf(&lbuf[pos], sizeof(lbuf) - pos, "%s,", pch);
				}
			}

			if (pos)
				lbuf[pos - 1] = 0;

			bbf_uci_set_value_by_section(s, "member_roles", lbuf);
			break;
		}
	}
}

static void get_unique_role_id(char **id)
{
	unsigned int i;

	if (id == NULL)
		return;

	*id = NULL;

	for (i = 0; i <= 0xFFFFFFFFU; i++) {
		char str_id[64] = {0};

		snprintf(str_id, sizeof(str_id), "%u", i);

		if (get_dup_section_in_config_opt("users", "role", "role_id", str_id) == NULL) {
			*id = dmstrdup(str_id);
			break;
		}
	}

	return;
}

static bool user_exists(const char *user)
{
	struct uci_section *s = NULL, *stmp = NULL;
	uci_path_foreach_sections_safe(bbfdm, "dmmap_users", "user", stmp, s) {
		char *username = NULL;
		bbf_uci_get_value_by_section(s, "section_name", &username);
		if (DM_STRCMP(username, user) == 0)
			return true;
	}

	g_node *node = NULL, *tmp = NULL;
	list_for_each_entry_safe(node, tmp, &user_head, list) {
		if (DM_STRCMP(node->name, user) == 0)
			return true;
	}

	return false;
}

static void get_system_user_id(const char *user_name, char **value)
{
	char cmd[256] = {0};

	if (DM_STRLEN(user_name) == 0) {
		*value = dmstrdup("");
		return;
	}

	snprintf(cmd, sizeof(cmd), "cat /etc/passwd | grep -r \"^%s:\" | cut -d: -f3", user_name);
	FILE *pp = popen(cmd, "r"); // flawfinder: ignore
	if (pp != NULL) {
		char line[25] = {0};

		if (fgets(line, sizeof(line), pp) != NULL) {
			remove_new_line(line);
			*value = dmstrdup(line);
		} else {
			*value = dmstrdup("");
		}

		pclose(pp);
	} else {
		*value = dmstrdup("");
	}
}

static bool group_name_exists(const char *group_name)
{
	struct uci_section *s = NULL, *stmp = NULL;
	uci_path_foreach_sections_safe(bbfdm, "dmmap_users", "group", stmp, s) {
		char *name = NULL;
		bbf_uci_get_value_by_section(s, "section_name", &name);
		if (DM_STRCMP(name, group_name) == 0)
			return true;
	}

	g_node *node = NULL, *tmp = NULL;
	list_for_each_entry_safe(node, tmp, &group_head, list) {
		if (DM_STRCMP(node->name, group_name) == 0)
			return true;
	}

	return false;
}

static void get_system_group_id(const char *group_name, char **value)
{
	char cmd[256] = {0};

	if (DM_STRLEN(group_name) == 0) {
		*value = dmstrdup("");
		return;
	}

	snprintf(cmd, sizeof(cmd), "cat /etc/group | grep -r \"^%s:\" | cut -d: -f3", group_name);
	FILE *pp = popen(cmd, "r"); // flawfinder: ignore
	if (pp != NULL) {
		char line[25] = {0};

		if (fgets(line, sizeof(line), pp) != NULL) {
			remove_new_line(line);
			*value = dmstrdup(line);
		} else {
			*value = dmstrdup("");
		}

		pclose(pp);
	} else {
		*value = dmstrdup("");
	}
}

static bool static_entry(struct uci_section *s)
{
	char *value = NULL;
	bool val = false;

	if (s == NULL)
		return val;

	bbf_uci_get_value_by_section(s, "static", &value);
	if (DM_STRLEN(value) == 0)
		return val;
 
	bbf_convert_string_to_bool(value, &val);

	return val;
}

static void synchronize_users_config_sections_with_dmmap(const char *package, const char *section_type, const char *dmmap_package, struct list_head *dup_list)
{
	struct uci_section *s, *stmp;

	bbf_uci_foreach_sections(package, section_type, s) {
		char *val = NULL;
		bool btmp = false;

		/* Do not include deleted group or user */
		bbf_uci_get_value_by_section(s, "deleted", &val);
		bbf_convert_string_to_bool(val, &btmp);
		if (btmp) {
			continue;
		}

		/* Do not include hidden group or user */
		bbf_uci_get_value_by_section(s, "hidden", &val);
		bbf_convert_string_to_bool(val, &btmp);
		if (btmp) {
			continue;
		}

		/*
		 * create/update corresponding dmmap section that have same config_section link and using param_value_array
		 */
		struct uci_section *dmmap_sect = NULL;

		if ((dmmap_sect = get_dup_section_in_dmmap(dmmap_package, section_type, section_name(s))) == NULL) {
			bbf_uci_add_section_bbfdm(dmmap_package, section_type, &dmmap_sect);
			bbf_uci_set_value_by_section(dmmap_sect, "section_name", section_name(s));
			bbf_uci_set_value_by_section(dmmap_sect, "static", "0");
		}

		/*
		 * Add system and dmmap sections to the list
		 */
		add_dmmap_config_dup_list(dup_list, s, dmmap_sect);
	}

	/*
	 * Delete unused dmmap user/group sections
	 */
	uci_path_foreach_sections_safe(bbfdm, dmmap_package, section_type, stmp, s) {
		char *sec = NULL;

		bbf_uci_get_value_by_section(s, "section_name", &sec);
		if (get_origin_section_from_config(package, section_type, sec) == NULL) {
			bbf_uci_delete_section_by_section(s, NULL, NULL);
		}
	}
}

/*************************************************************
* ENTRY METHOD
**************************************************************/
/*#Device.Users.User.{i}.!UCI:users/user/dmmap_users*/
static int browseUserInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	char *inst = NULL;
	struct dm_data *p = NULL;
	LIST_HEAD(dup_list);

	synchronize_users_config_sections_with_dmmap("users", "user", "dmmap_users", &dup_list);
	list_for_each_entry(p, &dup_list, list) {

		inst = handle_instance(dmctx, parent_node, p->dmmap_section, "user_instance", "user_alias");

		if (DM_LINK_INST_OBJ(dmctx, parent_node, (void *)p, inst) == DM_STOP)
			break;
	}
	free_dmmap_config_dup_list(&dup_list);
	return 0;
}

static int browseGroupInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	char *inst = NULL;
	struct dm_data *p = NULL;
	LIST_HEAD(dup_list);

	synchronize_users_config_sections_with_dmmap("users", "group", "dmmap_users", &dup_list);
	list_for_each_entry(p, &dup_list, list) {

		inst = handle_instance(dmctx, parent_node, p->dmmap_section, "group_instance", "group_alias");

		if (DM_LINK_INST_OBJ(dmctx, parent_node, (void *)p, inst) == DM_STOP)
			break;
	}
	free_dmmap_config_dup_list(&dup_list);
	return 0;
}

static int browseShellInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	char *inst = NULL;
	struct dm_data *p = NULL;
	LIST_HEAD(dup_list);

	synchronize_specific_config_sections_with_dmmap("users", "shell", "dmmap_users", &dup_list);

	list_for_each_entry(p, &dup_list, list) {
		inst = handle_instance(dmctx, parent_node, p->dmmap_section, "shell_instance", "shell_alias");

		if (DM_LINK_INST_OBJ(dmctx, parent_node, (void *)p, inst) == DM_STOP)
			break;
	}
	free_dmmap_config_dup_list(&dup_list);
	return 0;
}

static int browseRoleInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	struct dm_data *p = NULL;
	char *inst = NULL;
	LIST_HEAD(dup_list);

	synchronize_specific_config_sections_with_dmmap("users", "role", "dmmap_users", &dup_list);
	list_for_each_entry(p, &dup_list, list) {

		inst = handle_instance(dmctx, parent_node, p->dmmap_section, "role_instance", "role_alias");

		if (DM_LINK_INST_OBJ(dmctx, parent_node, (void *)p, inst) == DM_STOP)
			break;
	}

	free_dmmap_config_dup_list(&dup_list);
	return 0;
}

/*************************************************************
* ADD & DEL OBJ
**************************************************************/
static int add_users_user(char *refparam, struct dmctx *ctx, void *data, char **instance)
{
	struct uci_section *s = NULL, *dmmap_user = NULL;
	char sec_name[40];

	snprintf(sec_name, sizeof(sec_name), "user_%s", *instance);

	if (bbf_uci_add_section("users", "user", &s) == 0) {
		bbf_uci_rename_section(s, sec_name);
		bbf_uci_set_value_by_section(s, "enabled", "0");
		bbf_uci_set_value_by_section(s, "static", "0");
		bbf_uci_add_section_bbfdm("dmmap_users", "user", &dmmap_user);
		bbf_uci_set_value_by_section(dmmap_user, "section_name", sec_name);
		bbf_uci_set_value_by_section(dmmap_user, "user_instance", *instance);
	}

	return 0;
}

static int add_users_group(char *refparam, struct dmctx *ctx, void *data, char **instance)
{
	struct uci_section *s = NULL, *dmmap_group = NULL;
	char sec_name[40];

	snprintf(sec_name, sizeof(sec_name), "%sgroup_%s", GROUP_PREFIX, *instance);

	if (bbf_uci_add_section("users", "group", &s) == 0) {
		bbf_uci_rename_section(s, sec_name);
		bbf_uci_set_value_by_section(s, "enabled", "0");
		bbf_uci_set_value_by_section(s, "static", "0");
		bbf_uci_add_section_bbfdm("dmmap_users", "group", &dmmap_group);
		bbf_uci_set_value_by_section(dmmap_group, "section_name", sec_name);
		bbf_uci_set_value_by_section(dmmap_group, "group_instance", *instance);
	}

	return 0;
}

static int add_users_role(char *refparam, struct dmctx *ctx, void *data, char **instance)
{
	struct uci_section *s = NULL, *dmmap_role = NULL;
	char sec_name[40];
	char *role_id = NULL;

	snprintf(sec_name, sizeof(sec_name), "role_%s", *instance);
	get_unique_role_id(&role_id);
	if (role_id == NULL)
		return -1;

	if (bbf_uci_add_section("users", "role", &s) == 0) {
		char role_name[65] = {0};

		bbf_uci_rename_section(s, sec_name);
		bbf_uci_set_value_by_section(s, "enabled", "0");
		bbf_uci_set_value_by_section(s, "role_id", role_id);
		snprintf(role_name, sizeof(role_name), "Default_%s", *instance);
		bbf_uci_set_value_by_section(s, "name", role_name);
		bbf_uci_set_value_by_section(s, "static", "0");

		bbf_uci_add_section_bbfdm("dmmap_users", "role", &dmmap_role);
		bbf_uci_set_value_by_section(dmmap_role, "section_name", sec_name);
		bbf_uci_set_value_by_section(dmmap_role, "role_instance", *instance);
	}

	return 0;
}

static int delete_users_user(char *refparam, struct dmctx *ctx, void *data, char *instance, unsigned char del_action)
{
	if (static_entry(((struct dm_data *)data)->config_section)) {
		bbfdm_set_fault_message(ctx, "Static user can not be deleted");
		return FAULT_9001;
	}

	bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "deleted", "1");
	bbf_uci_delete_section_by_section(((struct dm_data *)data)->dmmap_section, NULL, NULL);
	return 0;
}

static int delete_users_group(char *refparam, struct dmctx *ctx, void *data, char *instance, unsigned char del_action)
{
	char *gr_name = NULL;

	if (static_entry(((struct dm_data *)data)->config_section)) {
		bbfdm_set_fault_message(ctx, "Static group can not be deleted");
		return FAULT_9001;
	}

	bbf_uci_get_value_by_section(((struct dm_data *)data)->dmmap_section, "section_name", &gr_name);
	update_group_participation(gr_name, NULL, DELETE);

	bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "deleted", "1");
	bbf_uci_delete_section_by_section(((struct dm_data *)data)->dmmap_section, NULL, NULL);
	return 0;
}

static int delete_users_role(char *refparam, struct dmctx *ctx, void *data, char *instance, unsigned char del_action)
{
	char *role_name = NULL;

	if (static_entry(((struct dm_data *)data)->config_section)) {
		bbfdm_set_fault_message(ctx, "Static role can not be deleted");
		return FAULT_9001;
	}

	bbf_uci_get_value_by_section(((struct dm_data *)data)->config_section, "name", &role_name);
	update_role_participation(role_name, NULL, DELETE, "user");
	update_role_participation(role_name, NULL, DELETE, "group");

	bbf_uci_delete_section_by_section(((struct dm_data *)data)->config_section, NULL, NULL);
	bbf_uci_delete_section_by_section(((struct dm_data *)data)->dmmap_section, NULL, NULL);
	return 0;
}

/*************************************************************
* GET & SET PARAM
**************************************************************/
static int get_users_user_number_of_entries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = bbf_get_number_of_entries(ctx, data, instance, browseUserInst);
	dmasprintf(value, "%d", cnt);
	return 0;
}

static int get_users_group_number_of_entries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = bbf_get_number_of_entries(ctx, data, instance, browseGroupInst);
	dmasprintf(value, "%d", cnt);
	return 0;
}

static int get_users_shell_number_of_entries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = bbf_get_number_of_entries(ctx, data, instance, browseShellInst);
	dmasprintf(value, "%d", cnt);
	return 0;
}

static int get_users_role_number_of_entries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = bbf_get_number_of_entries(ctx, data, instance, browseRoleInst);
	dmasprintf(value, "%d", cnt);
	return 0;
}

static int get_user_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return bbf_get_alias(ctx, ((struct dm_data *)data)->dmmap_section, "user_alias", instance, value);
}

static int get_group_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return bbf_get_alias(ctx, ((struct dm_data *)data)->dmmap_section, "group_alias", instance, value);
}

static int get_shell_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return bbf_get_alias(ctx, ((struct dm_data *)data)->dmmap_section, "shell_alias", instance, value);
}

static int get_role_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return bbf_get_alias(ctx, ((struct dm_data *)data)->dmmap_section, "role_alias", instance, value);
}

static int get_shell_enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = bbf_uci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "enabled", "1");
	return 0;
}

static int get_param_enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = bbf_uci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "enabled", "0");
	return 0;
}

static int get_user_id(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *sec_name = NULL;

	bbf_uci_get_value_by_section(((struct dm_data *)data)->dmmap_section, "section_name", &sec_name);
	if (sec_name != NULL) {
		get_system_user_id(sec_name, value);
	}

	return 0;
}

static int get_group_id(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *sec_name = NULL, *group_name = NULL;

	bbf_uci_get_value_by_section(((struct dm_data *)data)->dmmap_section, "section_name", &sec_name);
	if (sec_name != NULL) {
		if (DM_STRNCMP(sec_name, GROUP_PREFIX, strlen(GROUP_PREFIX)) == 0)
			group_name = dmstrdup(sec_name + strlen(GROUP_PREFIX));
		else
			group_name = dmstrdup(sec_name);

		get_system_group_id(group_name, value);
	}

	return 0;
}

static int get_role_id(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	bbf_uci_get_value_by_section(((struct dm_data *)data)->config_section, "role_id", value);
	return 0;
}


static int get_param_name(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *sec = NULL;
	bbf_uci_get_value_by_section(((struct dm_data *)data)->dmmap_section, "section_name", &sec);

	if (DM_STRLEN(sec) != 0) {
		struct uci_section *s = ((struct dm_data *)data)->dmmap_section;
		char *type = section_type(s);
		if (DM_STRCMP(type, "group") == 0) {
			*value = dmstrdup(sec+strlen(GROUP_PREFIX));
		} else {
			*value = dmstrdup(sec);
		}
	}

	return 0;
}

static int get_shell_name(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	bbf_uci_get_value_by_section(((struct dm_data *)data)->config_section, "name", value);
	return 0;
}

static int get_role_name(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	bbf_uci_get_value_by_section(((struct dm_data *)data)->config_section, "name", value);
	return 0;
}

static int get_user_password(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	json_object *res = NULL;
	char *encrypted_password = NULL;

	bbf_uci_get_value_by_section(((struct dm_data *)data)->config_section, "encrypted_password", &encrypted_password);
	dmubus_call("bbf.secure", "decode", UBUS_ARGS{{"data", encrypted_password, String}}, 1, &res);
	DM_ASSERT(res, *value = dmstrdup(""));
	*value = dmjson_get_value(res, 1, "value");
	return 0;
}

#ifdef USERMNGR_ENABLE_VENDOR_EXT
static int get_user_password_expiry(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = bbf_uci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "password_expiry", "9999-12-31T23:59:59Z");
	return 0;
}
#endif

static int get_user_group_participation(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *groups = NULL;

	bbf_uci_get_value_by_section(((struct dm_data *)data)->config_section, "member_groups", &groups);

	if (groups != NULL) {
		char **arr;
		size_t length;
		char buf[2056] = {0};
		int i;

		arr = strsplit(groups, ",", &length);
		for (i = 0; i < length; i++) {
			char *groupname = arr[i] + strlen(GROUP_PREFIX);
			bbfdm_get_references(ctx, MATCH_ALL, "Device.Users.Group.", "Groupname", groupname, buf, sizeof(buf));
		}

		*value = dmstrdup(buf);
	}

	return 0;
}

static int get_role_participation(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *roles = NULL;

	bbf_uci_get_value_by_section(((struct dm_data *)data)->config_section, "member_roles", &roles);

	if (roles != NULL) {
		char **arr;
		size_t length;
		char buf[2056] = {0};
		int i;

		arr = strsplit(roles, ",", &length);
		for (i = 0; i < length; i++) {
			bbfdm_get_references(ctx, MATCH_ALL, "Device.Users.Role.", "RoleName", arr[i], buf, sizeof(buf));
		}

		*value = dmstrdup(buf);
	}

	return 0;
}

static int get_user_static_user(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	if (static_entry(((struct dm_data *)data)->config_section)) {
		*value = dmstrdup("1");
	} else {
		*value = dmstrdup("0");
	}

	return 0;
}

static int get_static_group(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	if (static_entry(((struct dm_data *)data)->config_section)) {
		*value = dmstrdup("1");
	} else {
		*value = dmstrdup("0");
	}

	return 0;
}

static int get_static_role(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	if (static_entry(((struct dm_data *)data)->config_section)) {
		*value = dmstrdup("1");
	} else {
		*value = dmstrdup("0");
	}
	return 0;
}

static int get_user_language(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	bbf_uci_get_value_by_section(((struct dm_data *)data)->config_section, "language", value);
	return 0;
}

static int get_user_shell(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *shell = NULL;

	bbf_uci_get_value_by_section(((struct dm_data *)data)->config_section, "shell", &shell);
	return _bbfdm_get_references(ctx, "Device.Users.SupportedShell.", "Name", shell, value);
}

static int set_user_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	return bbf_set_alias(ctx, ((struct dm_data *)data)->dmmap_section, "user_alias", instance, value);
}

static int set_group_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	return bbf_set_alias(ctx, ((struct dm_data *)data)->dmmap_section, "group_alias", instance, value);
}

static int set_shell_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	return bbf_set_alias(ctx, ((struct dm_data *)data)->dmmap_section, "shell_alias", instance, value);
}

static int set_role_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	return bbf_set_alias(ctx, ((struct dm_data *)data)->dmmap_section, "role_alias", instance, value);
}

static int set_shell_enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	bool b;

	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_boolean(ctx, value))
			return FAULT_9007;
		break;
	case VALUESET:
		bbf_convert_string_to_bool(value, &b);
		bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "enabled", b ? "1" : "0");
		break;
	}
	return 0;
}

static int set_param_enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	bool b;

	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_boolean(ctx, value))
			return FAULT_9007;
		break;
	case VALUESET:
		bbf_convert_string_to_bool(value, &b);
		bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "enabled", b ? "1" : "0");

		break;
	}
	return 0;
}

static int set_user_username(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char name[70] = {0};
	char *curr_name = NULL;

	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_string(ctx, value, -1, 64, NULL, NULL))
			return FAULT_9007;

		// Check if the value is empty
		if (*value == '\0')
			return FAULT_9007;

		// Check if user already exist
		snprintf(name, sizeof(name), "%s", value);
		replace_special_char(name, '_');

		// Check if same as current username
		curr_name = section_name(((struct dm_data *)data)->config_section);
		if (DM_STRCMP(curr_name, name) == 0)
			break;

		if (user_exists(name))
			return FAULT_9001;
		break;
	case VALUESET:
		snprintf(name, sizeof(name), "%s", value);
		replace_special_char(name, '_');

		// Check if same as current username
		curr_name = section_name(((struct dm_data *)data)->config_section);
		if (DM_STRCMP(curr_name, name) == 0)
			break;

		// Update dmmap_users file
		bbf_uci_set_value_by_section(((struct dm_data *)data)->dmmap_section, "section_name", name);

		// Update users config
		bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "old_name",
						section_name(((struct dm_data *)data)->config_section));
		bbf_uci_rename_section(((struct dm_data *)data)->config_section, name);
		break;
	}
	return 0;
}

static int set_group_name(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *gr_name = NULL;
	char name[70] = {0};

	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_string(ctx, value, -1, 64, NULL, NULL))
			return FAULT_9007;

		// Check if the value is empty
		if (*value == '\0')
			return FAULT_9007;

		snprintf(name, sizeof(name), "%s%s", GROUP_PREFIX, value);
		replace_special_char(name, '_');

		// Check if same as current groupname
		gr_name = section_name(((struct dm_data *)data)->config_section);
		if (DM_STRCMP(gr_name, name) == 0)
			break;

		if (group_name_exists(name))
			return FAULT_9001;

		break;
	case VALUESET:
		gr_name = section_name(((struct dm_data *)data)->config_section);
		snprintf(name, sizeof(name), "%s%s", GROUP_PREFIX, value);
		replace_special_char(name, '_');
		if (DM_STRCMP(gr_name, name) == 0)
			break;

		update_group_participation(gr_name, name, NAME_CHANGE);

		// Update dmmap_users file
		bbf_uci_set_value_by_section(((struct dm_data *)data)->dmmap_section, "section_name", name);

		// Update users config
		bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "old_name", gr_name);
		bbf_uci_rename_section(((struct dm_data *)data)->config_section, name);

		break;
	}

	return 0;
}

static int set_role_name(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *role_name = NULL;

	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_string(ctx, value, -1, 64, NULL, NULL))
			return FAULT_9007;

		// Check if the value is empty
		if (*value == '\0')
			return FAULT_9007;

		// Check if same as current role name
		bbf_uci_get_value_by_section(((struct dm_data *)data)->config_section, "name", &role_name);
		if (DM_STRCMP(role_name, value) == 0)
			break;

		// Check if role name already exists
		if (get_dup_section_in_config_opt("users", "role", "name", value) != NULL)
			return FAULT_9001;

		break;
	case VALUESET:
		// get current role name
		bbf_uci_get_value_by_section(((struct dm_data *)data)->config_section, "name", &role_name);
		if (DM_STRCMP(role_name, value) == 0)
			break;

		update_role_participation(role_name, value, NAME_CHANGE, "user");
		update_role_participation(role_name, value, NAME_CHANGE, "group");

		// Update users config
		bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "name", value);

		break;
	}

	return 0;
}

static int set_role_id(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *role_id = NULL;

	switch (action) {
	case VALUECHECK:
		// validate the value
		if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{NULL,NULL}}, 1))
			return FAULT_9007;

		// Check if same as current role id
		bbf_uci_get_value_by_section(((struct dm_data *)data)->config_section, "role_id", &role_id);
		if (DM_STRCMP(role_id, value) == 0)
			break;

		// Check if this id already exists
		if (get_dup_section_in_config_opt("users", "role", "role_id", value) != NULL)
			return FAULT_9001;

		break;
	case VALUESET:
		// get current role id
		bbf_uci_get_value_by_section(((struct dm_data *)data)->config_section, "role_id", &role_id);
		if (DM_STRCMP(role_id, value) == 0)
			break;

		// Update users config
		bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "role_id", value);

		break;
	}

	return 0;
}

static int set_user_password(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	json_object *res = NULL;

	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_string(ctx, value, -1, 64, NULL, NULL))
			return FAULT_9007;

		if (escape_char_exist(value)) {
			bbfdm_set_fault_message(ctx, "Invalid password, do not accept escape characters");
			return FAULT_9007;
		}

#ifdef USERMNGR_SECURITY_HARDENING
		char *auth_enable = dmuci_get_option_value_fallback_def("users", "users", "auth_policy_enable", "0");
		char *pwqc_enable = dmuci_get_option_value_fallback_def("users", "passwdqc", "enabled", "0");

		bool pw_enable = false, au_enable = false;
		string_to_bool(auth_enable, &au_enable);
		string_to_bool(pwqc_enable, &pw_enable);

		if (au_enable == true && pw_enable == true) {
			char *pwqc_min = NULL;
			char *pwqc_max = NULL;

			dmuci_get_option_value_string("users", "passwdqc", "min", &pwqc_min);
			dmuci_get_option_value_string("users", "passwdqc", "max", &pwqc_max);
			if (DM_STRLEN(pwqc_min) == 0 || DM_STRLEN(pwqc_max) == 0) {
				bbfdm_set_fault_message(ctx, "Invalid value of passwdqc option min=%s, max=%s", pwqc_min, pwqc_max);
				return FAULT_9007;
			}

			if (validate_password_quality(value, pwqc_min, pwqc_max) != 0) {
				bbfdm_set_fault_message(ctx, "Password is not enough strong");
				return FAULT_9007;
			}
		}
#endif

		dmubus_call("bbf.secure", "encode", UBUS_ARGS{{"data", value, String}}, 1, &res);
		if (res == NULL) {
			bbfdm_set_fault_message(ctx, "Can't encode the given passord");
			return FAULT_9007;
		}

		break;
	case VALUESET:
		dmubus_call("bbf.secure", "encode", UBUS_ARGS{{"data", value, String}}, 1, &res);
		char *str = dmjson_get_value(res, 1, "value");
		bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "encrypted_password", str);
		break;
	}
	return 0;
}

#ifdef USERMNGR_ENABLE_VENDOR_EXT
static int set_user_password_expiry(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	struct dm_data *p = (struct dm_data *)data;

	switch (action) {
		case VALUECHECK:
			if (bbfdm_validate_dateTime(ctx, value))
				return FAULT_9007;
			break;

		case VALUESET:
			if (DM_STRCMP(value, "9999-12-31T23:59:59Z") == 0) {
				/* no value in uci means no expiry */
				dmuci_set_value_by_section(p->config_section, "password_expiry", "");
			} else {
				dmuci_set_value_by_section(p->config_section, "password_expiry", value);
			}
			break;
	}

	return 0;
}
#endif

static int set_user_group_participation(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	struct dm_reference reference = {0};
	char gr_list[1025] = {0};
	char **arr;
	size_t length, gr_len, gr_left;
	int i;

	char *allowed_obj[] = {
		"Device.Users.Group.",
		NULL
	};

	switch (action) {
	case VALUECHECK:
		if (DM_STRLEN(value) == 0)
			break;

		arr = strsplit(value, ",", &length);
		for (i = 0; i < length; i++) {
			memset(&reference, 0 , sizeof(struct dm_reference));
			bbfdm_get_reference_linker(ctx, arr[i], &reference);

			if (DM_STRLEN(reference.path) == 0)
				continue;

			if (dm_validate_allowed_objects(ctx, &reference, allowed_obj))
				return FAULT_9007;

			gr_len = strlen(gr_list);
			gr_left = sizeof(gr_list) - gr_len;

			if (gr_left < strlen(reference.path))
				return FAULT_9004;

			snprintf(&gr_list[gr_len], gr_left, "%s%s", gr_len ? "," : "", reference.path);
		}

		gr_len = strlen(gr_list);
		if (gr_len && gr_list[gr_len - 1] == ',')
			gr_list[gr_len - 1] = '\0';

		if (bbfdm_validate_string_list(ctx, gr_list, -1, -1, 1024, -1, -1, NULL, NULL))
			return FAULT_9007;

		break;
	case VALUESET:
		if (DM_STRLEN(value) == 0) {
			bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "member_groups", "");
			break;
		}

		arr = strsplit(value, ",", &length);
		for (i = 0; i < length; i++) {
			memset(&reference, 0 , sizeof(struct dm_reference));
			bbfdm_get_reference_linker(ctx, arr[i], &reference);

			if (DM_STRLEN(reference.path) == 0 || DM_STRLEN(reference.value) == 0)
				continue;

			gr_len = strlen(gr_list);
			gr_left = sizeof(gr_list) - gr_len;

			if (gr_left < (strlen(reference.value) + strlen(GROUP_PREFIX)))
				return FAULT_9004;

			snprintf(&gr_list[gr_len], gr_left, "%s%s%s", gr_len ? "," : "", GROUP_PREFIX, reference.value);
		}

		gr_len = strlen(gr_list);
		if (gr_len && gr_list[gr_len - 1] == ',')
			gr_list[gr_len - 1] = '\0';

		bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "member_groups", gr_list);
		break;
	}

	return 0;
}

static int set_role_participation(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	struct dm_reference reference = {0};
	char role_list[1025] = {0};
	char **arr;
	size_t length, role_len, role_left;
	int i;

	char *allowed_obj[] = {
		"Device.Users.Role.",
		NULL
	};

	switch (action) {
	case VALUECHECK:
		if (DM_STRLEN(value) == 0)
			break;

		arr = strsplit(value, ",", &length);
		for (i = 0; i < length; i++) {
			memset(&reference, 0 , sizeof(struct dm_reference));
			bbfdm_get_reference_linker(ctx, arr[i], &reference);

			if (DM_STRLEN(reference.path) == 0)
				continue;

			if (dm_validate_allowed_objects(ctx, &reference, allowed_obj))
				return FAULT_9007;

			role_len = strlen(role_list);
			role_left = sizeof(role_list) - role_len;

			if (role_left < strlen(reference.path))
				return FAULT_9004;

			snprintf(&role_list[role_len], role_left, "%s%s", role_len ? "," : "", reference.path);
		}

		role_len = strlen(role_list);
		if (role_len && role_list[role_len - 1] == ',')
			role_list[role_len - 1] = '\0';

		if (bbfdm_validate_string_list(ctx, role_list, -1, -1, 1024, -1, -1, NULL, NULL))
			return FAULT_9007;

		break;
	case VALUESET:
		if (DM_STRLEN(value) == 0) {
			bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "member_roles", "");
			break;
		}

		arr = strsplit(value, ",", &length);
		for (i = 0; i < length; i++) {
			memset(&reference, 0 , sizeof(struct dm_reference));
			bbfdm_get_reference_linker(ctx, arr[i], &reference);

			if (DM_STRLEN(reference.path) == 0 || DM_STRLEN(reference.value) == 0)
				continue;

			role_len = strlen(role_list);
			role_left = sizeof(role_list) - role_len;

			if (role_left < strlen(reference.value))
				return FAULT_9004;

			snprintf(&role_list[role_len], role_left, "%s%s", role_len ? "," : "", reference.value);
		}

		role_len = strlen(role_list);
		if (role_len && role_list[role_len - 1] == ',')
			role_list[role_len - 1] = '\0';

		bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "member_roles", role_list);
		break;
	}

	return 0;
}

static int set_user_language(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_string(ctx, value, -1, 16, NULL, NULL))
			return FAULT_9007;
		break;
	case VALUESET:
		bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "language", value);
		break;
	}
	return 0;
}

static int set_user_shell(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	struct dm_reference reference = {0};
	char *allowed_obj[] = {
		"Device.Users.SupportedShell.",
		NULL
	};

	bbfdm_get_reference_linker(ctx, value, &reference);

	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_string(ctx, reference.path, -1, -1, NULL, NULL))
			return FAULT_9007;

		if (dm_validate_allowed_objects(ctx, &reference, allowed_obj))
			return FAULT_9007;
		break;
	case VALUESET:
		bbf_uci_set_value_by_section(((struct dm_data *)data)->config_section, "shell", reference.value);

		break;
	}

	return 0;
}

#ifdef USERMNGR_ENABLE_VENDOR_EXT
/*******************************
 * Helper functions
 ******************************/
static int get_complexity_index(const char *comp)
{
	if (DM_STRCMP(comp, "Low") == 0)
		return 0;

	if (DM_STRCMP(comp, "Fair") == 0)
		return 1;

	if (DM_STRCMP(comp, "Medium") == 0)
		return 3;

	if (DM_STRCMP(comp, "High") == 0)
		return 4;

	return -1;
}

static int set_passwdqc_min(const char *length, int comp_id)
{
	char buff[64] = {0};
	int i = 0, pos = 0;

	if (DM_STRLEN(length) == 0)
		return FAULT_9002;

	for (i = 0; i < 5; i++) {
		int avail_space = sizeof(buff) - pos;
		if (i < comp_id) {
			if (avail_space < 9)
				return FAULT_9002;

			pos += snprintf(&buff[pos], avail_space, "disabled");
		} else if (i == comp_id) {
			if (avail_space < (DM_STRLEN(length) + 1))
				return FAULT_9002;

			pos += snprintf(&buff[pos], avail_space, "%s", length);
		} else {
			if (avail_space < 2)
				return FAULT_9002;

			pos += snprintf(&buff[pos], avail_space, "%d", 0);
		}

		avail_space = sizeof(buff) - pos;
		if (i < 4) {
			if (avail_space < 2)
				return FAULT_9002;

			pos += snprintf(&buff[pos], avail_space, ",");
		}
	}

	dmuci_set_value("users", "passwdqc", "min", buff);

	return 0;
}

/**********************************
 * Set & Get handlers
 *********************************/
static int get_UserAuthentication_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_option_value_fallback_def("users", "users", "auth_policy_enable", "0");
	return 0;
}

static int set_UserAuthentication_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	bool b;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;
			break;
		case VALUESET:
			string_to_bool(value, &b);
			dmuci_set_value("users", "users", "auth_policy_enable", b ? "1" : "0");
			break;
	}
	return 0;
}

static int get_UsersAuthentication_SecPolicy(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_option_value_fallback_def("users", "authentication_policy", "enabled", "0");
	return 0;
}

static int set_UsersAuthentication_SecPolicy(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	bool b;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;
			break;
		case VALUESET:
			string_to_bool(value, &b);
			dmuci_set_value("users", "authentication_policy", "enabled", b ? "1" : "0");
			break;
	}
	return 0;
}

static int get_UsersAuthentication_FailDelay(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_option_value_fallback_def("users", "authentication_policy", "fail_delay", "3");
	return 0;
}

static int set_UsersAuthentication_FailDelay(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"1",NULL}}, 1))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value("users", "authentication_policy", "fail_delay", value);
			break;
	}
	return 0;
}

static int get_UsersAuthentication_FailLock(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_option_value_fallback_def("users", "authentication_policy", "faillock_attempts", "6");
	return 0;
}

static int set_UsersAuthentication_FailLock(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"1",NULL}}, 1))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value("users", "authentication_policy", "faillock_attempts", value);
			break;
	}
	return 0;
}

static int get_UsersAuthentication_LockTime(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_option_value_fallback_def("users", "authentication_policy", "faillock_lockout_time", "300");
	return 0;
}

static int set_UsersAuthentication_LockTime(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"1",NULL}}, 1))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value("users", "authentication_policy", "faillock_lockout_time", value);
			break;
	}
	return 0;
}

static int get_UsersAuthentication_PassQC(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_option_value_fallback_def("users", "passwdqc", "enabled", "0");
	return 0;
}

static int set_UsersAuthentication_PassQC(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	bool b;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;
			break;
		case VALUESET:
			string_to_bool(value, &b);
			dmuci_set_value("users", "passwdqc", "enabled", b ? "1" : "0");
			break;
	}
	return 0;
}

static int get_UsersAuthentication_SuppComp(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmstrdup("Low,Fair,Medium,High");
	return 0;
}

static int get_UsersAuthentication_PassComp(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *min = NULL;
	dmuci_get_option_value_string("users", "passwdqc", "min", &min);

	if (min == NULL)
		return 0;

	char **arr;
	size_t len;
	int i;

	arr = strsplit(min, ",", &len);
	for (i = 0; i < len; i++) {
		if (DM_STRCMP(arr[i], "disabled") == 0 || DM_STRCMP(arr[i], "0") == 0) {
			continue;
		}

		switch (i) {
		case 0:
			*value = dmstrdup("Low");
			break;
		case 1:
		case 2:
			*value = dmstrdup("Fair");
			break;
		case 3:
			*value = dmstrdup("Medium");
			break;
		case 4:
			*value = dmstrdup("High");
			break;
		default:
			break;
		}

		break;
	}

	return 0;
}

static int get_UsersAuthentication_MinPassLen(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *min = NULL;
	dmuci_get_option_value_string("users", "passwdqc", "min", &min);

	if (min == NULL)
		return 0;

	char **arr;
	size_t len;
	int i;

	arr = strsplit(min, ",", &len);
	for (i = 0; i < len; i++) {
		if (DM_STRCMP(arr[i], "disabled") == 0 || DM_STRCMP(arr[i], "0") == 0) {
			continue;
		}

		*value = dmstrdup(arr[i]);
		break;
	}

	return 0;
}

static int set_UsersAuthentication_PassComp(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *allowed_values[] = {"Low", "Fair", "Medium", "High", NULL};
	char *length = NULL;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, -1, allowed_values, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			get_UsersAuthentication_MinPassLen(refparam, ctx, data, instance, &length);
			if (length == NULL)
				return FAULT_9002;

			int comp_id = get_complexity_index(value);
			if (comp_id < 0)
				return FAULT_9002;

			return set_passwdqc_min(length, comp_id);
	}
	return 0;
}

static int set_UsersAuthentication_MinPassLen(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *comp = NULL;
	char *max = NULL;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"5","64"}}, 1))
				return FAULT_9007;

			dmuci_get_option_value_string("users", "passwdqc", "max", &max);
			if (DM_STRLEN(max) != 0) {
				unsigned int max_len = strtoul(max, NULL, 10);
				unsigned int min_len = strtoul(value, NULL, 10);

				if (min_len > max_len) {
					bbfdm_set_fault_message(ctx, "MinPasswordLen should be smaller or equal to MaxPasswordLen");
					return FAULT_9007;
				}
			}

			break;
		case VALUESET:
			get_UsersAuthentication_PassComp(refparam, ctx, data, instance, &comp);
			if (comp == NULL)
				return FAULT_9002;

			int comp_id = get_complexity_index(comp);
			if (comp_id < 0)
				return FAULT_9002;

			return set_passwdqc_min(value, comp_id);
	}
	return 0;
}

static int get_UsersAuthentication_MaxPassLen(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	dmuci_get_option_value_string("users", "passwdqc", "max", value);
	return 0;
}

static int set_UsersAuthentication_MaxPassLen(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *min = NULL;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"5","64"}}, 1))
				return FAULT_9007;

			get_UsersAuthentication_MinPassLen(refparam, ctx, data, instance, &min);
			if (DM_STRLEN(min) != 0) {
				unsigned int min_len = strtoul(min, NULL, 10);
				unsigned int max_len = strtoul(value, NULL, 10);

				if (max_len < min_len) {
					bbfdm_set_fault_message(ctx, "MaxPasswordLen should be bigger or equal to MinPasswordLen");
					return FAULT_9007;
				}
			}

			break;
		case VALUESET:
			dmuci_set_value("users", "passwdqc", "max", value);
			break;
	}
	return 0;
}
#endif

/**********************************************************************************************************************************
*                                            OBJ & LEAF DEFINITION
***********************************************************************************************************************************/
DM_MAP_OBJ tDynamicObj[] = {
/* parentObj, nextObject, parameter */
{"Device.", tUsersObj, NULL},
{0}
};

DMOBJ tUsersObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys*/
{"Users", &DMREAD, NULL, NULL, NULL, NULL, NULL, NULL, tDeviceUsersObj, tUsersParams, NULL, BBFDM_BOTH, NULL},
{0}
};

/* *** Device.Users. *** */
DMOBJ tDeviceUsersObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys, version*/
{"User", &DMWRITE, add_users_user, delete_users_user, NULL, browseUserInst, NULL, NULL, NULL, tUsersUserParams, NULL, BBFDM_BOTH, NULL},
{"Group", &DMWRITE, add_users_group, delete_users_group, NULL, browseGroupInst, NULL, NULL, NULL, tUsersGroupParams, NULL, BBFDM_BOTH, NULL},
{"SupportedShell", &DMREAD, NULL, NULL, NULL, browseShellInst, NULL, NULL, NULL, tUsersShellParams, NULL, BBFDM_BOTH, NULL},
{"Role", &DMWRITE, add_users_role, delete_users_role, NULL, browseRoleInst, NULL, NULL, NULL, tUsersRoleParams, NULL, BBFDM_BOTH, NULL},
#ifdef USERMNGR_ENABLE_VENDOR_EXT
{BBF_VENDOR_PREFIX"AuthenticationPolicy", &DMREAD, NULL, NULL, NULL, NULL, NULL, NULL, tUsersAuthenticationObj, tUsersAuthenticationParams, NULL, BBFDM_BOTH, NULL},
#endif
{0}
};

DMLEAF tUsersParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"UserNumberOfEntries", &DMREAD, DMT_UNINT, get_users_user_number_of_entries, NULL, BBFDM_BOTH},
{"GroupNumberOfEntries", &DMREAD, DMT_UNINT, get_users_group_number_of_entries, NULL, BBFDM_BOTH},
{"RoleNumberOfEntries", &DMREAD, DMT_UNINT, get_users_role_number_of_entries, NULL, BBFDM_BOTH},
{"SupportedShellNumberOfEntries", &DMREAD, DMT_UNINT, get_users_shell_number_of_entries, NULL, BBFDM_BOTH},
{"CheckCredentialsDiagnostics()", &DMSYNC, DMT_COMMAND, get_operate_args_CheckCred, operate_CheckCred, BBFDM_USP},
{0}
};

/* *** Device.Users.User.{i}. *** */
DMLEAF tUsersUserParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Alias", &DMWRITE, DMT_STRING, get_user_alias, set_user_alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Enable", &DMWRITE, DMT_BOOL, get_param_enable, set_param_enable, BBFDM_BOTH},
{"UserID", &DMREAD, DMT_UNINT, get_user_id, NULL, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Username", &DMWRITE, DMT_STRING, get_param_name, set_user_username, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Password", &DMWRITE, DMT_STRING, get_user_password, set_user_password, BBFDM_BOTH, DM_FLAG_SECURE},
#ifdef USERMNGR_ENABLE_VENDOR_EXT
{BBF_VENDOR_PREFIX"PasswordExpiry", &DMWRITE, DMT_STRING, get_user_password_expiry, set_user_password_expiry, BBFDM_BOTH},
#endif
{"GroupParticipation", &DMWRITE, DMT_STRING, get_user_group_participation, set_user_group_participation, BBFDM_BOTH, DM_FLAG_REFERENCE},
{"RoleParticipation", &DMWRITE, DMT_STRING, get_role_participation, set_role_participation, BBFDM_BOTH, DM_FLAG_REFERENCE},
{"StaticUser", &DMREAD, DMT_BOOL, get_user_static_user, NULL, BBFDM_BOTH},
{"Language", &DMWRITE, DMT_STRING, get_user_language, set_user_language, BBFDM_BOTH},
{"Shell", &DMWRITE, DMT_STRING, get_user_shell, set_user_shell, BBFDM_BOTH, DM_FLAG_REFERENCE},
{0}
};

/* *** Device.Users.Group.{i}. *** */
DMLEAF tUsersGroupParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Alias", &DMWRITE, DMT_STRING, get_group_alias, set_group_alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Enable", &DMWRITE, DMT_BOOL, get_param_enable, set_param_enable, BBFDM_BOTH},
{"GroupID", &DMREAD, DMT_UNINT, get_group_id, NULL, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Groupname", &DMWRITE, DMT_STRING, get_param_name, set_group_name, BBFDM_BOTH, DM_FLAG_UNIQUE|DM_FLAG_LINKER},
{"RoleParticipation", &DMWRITE, DMT_STRING, get_role_participation, set_role_participation, BBFDM_BOTH, DM_FLAG_REFERENCE},
{"StaticGroup", &DMREAD, DMT_BOOL, get_static_group, NULL, BBFDM_BOTH},
{0}
};

/* *** Device.Users.SupportedShell.{i}. *** */
DMLEAF tUsersShellParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Alias", &DMWRITE, DMT_STRING, get_shell_alias, set_shell_alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Enable", &DMWRITE, DMT_BOOL, get_shell_enable, set_shell_enable, BBFDM_BOTH},
{"Name", &DMREAD, DMT_STRING, get_shell_name, NULL, BBFDM_BOTH, DM_FLAG_UNIQUE|DM_FLAG_LINKER},
{0}
};

/* *** Device.Users.Role.{i}. *** */
DMLEAF tUsersRoleParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Alias", &DMWRITE, DMT_STRING, get_role_alias, set_role_alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Enable", &DMWRITE, DMT_BOOL, get_param_enable, set_param_enable, BBFDM_BOTH},
{"RoleID", &DMWRITE, DMT_UNINT, get_role_id, set_role_id, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"RoleName", &DMWRITE, DMT_STRING, get_role_name, set_role_name, BBFDM_BOTH, DM_FLAG_UNIQUE|DM_FLAG_LINKER},
{"StaticRole", &DMREAD, DMT_BOOL, get_static_role, NULL, BBFDM_BOTH},
{0}
};

#ifdef USERMNGR_ENABLE_VENDOR_EXT
DMLEAF tUsersAuthenticationParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Enable", &DMWRITE, DMT_BOOL, get_UserAuthentication_Enable, set_UserAuthentication_Enable, BBFDM_BOTH},
{0}
};

/* *** Device.Users.BBF_VENDOR_PREFIX_AuthenticationPolicy. *** */
DMOBJ tUsersAuthenticationObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys, version*/
{"Lockout", &DMREAD, NULL, NULL, NULL, NULL, NULL, NULL, NULL, tLockoutParams, NULL, BBFDM_BOTH, NULL},
{"Password", &DMREAD, NULL, NULL, NULL, NULL, NULL, NULL, NULL, tPasswordParams, NULL, BBFDM_BOTH, NULL},
{0}
};

/* *** Device.Users.BBF_VENDOR_PREFIX_AuthenticationPolicy.Lockout. *** */
DMLEAF tLockoutParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type*/
{"Enable", &DMWRITE, DMT_BOOL, get_UsersAuthentication_SecPolicy, set_UsersAuthentication_SecPolicy, BBFDM_BOTH},
{"FailureDelay", &DMWRITE, DMT_UNINT, get_UsersAuthentication_FailDelay, set_UsersAuthentication_FailDelay, BBFDM_BOTH},
{"FailureLockAttempts", &DMWRITE, DMT_UNINT, get_UsersAuthentication_FailLock, set_UsersAuthentication_FailLock, BBFDM_BOTH},
{"FailureLockDuration", &DMWRITE, DMT_UNINT, get_UsersAuthentication_LockTime, set_UsersAuthentication_LockTime, BBFDM_BOTH},
{0}
};

DMLEAF tPasswordParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type*/
{"QualityCheck", &DMWRITE, DMT_BOOL, get_UsersAuthentication_PassQC, set_UsersAuthentication_PassQC, BBFDM_BOTH},
{"SupportedComplexity", &DMREAD, DMT_STRING, get_UsersAuthentication_SuppComp, NULL, BBFDM_BOTH},
{"Complexity", &DMWRITE, DMT_STRING, get_UsersAuthentication_PassComp, set_UsersAuthentication_PassComp, BBFDM_BOTH},
{"MinLen", &DMWRITE, DMT_UNINT, get_UsersAuthentication_MinPassLen, set_UsersAuthentication_MinPassLen, BBFDM_BOTH},
{"MaxLen", &DMWRITE, DMT_UNINT, get_UsersAuthentication_MaxPassLen, set_UsersAuthentication_MaxPassLen, BBFDM_BOTH},
{0}
};
#endif
