/*
 * Copyright (C) 2024 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>
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libubus.h>
#include <libubox/uloop.h>
#include <libbbfdm-ubus/bbfdm-ubus.h>

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

extern DM_MAP_OBJ tDynamicObj[];
static struct bbfdm_context bbfdm_ctx = {0};
static struct ubus_event_handler ev = {0};
struct list_head user_head;
struct list_head group_head;

static void load_system_users(void)
{
	char cmd[256] = {0};
	char line[65] = {0};
	g_node *node = NULL;

	memset(&user_head, 0, sizeof(struct list_head));
	INIT_LIST_HEAD(&user_head);

	snprintf(cmd, sizeof(cmd), "cat /etc/passwd | cut -d: -f1");

	FILE *pp = popen(cmd, "r"); // flawfinder: ignore
	if (pp != NULL) {
		while (fgets(line, sizeof(line), pp) != NULL) {
			char name[70] = {0};

			remove_new_line(line);
			snprintf(name, sizeof(name), "%s", line);

			if (get_section_from_config(STD_UCI_PATH, "users", "user", name) != NULL) {
				continue;
			}

			node = (g_node *)malloc(sizeof(g_node));
			if (node == NULL) {
				break;
			}

			memset(node, 0, sizeof(g_node));
			snprintf(node->name, sizeof(node->name), "%s", line);
			INIT_LIST_HEAD(&node->list);
			list_add_tail(&node->list, &user_head);
		}

		pclose(pp);
	}
}

static void free_system_users(void)
{
	g_node *node = NULL, *tmp = NULL;

	list_for_each_entry_safe(node, tmp, &user_head, list) {
		list_del(&node->list);
		free(node);
	}
}

static void load_system_groups(void)
{
	char cmd[256] = {0};
	char line[65] = {0};
	g_node *node = NULL;

	memset(&group_head, 0, sizeof(struct list_head));
	INIT_LIST_HEAD(&group_head);

	snprintf(cmd, sizeof(cmd), "cat /etc/group | cut -d: -f1");

	FILE *pp = popen(cmd, "r"); // flawfinder: ignore
	if (pp != NULL) {
		while (fgets(line, sizeof(line), pp) != NULL) {
			char name[70] = {0};

			remove_new_line(line);
			snprintf(name, sizeof(name), "%s%s", GROUP_PREFIX, line);

			if (get_section_from_config(STD_UCI_PATH, "users", "group", name) != NULL) {
				continue;
			}

			node = (g_node *)malloc(sizeof(g_node));
			if (node == NULL) {
				break;
			}

			memset(node, 0, sizeof(g_node));
			snprintf(node->name, sizeof(node->name), "%s%s", GROUP_PREFIX, line);
			INIT_LIST_HEAD(&node->list);
			list_add_tail(&node->list, &group_head);
		}

		pclose(pp);
	}
}

static void free_system_groups(void)
{
	g_node *node = NULL, *tmp = NULL;

	list_for_each_entry_safe(node, tmp, &group_head, list) {
		list_del(&node->list);
		free(node);
	}
}

static void create_groups(void)
{
	struct uci_section *s = NULL;

	usermgr_uci_init(STD_UCI_PATH);

	usermgr_uci_foreach_section("users", "group", s) {
		char *group_name = NULL;
		const char *val = NULL;

		int len = strlen(GROUP_PREFIX);

		if (strncmp(s->e.name, GROUP_PREFIX, len) == 0)
			group_name = strdup(s->e.name + len);
		else
			group_name = strdup(s->e.name);

		val = usermgr_uci_get_value_by_section(s, "enabled");
		bool enable = dmuci_string_to_boolean(val);

		val = usermgr_uci_get_value_by_section(s, "deleted");
		bool deleted = dmuci_string_to_boolean(val);

		const char *old_name = usermgr_uci_get_value_by_section(s, "old_name");

		if (deleted) {
			del_group(group_name);
			usermgr_uci_delete_by_section(s, NULL, NULL);
			FREE(group_name);
			continue;
		}

		if (strlen(old_name) != 0) {
			del_group(old_name);
			usermgr_uci_set_value_by_section(s, "old_name", "");
		}

		if (enable) {
			add_group(group_name);
		} else {
			del_group(group_name);
		}

		FREE(group_name);
	}

	usermgr_uci_fini("users");
}

static void create_users(void)
{
	struct uci_section *s = NULL;

	usermgr_uci_init(STD_UCI_PATH);

	usermgr_uci_foreach_section("users", "user", s) {
		const char *val = NULL;
		char *user_name = strdup(s->e.name);

		val = usermgr_uci_get_value_by_section(s, "enabled");
		bool enable = dmuci_string_to_boolean(val);

		val = usermgr_uci_get_value_by_section(s, "deleted");
		bool deleted = dmuci_string_to_boolean(val);

		const char *old_name = usermgr_uci_get_value_by_section(s, "old_name");
		const char *member_groups = usermgr_uci_get_value_by_section(s, "member_groups");
		const char *shell = usermgr_uci_get_value_by_section(s, "shell");
		const char *password = usermgr_uci_get_value_by_section(s, "encrypted_password");

		val = usermgr_uci_get_value_by_section(s, "hidden");
		bool hidden = dmuci_string_to_boolean(val);

		if (deleted) {
			del_user(user_name);
			usermgr_uci_delete_by_section(s, NULL, NULL);
			continue;
		}

		if (strlen(old_name) != 0) {
			change_username(old_name, user_name);
			usermgr_uci_set_value_by_section(s, "old_name", "");
		}

		if (!user_exist(user_name)) {
			add_user(user_name);
		}

		if (strlen(password) != 0) {
			change_password(user_name, password, true);
		} else {
			password = usermgr_uci_get_value_by_section(s, "password_hash");
			if (strlen(password) != 0) {
				change_password(user_name, password, false);
			}
		}

		configure_user_shell(user_name, shell, hidden);
		configure_user_group(user_name, member_groups);

		if (enable)
			enable_user(user_name);
		else
			disable_user(user_name);

		FREE(user_name);
	}

	usermgr_uci_fini("users");
}

static void config_reload_cb(struct ubus_context *ctx __attribute__((unused)),
			struct ubus_event_handler *ev __attribute__((unused)),
			const char *type __attribute__((unused)),
			struct blob_attr *msg __attribute__((unused)))
{
	create_groups();
	create_users();
}

static int register_config_change(struct ubus_context *uctx)
{
	int ret;

	memset(&ev, 0, sizeof(struct ubus_event_handler));
	ev.cb = config_reload_cb;

	ret = ubus_register_event_handler(uctx, &ev, "usermngr.reload");
	if (ret)
		return -1;

	return 0;
}

static void usage(const char *prog)
{
	fprintf(stderr, "Usage: %s [options]\n", prog);
	fprintf(stderr, "\n");
	fprintf(stderr, "options:\n");
	fprintf(stderr, "    -l  <0-7> Set the loglevel\n");
	fprintf(stderr, "    -d <schema dm>  Display the schema data model supported by micro-service\n");
	fprintf(stderr, "    -h  Displays this help\n");
	fprintf(stderr, "\n");
}

int main(int argc, char **argv)
{
	int log_level = 7;
	int c = 0, dm_type = 0;

	while ((c = getopt(argc, argv, "hdl:")) != -1) {
		switch (c) {
		case 'l':
			log_level = (int)strtod(optarg, NULL);
			if (log_level < 0 || log_level > 7) {
				log_level = 7;
			}
			break;
		case 'd':
			dm_type++;
			break;
		case 'h':
			usage(argv[0]);
			exit(0);
		default:
			usage(argv[0]);
			exit(0);
		}
	}

	memset(&bbfdm_ctx, 0, sizeof(struct bbfdm_context));

	openlog(argv[0], LOG_CONS | LOG_PID | LOG_NDELAY, LOG_DAEMON);
	bbfdm_ubus_set_service_name(&bbfdm_ctx, "usermngr");
	bbfdm_ubus_set_log_level(log_level);
	bbfdm_ubus_load_data_model(tDynamicObj);

	if (dm_type > 0) {
		int res = bbfdm_print_data_model_schema(&bbfdm_ctx, dm_type);
		exit(res);
	}

	load_system_users();
	load_system_groups();

	create_groups();
	create_users();

	if (bbfdm_ubus_register_init(&bbfdm_ctx))
		goto out;

	if (register_config_change(bbfdm_ctx.ubus_ctx) != 0)
		goto out;

	uloop_run();

out:
	free_system_users();
	free_system_groups();
	bbfdm_ubus_register_free(&bbfdm_ctx);
	return 0;
}
