/*
 * xmpp.c - XMPP core
 *
 * Copyright (C) 2022, IOPSYS Software Solutions AB.
 *
 * Author: Amin Ben Ramdhane <amin.benramdhane@pivasoftware.com>
 *
 * See LICENSE file for license related information.
 *
 */

#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strophe.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <math.h>

#include <syslog.h>
#include <libubus.h>
#include <libubox/blobmsg.h>

#include "xmpp.h"
#include "log.h"
#include "xuci.h"


#define KEEPALIVE_ID "s2c1"

struct xmpp_config cur_xmpp_conf = {0};
struct xmpp_connection cur_xmpp_con = {0};
struct xmpp_connection_stats cur_xmpp_stats = {0};
static unsigned int server_nbr = 0;
static unsigned int srv_priority_id = 0;

static int missed_pings = 0;
static bool awaiting_pong = false;

static void xmpp_connecting(void);

static bool file_exists(const char *path)
{
	struct stat buffer;

	return stat(path, &buffer) == 0;
}

static void save_xmpp_stats(void)
{
	FILE *fp;
	// cppcheck-suppress cert-MSC24-C
	fp = fopen(XMPP_STATS_PATH, "w");
	if (fp) {
		fprintf(fp, "%u %u %u %u", cur_xmpp_stats.received_messages,
								   cur_xmpp_stats.transmitted_messages,
								   cur_xmpp_stats.received_error_messages,
								   cur_xmpp_stats.transmitted_error_messages);
		fclose(fp);
	}
}

static void load_server(struct uci_section *s, unsigned int idx)
{
	char *res = NULL;

	res = xmpp_uci_get_value_bysection(s, "server_address");
	cur_xmpp_con.server[idx].server_addr = res ? strdup(res) : strdup("");
	FREE(res);

	res = xmpp_uci_get_value_bysection(s, "priority");
	cur_xmpp_con.server[idx].priority = res ? (int)strtol(res, NULL, 10) : 0;
	FREE(res);

	res = xmpp_uci_get_value_bysection(s, "weight");
	cur_xmpp_con.server[idx].weight = res ? (int)strtol(res, NULL, 10) : 0;
	FREE(res);

	res = xmpp_uci_get_value_bysection(s, "port");
	cur_xmpp_con.server[idx].port = res ? (int)strtol(res, NULL, 10) : 0;
	FREE(res);

	xmpp_syslog(SDEBUG,"XMPP Connectin Server %u server address: %s", idx, cur_xmpp_con.server[idx].server_addr);
	xmpp_syslog(SDEBUG,"XMPP Connectin Server %u priority: %u", idx, cur_xmpp_con.server[idx].priority);
	xmpp_syslog(SDEBUG,"XMPP Connectin Server %u weight: %d", idx, cur_xmpp_con.server[idx].weight);
	xmpp_syslog(SDEBUG,"XMPP Connectin Server %u port: %u", idx, cur_xmpp_con.server[idx].port);
}

static void sorting_list_server(unsigned int srv_nbr)
{
	struct xmpp_server tmp = {0};
	int i = 0, j = 0;

	for (i = 0; i < srv_nbr - 1; i++) {
		for (j = i + 1; j < srv_nbr; j++) {
			if (cur_xmpp_con.server[i].priority > cur_xmpp_con.server[j].priority) {
				tmp = cur_xmpp_con.server[i];
				cur_xmpp_con.server[i] = cur_xmpp_con.server[j];
				cur_xmpp_con.server[j] = tmp;
			} else if (cur_xmpp_con.server[i].priority == cur_xmpp_con.server[j].priority) {
				if (cur_xmpp_con.server[i].weight < cur_xmpp_con.server[j].weight) {
					tmp = cur_xmpp_con.server[i];
					cur_xmpp_con.server[i] = cur_xmpp_con.server[j];
					cur_xmpp_con.server[j] = tmp;
				}
			}
		}
	}
}

static void xmpp_exit(xmpp_ctx_t *ctx, xmpp_conn_t *conn, bool connected)
{
	xmpp_stop(ctx);
	if (connected) {
		xmpp_conn_release(conn);
		xmpp_ctx_free(ctx);
		xmpp_shutdown();
	}
}

static void xmpp_connection_server_free(void)
{
	for (int idx = 0; idx < server_nbr; idx++)
		FREE(cur_xmpp_con.server[idx].server_addr);

	FREE(cur_xmpp_con.server);
}

static void xmpp_con_exit(void)
{
	FREE(cur_xmpp_con.username);
	FREE(cur_xmpp_con.password);
	FREE(cur_xmpp_con.domain);
	FREE(cur_xmpp_con.resource);
	FREE(cur_xmpp_con.server_algorithm);
	FREE(cur_xmpp_conf.xmpp_allowed_jid);
	xmpp_connection_server_free();
}

static void send_stanza_cr_response(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void * const userdata)
{
	xmpp_stanza_t *reply = xmpp_stanza_new((xmpp_ctx_t *)userdata);
	if (!reply) {
		xmpp_syslog(SCRIT,"XMPP CR response Error");
		return;
	}

	xmpp_stanza_set_name(reply, "iq");
	xmpp_stanza_set_type(reply, "result");
	xmpp_stanza_set_attribute(reply, "from", xmpp_stanza_get_attribute(stanza, "to"));
	xmpp_stanza_set_attribute(reply, "id", xmpp_stanza_get_attribute(stanza, "id"));
	xmpp_stanza_set_attribute(reply, "to", xmpp_stanza_get_attribute(stanza, "from"));
	xmpp_send(conn, reply);
	xmpp_stanza_release(reply);

	cur_xmpp_stats.transmitted_messages++;
	save_xmpp_stats();
}

static void send_stanza_cr_error(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata, int xmpp_error)
{
	xmpp_ctx_t *ctx = (xmpp_ctx_t*)userdata;
	char *username = NULL, *password = NULL;

	xmpp_stanza_t *cr_stanza = xmpp_stanza_new(ctx);
	if (!cr_stanza) {
		xmpp_syslog(SCRIT,"XMPP CR response Error");
		return;
	}

	xmpp_stanza_set_name(cr_stanza, "iq");
	xmpp_stanza_set_type(cr_stanza, "error");
	xmpp_stanza_set_attribute(cr_stanza, "id", xmpp_stanza_get_attribute(stanza, "id"));
	xmpp_stanza_set_attribute(cr_stanza, "to", xmpp_stanza_get_attribute(stanza, "from"));
	xmpp_stanza_set_attribute(cr_stanza, "from", xmpp_stanza_get_attribute(stanza, "to"));

	// Connection Request Message
	xmpp_stanza_t *stanza_cr = xmpp_stanza_get_child_by_name(stanza, "connectionRequest");
	if (stanza_cr) {
		// Username
		xmpp_stanza_t *stanza_username = xmpp_stanza_get_child_by_name(stanza_cr, "username");
		if (stanza_username)
			username = xmpp_stanza_get_text(stanza_username);

		//Password
		xmpp_stanza_t *stanza_password = xmpp_stanza_get_child_by_name(stanza_cr, "password");
		if (stanza_password)
			password = xmpp_stanza_get_text(stanza_password);
			
		if (!password) {
			password = calloc(1, sizeof(char)); // Treats no password in stanza as empty string
			xmpp_syslog(SCRIT,"XMPP CR Password is empty!");
			if (!password) {
				xmpp_syslog(SCRIT, "XMPP CR response insufficient memory Error");
				// Clean-up
				xmpp_stanza_release(cr_stanza);
				xmpp_stanza_release(stanza_cr);
				xmpp_stanza_release(stanza_username);
				xmpp_stanza_release(stanza_password);
				FREE(username);
				return;
			}
		}
	}

	xmpp_stanza_t *stanza_cr_msg = xmpp_stanza_new(ctx);
	xmpp_stanza_set_name(stanza_cr_msg, "connectionRequest");
	xmpp_stanza_set_ns(stanza_cr_msg, XMPP_CR_NS);

	xmpp_stanza_t *stanza_username_msg = xmpp_stanza_new(ctx);
	xmpp_stanza_set_name(stanza_username_msg, "username");

	xmpp_stanza_t *username_msg = xmpp_stanza_new(ctx);
	xmpp_stanza_set_text(username_msg, username);
	xmpp_stanza_add_child(stanza_username_msg, username_msg);
	xmpp_stanza_release(username_msg);

	xmpp_stanza_add_child(stanza_cr_msg, stanza_username_msg);
	xmpp_stanza_release(stanza_username_msg);

	xmpp_stanza_t *stanza_password_msg = xmpp_stanza_new(ctx);
	xmpp_stanza_set_name(stanza_password_msg, "password");

	xmpp_stanza_t *password_msg = xmpp_stanza_new(ctx);
	xmpp_stanza_set_text(password_msg, password);
	xmpp_stanza_add_child(stanza_password_msg, password_msg);
	xmpp_stanza_release(password_msg);

	xmpp_stanza_add_child(stanza_cr_msg, stanza_password_msg);
	xmpp_stanza_release(stanza_password_msg);

	xmpp_stanza_add_child(cr_stanza, stanza_cr_msg);
	xmpp_stanza_release(stanza_cr_msg);

	xmpp_stanza_t *stanza_error = xmpp_stanza_new(ctx);
	xmpp_stanza_set_name(stanza_error, "error");
	if (xmpp_error == XMPP_SERVICE_UNAVAILABLE)
		xmpp_stanza_set_attribute(stanza_error, "code", "503");

	xmpp_stanza_set_type(stanza_error, "cancel");
	xmpp_stanza_t *stanza_service = xmpp_stanza_new(ctx);
	if (xmpp_error == XMPP_SERVICE_UNAVAILABLE)
		xmpp_stanza_set_name(stanza_service, "service-unavailable");
	else if (xmpp_error == XMPP_NOT_AUTHORIZED)
		xmpp_stanza_set_name(stanza_service, "not-authorized");

	xmpp_stanza_set_attribute(stanza_service, "xmlns", XMPP_ERROR_NS);
	xmpp_stanza_add_child(stanza_error, stanza_service);
	xmpp_stanza_release(stanza_service);

	xmpp_stanza_add_child(cr_stanza, stanza_error);
	xmpp_stanza_release(stanza_error);

	xmpp_send(conn, cr_stanza);
	xmpp_stanza_release(cr_stanza);

	cur_xmpp_stats.transmitted_error_messages++;
	save_xmpp_stats();

	// Free allocated memory
	FREE(username);
	FREE(password);
}

static bool check_xmpp_jid_authorized(const char *from)
{
	// XMPP config : allowed jabber id is empty
	if (cur_xmpp_conf.xmpp_allowed_jid == NULL || cur_xmpp_conf.xmpp_allowed_jid[0] == '\0') {
		xmpp_syslog(SDEBUG,"xmpp connection request handler : allowed jid is empty");
		return true;
	}

	// XMPP config : allowed jabber id is not empty
	char *spch = NULL;

	xmpp_syslog(SDEBUG,"xmpp connection request handler : check each jabber id");
	char *allowed_jid = strdup(cur_xmpp_conf.xmpp_allowed_jid);
	char *pch = strtok_r(allowed_jid, ",", &spch);
	while (pch != NULL) {
		if (strncmp(pch, from, strlen(pch)) == 0) {
			FREE(allowed_jid);
			xmpp_syslog(SDEBUG,"xmpp connection request handler : jabber id is authorized");
			return true;
		}
		pch = strtok_r(NULL, ",", &spch);
	}
	FREE(allowed_jid);

	xmpp_syslog(SDEBUG,"xmpp connection request handler : jabber id is not authorized");
	return false;
}

static int cr_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata)
{
	bool valid_ns = true, auth_status = false, service_available = false;

	char *name = (char *)xmpp_stanza_get_name(stanza);
	if (strcmp(name, "iq") != 0 && strcmp(name, "presence") != 0 && strcmp(name, "message") != 0)
		return 1;

	const char *type = xmpp_stanza_get_attribute(stanza, "type");
	if (strcmp(type, "error") == 0)
		cur_xmpp_stats.received_error_messages++;
	else
		cur_xmpp_stats.received_messages++;
	save_xmpp_stats();

	if (xmpp_stanza_get_child_by_name(stanza, "connectionRequest")) {
		const char *from = (char *)xmpp_stanza_get_attribute(stanza, "from");

		if (!check_xmpp_jid_authorized(from)) {
			service_available = false;
			xmpp_syslog(SDEBUG,"xmpp connection request handler not authorized by allowed jid");
			goto xmpp_end;
		}
	} else {
		xmpp_syslog(SDEBUG,"xmpp connection request handler does not contain an iq type");

		if (cur_xmpp_con.max_keepalive_fails > 0) {
			const char *id = xmpp_stanza_get_attribute(stanza, "id");
			if (id && strcmp(id, KEEPALIVE_ID) == 0) {
				missed_pings = 0;
				awaiting_pong = false;
			}
		}

		return 1;
	}

	xmpp_stanza_t *stanza_cr = xmpp_stanza_get_child_by_name(stanza, "connectionRequest");
	if (stanza_cr) {
		service_available = true;
		char *name_space = (char *)xmpp_stanza_get_attribute(stanza_cr, "xmlns");
		if (strcmp(name_space, XMPP_CR_NS) != 0) {
			valid_ns = false;
			goto xmpp_end; //send error response
		}

		xmpp_stanza_t *mech = xmpp_stanza_get_child_by_name(stanza_cr, "username");
		if (mech) {
			char *text = xmpp_stanza_get_text(mech);
			xmpp_uci_init();
			char *username = xmpp_uci_get_value("cwmp", "cpe", "userid");
			if (username && text && strcmp(text, username) == 0) {
				FREE(text);
				mech = xmpp_stanza_get_child_by_name(stanza_cr, "password");
				if (mech) {
					text = xmpp_stanza_get_text(mech);
					char *password = xmpp_uci_get_value("cwmp", "cpe", "passwd");
					auth_status = (password && text && (strcmp(text, password) == 0)) ? true : false;
					FREE(password);
				}
			}
			xmpp_uci_fini();
			FREE(username);
			FREE(text);
		}
	} else {
		service_available = false;
	}

xmpp_end:
	if (!valid_ns) {

		xmpp_syslog(SINFO, "XMPP Invalid Name space");
		send_stanza_cr_error(conn, stanza, userdata, XMPP_SERVICE_UNAVAILABLE);

	} else if (!service_available) {

		xmpp_syslog(SINFO, "XMPP Service Unavailable");
		send_stanza_cr_error(conn, stanza, userdata, XMPP_SERVICE_UNAVAILABLE);

	} else if (!auth_status) {

		xmpp_syslog(SINFO, "XMPP Not Authorized");
		send_stanza_cr_error(conn, stanza, userdata, XMPP_NOT_AUTHORIZED);

	} else {

		struct blob_buf bb;
		struct ubus_context *ubus_ctx;
		uint32_t id;

		xmpp_syslog(SINFO, "XMPP Authorized");
		send_stanza_cr_response(conn, stanza, userdata);

		ubus_ctx = ubus_connect(NULL);
		if (!ubus_ctx) {
			xmpp_syslog(SWARNING, "Failed to connect with ubus");
			return 1;
		}

		if (ubus_lookup_id(ubus_ctx, "tr069", &id) != 0) {
			xmpp_syslog(SWARNING, "tr069 ubus object not found");
			ubus_free(ubus_ctx);
			return 1;
		}

		memset(&bb, 0, sizeof(struct blob_buf));
		blob_buf_init(&bb, 0);
		blobmsg_add_string(&bb, "event", "6 CONNECTION REQUEST");

		ubus_invoke(ubus_ctx, id, "inform", bb.head, NULL, NULL, 3);
		blob_buf_free(&bb);
		ubus_free(ubus_ctx);
	}

	return 1;
}

static int ping_keepalive_handler(xmpp_conn_t * const conn, void * const userdata)
{
	xmpp_stanza_t *ping_ka, *ping;
	xmpp_ctx_t *ctx = (xmpp_ctx_t*)userdata;
	char jid[512] = {0};

	xmpp_syslog(SDEBUG, "XMPP PING OF KEEPALIVE ");

	if (awaiting_pong) {
		missed_pings++;
		if (cur_xmpp_con.max_keepalive_fails > 0 && missed_pings >= cur_xmpp_con.max_keepalive_fails) {
			xmpp_syslog(SINFO, "Exceeded max missed keep-alive responses, reconnecting");
			// the disconnect will trigger the reconnect
			xmpp_disconnect(conn);
			return 0; // Stop the timed handler
		}
	} else {
		missed_pings = 0;
	}

	snprintf(jid, sizeof(jid), "%s@%s/%s", cur_xmpp_con.username, cur_xmpp_con.domain, cur_xmpp_con.resource);

	ping_ka = xmpp_stanza_new(ctx);
	xmpp_stanza_set_name(ping_ka, "iq");
	xmpp_stanza_set_type(ping_ka, "get");
	xmpp_stanza_set_id(ping_ka, KEEPALIVE_ID);
	xmpp_stanza_set_attribute(ping_ka, "from", jid);
	xmpp_stanza_set_attribute(ping_ka, "to", cur_xmpp_con.domain);

	ping = xmpp_stanza_new(ctx);
	xmpp_stanza_set_name(ping, "ping");
	xmpp_stanza_set_attribute(ping, "xmlns", "urn:xmpp:ping");
	xmpp_stanza_add_child(ping_ka, ping);
	xmpp_stanza_release(ping);
	xmpp_send(conn, ping_ka);
	xmpp_stanza_release(ping_ka);

	cur_xmpp_stats.transmitted_messages++;
	save_xmpp_stats();

	if (cur_xmpp_con.max_keepalive_fails > 0) {
		awaiting_pong = true; // Set the flag indicating we're waiting for a response
	}

	return 1;
}

static void xmpp_set_conn_status(const char *value)
{
	xmpp_uci_init();
	char *con_name = xmpp_uci_get_value("xmpp", "xmpp", "conn_req_connection");
	xmpp_uci_fini();

	if (STRLEN(con_name) == 0) {
		xmpp_syslog(SINFO, "No xmpp connection found to capture status");
		FREE(con_name);
		return;
	}

	if (0 != xmpp_var_state_set_value("xmppc.conn_status.name", con_name)) {
		xmpp_syslog(SINFO, "UCI set operation failed to save connection details");
		FREE(con_name);
		return;
	}
	FREE(con_name);

	if (0 != xmpp_var_state_set_value("xmppc.conn_status.status", value)) {
		xmpp_syslog(SINFO, "UCI set operation failed to save connection details");
		return;
	}

	time_t now = time(NULL);
	char time_stamp[35] = {0};

	strftime(time_stamp, sizeof(time_stamp), "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
	if (0 != xmpp_var_state_set_value("xmppc.conn_status.last_change", time_stamp)) {
		xmpp_syslog(SINFO, "UCI set operation failed to change last_change");
	}
}

static void xmpp_reconnect(xmpp_ctx_t *ctx, xmpp_conn_t * const conn)
{
	xmpp_exit(ctx, conn, true);

	xmpp_syslog(SINFO,"XMPP Connection Retry");

	if (cur_xmpp_con.attempt_count == 0 && cur_xmpp_con.connect_attempt != 0) {

		if (cur_xmpp_con.retry_initial_interval != 0) {
			// cppcheck-suppress cert-MSC30-c
			sleep(rand()%cur_xmpp_con.retry_initial_interval);
		}

	} else if (cur_xmpp_con.attempt_count > cur_xmpp_con.connect_attempt) {

		xmpp_syslog(SINFO,"XMPP max connection attempt over, aborting connection");
		xmpp_exit(ctx, conn, false);
		xmpp_con_exit();
		xmpp_set_conn_status("Error");
		exit(EXIT_FAILURE);

	} else if (cur_xmpp_con.attempt_count >= 1 && cur_xmpp_con.connect_attempt != 0) {

		// Compute wait interval according to spec
		int n = cur_xmpp_con.attempt_count + 1; // n is the attempt number starting from 1

		double interval_multiplier = cur_xmpp_con.retry_interval_multiplier / 1000.0;
		double wait_interval_max = cur_xmpp_con.retry_initial_interval * pow(interval_multiplier, n - 1);

		// Cap at ServerRetryMaxInterval
		if (wait_interval_max > cur_xmpp_con.retry_max_interval)
			wait_interval_max = cur_xmpp_con.retry_max_interval;


		// cppcheck-suppress cert-MSC30-c
		int sleep_time = rand() % ((int)wait_interval_max + 1);
		xmpp_syslog(SDEBUG, "Attempt %d: Waiting for %d seconds before reconnecting (max interval: %f seconds)", n, sleep_time, wait_interval_max);
		sleep(sleep_time);

	} else
		sleep(DEFAULT_XMPP_RECONNECTION_RETRY);

	cur_xmpp_con.attempt_count += 1;
	xmpp_connecting();
}

static void conn_handler(xmpp_conn_t * const conn, const xmpp_conn_event_t status,
		const int error, xmpp_stream_error_t * const stream_error,
		void * const userdata)
{
	xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata;

	if (status == XMPP_CONN_CONNECT) {
		xmpp_syslog(SINFO,"XMPP Connection Established");
		cur_xmpp_con.attempt_count = 0;
		missed_pings = 0;
		awaiting_pong = false;
		xmpp_handler_add(conn, cr_handler, NULL, "iq", NULL, ctx);
		xmpp_timed_handler_add(conn, ping_keepalive_handler, cur_xmpp_con.keepalive_interval * 1000, userdata);

		xmpp_stanza_t *pres = xmpp_stanza_new(ctx);
		xmpp_stanza_set_name(pres, "presence");
		xmpp_send(conn, pres);
		xmpp_stanza_release(pres);

		cur_xmpp_stats.transmitted_messages++;
		save_xmpp_stats();

		xmpp_conn_set_keepalive(conn, 30, cur_xmpp_con.keepalive_interval);
	} else {
		xmpp_syslog(SINFO,"XMPP Connection Lost");

		if (status == XMPP_CONN_DISCONNECT) {
			if (strcmp(cur_xmpp_con.server_algorithm, "ServerTable") == 0) {
				srv_priority_id ++;
				if (srv_priority_id == server_nbr)
					srv_priority_id = 0;
			}
		}

		xmpp_reconnect(ctx, conn);
	}
}

static void xmpp_connecting(void)
{
	xmpp_log_t log_xmpp;
	char jid[512] = {0};
	int connected = 0;
	long flags = 0;

	xmpp_initialize();
	int xmpp_mesode_log_level = xmpp_log_get_level(cur_xmpp_conf.xmpp_loglevel);
	log_xmpp.handler = &xmpp_syslog_handler;
	log_xmpp.userdata = &(xmpp_mesode_log_level);
	xmpp_ctx_t *ctx = xmpp_ctx_new(NULL, &log_xmpp);
	xmpp_conn_t *conn = xmpp_conn_new(ctx);

	if(cur_xmpp_con.usetls)
		flags |= XMPP_CONN_FLAG_MANDATORY_TLS; /* Set flag XMPP_CONN_FLAG_MANDATORY_TLS to oblige the verification of tls */
	else
		flags |= XMPP_CONN_FLAG_TRUST_TLS; /* Set flag XMPP_CONN_FLAG_TRUST_TLS to ignore result of the verification */
	xmpp_conn_set_flags(conn, flags);

	snprintf(jid, sizeof(jid), "%s@%s/%s", cur_xmpp_con.username, cur_xmpp_con.domain, cur_xmpp_con.resource);

	xmpp_conn_set_jid(conn, jid);
	xmpp_conn_set_pass(conn, cur_xmpp_con.password);

	xmpp_set_conn_status("Connecting");
	/* initiate connection */
	if (strcmp(cur_xmpp_con.server_algorithm, "DNS-SRV") == 0 ||
		strcmp(cur_xmpp_con.server_algorithm, "DNS") == 0) {
		connected = xmpp_connect_client(conn, NULL, 0, conn_handler, ctx);
	} else {
		if (cur_xmpp_con.server)
			connected = xmpp_connect_client(conn, cur_xmpp_con.server[srv_priority_id].server_addr[0] ? cur_xmpp_con.server[srv_priority_id].server_addr : NULL,
					cur_xmpp_con.server[srv_priority_id].port, conn_handler, ctx);
	}

	if (connected < 0 ) {
		xmpp_reconnect(ctx, conn);
	} else {
		cur_xmpp_con.attempt_count = 0;
		xmpp_syslog(SDEBUG,"XMPP Handle Connection");
		xmpp_set_conn_status("Enabled");
		xmpp_run(ctx);
	}
}

static void xmpp_global_conf(void)
{
	xmpp_uci_init();

	// XMPP Log Level
	char *loglevel = xmpp_uci_get_value("xmpp", "xmpp", "loglevel");
	if (STRLEN(loglevel) != 0)
		cur_xmpp_conf.xmpp_loglevel = (int)strtol(loglevel, NULL, 10);
	else
		cur_xmpp_conf.xmpp_loglevel = DEFAULT_LOGLEVEL;
	xmpp_syslog(SDEBUG,"Log Level of XMPP connection is :%d", cur_xmpp_conf.xmpp_loglevel);
	FREE(loglevel);

	// XMPP Allowed Jabber id
	char *allowed_jid = xmpp_uci_get_value("xmpp", "xmpp", "allowed_jid");
	if (STRLEN(allowed_jid) != 0) {
		cur_xmpp_conf.xmpp_allowed_jid = strdup(allowed_jid);
		xmpp_syslog(SDEBUG,"XMPP connection allowed jaber id :%s", cur_xmpp_conf.xmpp_allowed_jid);
	} else {
		cur_xmpp_conf.xmpp_allowed_jid = strdup("");
		xmpp_syslog(SDEBUG,"XMPP connection allowed jaber id is empty");
	}
	FREE(allowed_jid);

	xmpp_uci_fini();
}

static void xmpp_con_init(void)
{
	struct uci_section *s = NULL;
	char *res = NULL;
	char *addr = NULL;
	unsigned int nbr = 0;

	xmpp_uci_init();

	char *con_name = xmpp_uci_get_value("xmpp", "xmpp", "conn_req_connection");
	if (STRLEN(con_name) == 0) {
		xmpp_syslog(SINFO, "No xmpp connection found");
		FREE(con_name);
		xmpp_uci_fini();
		exit(EXIT_SUCCESS);
	}

	res = xmpp_uci_get_value("xmpp", con_name, "enable");
	if (!res || strcmp(res, "1") != 0) {
		xmpp_syslog(SINFO, "the current xmpp connection is disabled");
		FREE(res);
		FREE(con_name);
		xmpp_uci_fini();
		exit(EXIT_SUCCESS);
	}
	FREE(res);

	cur_xmpp_con.attempt_count = 0;

	res = xmpp_uci_get_value("xmpp", con_name, "username");
	cur_xmpp_con.username = res ? strdup(res) : strdup("");
	FREE(res);

	res = xmpp_uci_get_value("xmpp", con_name, "password");
	cur_xmpp_con.password = res ? strdup(res) : strdup("");
	FREE(res);

	res = xmpp_uci_get_value("xmpp", con_name, "domain");
	cur_xmpp_con.domain = res ? strdup(res) : strdup("");
	FREE(res);

	res = xmpp_uci_get_value("xmpp", con_name, "resource");
	cur_xmpp_con.resource = res ? strdup(res) : strdup("");
	FREE(res);

	res = xmpp_uci_get_value("xmpp", con_name, "usetls");
	cur_xmpp_con.usetls = res ? (int)strtol(res, NULL, 10) : 0;
	FREE(res);

	res = xmpp_uci_get_value("xmpp", con_name, "serveralgorithm");
	cur_xmpp_con.server_algorithm = res ? strdup(res) : strdup("DNS-SRV");
	FREE(res);

	res = xmpp_uci_get_value("xmpp", con_name, "interval");
	cur_xmpp_con.keepalive_interval = res ? (int)strtol(res, NULL, 10) : 30;
	FREE(res);

	res = xmpp_uci_get_value("xmpp", con_name, "attempt");
	cur_xmpp_con.connect_attempt = res ? (int)strtol(res, NULL, 10) : 16;
	FREE(res);

	if (cur_xmpp_con.connect_attempt) {
		res = xmpp_uci_get_value("xmpp", con_name, "initial_retry_interval");
		cur_xmpp_con.retry_initial_interval = STRLEN(res) ? (int)strtol(res, NULL, 10) : DEFAULT_RETRY_INITIAL_INTERVAL;
		FREE(res);

		res = xmpp_uci_get_value("xmpp", con_name, "retry_interval_multiplier");
		cur_xmpp_con.retry_interval_multiplier = STRLEN(res) ? (int)strtol(res, NULL, 10) : DEFAULT_RETRY_INTERVAL_MULTIPLIER;
		FREE(res);

		res = xmpp_uci_get_value("xmpp", con_name, "retry_max_interval");
		cur_xmpp_con.retry_max_interval = STRLEN(res) ? (int)strtol(res, NULL, 10) : DEFAULT_RETRY_MAX_INTERVAL;
		FREE(res);
	}

	res = xmpp_uci_get_value("xmpp", con_name, "max_keepalive_fails");
	cur_xmpp_con.max_keepalive_fails = STRLEN(res) ? (int)strtol(res, NULL, 10) : 0;
	FREE(res);

	xmpp_syslog(SDEBUG,"XMPP Connection name: %s", con_name);
	xmpp_syslog(SDEBUG,"XMPP username: %s", cur_xmpp_con.username);
	xmpp_syslog(SDEBUG,"XMPP password: %s", cur_xmpp_con.password);
	xmpp_syslog(SDEBUG,"XMPP domain: %s", cur_xmpp_con.domain);
	xmpp_syslog(SDEBUG,"XMPP resource: %s", cur_xmpp_con.resource);
	xmpp_syslog(SDEBUG,"XMPP use_tls: %d", cur_xmpp_con.usetls);
	xmpp_syslog(SDEBUG,"XMPP server_algorithm: %s", cur_xmpp_con.server_algorithm);
	xmpp_syslog(SDEBUG,"XMPP keepalive_interval: %d", cur_xmpp_con.keepalive_interval);
	xmpp_syslog(SDEBUG,"XMPP max_keepalive_fails: %d", cur_xmpp_con.max_keepalive_fails);
	xmpp_syslog(SDEBUG,"XMPP connect_attempt: %d", cur_xmpp_con.connect_attempt);
	xmpp_syslog(SDEBUG,"XMPP retry_initial_interval: %d", cur_xmpp_con.retry_initial_interval);
	xmpp_syslog(SDEBUG,"XMPP retry_interval_multiplier: %d", cur_xmpp_con.retry_interval_multiplier);
	xmpp_syslog(SDEBUG,"XMPP retry_max_interval: %d", cur_xmpp_con.retry_max_interval);

	xmpp_uci_foreach_section("xmpp", "connection_server", s) {
		res = xmpp_uci_get_value_bysection(s, "con_name");
		if (res == NULL || strcmp(res, con_name) != 0) {
			FREE(res);
			continue;
		}
		FREE(res);

		res = xmpp_uci_get_value_bysection(s, "enable");
		addr = xmpp_uci_get_value_bysection(s, "server_address");
		if ((res && strcmp(res, "1") == 0) && (STRLEN(addr) != 0)) {
			cur_xmpp_con.server = realloc(cur_xmpp_con.server, (nbr + 1) * sizeof(struct xmpp_server));
			load_server(s, nbr);
			nbr++;
		}
		FREE(res);
		FREE(addr);
	}

	if (nbr != 0) {
		server_nbr = nbr;
		sorting_list_server(nbr);
	} else if (strcmp(cur_xmpp_con.server_algorithm, "ServerTable") == 0) {
		xmpp_syslog(SCRIT, "No ServerTable Configured and enabled");
		FREE(con_name);
		xmpp_uci_fini();
		exit(EXIT_FAILURE);
	}

	xmpp_uci_fini();
	FREE(con_name);
}

int main(void)
{
	time_t now = time(NULL);
	char time_stamp[35] = {0};

	srand(now);
	openlog("xmppc", 0, LOG_DAEMON);
	xmpp_global_conf();
	xmpp_syslog(SINFO,"START XMPPC");

	if (!file_exists("/var/state/xmppc")) {
		creat("/var/state/xmppc", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	}

	/* add sections */
	xmpp_var_state_set_value("xmppc.global", "global");
	xmpp_var_state_set_value("xmppc.conn_status", "conn_status");

	strftime(time_stamp, sizeof(time_stamp), "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
	if (0 != xmpp_var_state_set_value("xmppc.global.last_change", time_stamp)) {
		xmpp_syslog(SINFO, "UCI set operation failed to change last_change");
	}

	xmpp_con_init();
	xmpp_connecting();

	xmpp_syslog(SINFO,"EXIT XMPPC");
	xmpp_con_exit();
	closelog();

	return 0;
}
